diff --git a/README.md b/README.md index b159508..82a08e3 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,7 @@ syncgames doctor | [docs/protocol.md](docs/protocol.md) | Shared SSOT protocol (Python ↔ Android) | | [docs/nas-minio-cloudflare.md](docs/nas-minio-cloudflare.md) | MinIO + NGINX + Cloudflare | | [docs/add-game.md](docs/add-game.md) | Add / remove games | +| [docs/seamless-save-paths.md](docs/seamless-save-paths.md) | Seamless Coop `.co2` locations (per-device) | | [docs/fallback-path1.md](docs/fallback-path1.md) | Syncthing fallback | | [docs/planning/](docs/planning/) | Planning archive | diff --git a/agent/syncgames/watchers.py b/agent/syncgames/watchers.py index 073a4b5..826e908 100644 --- a/agent/syncgames/watchers.py +++ b/agent/syncgames/watchers.py @@ -1,6 +1,7 @@ from __future__ import annotations import time +from pathlib import Path from typing import Callable from syncgames.config import GameConfig @@ -26,18 +27,21 @@ def is_game_running(game: GameConfig) -> bool: pname = _norm(proc.info["name"] or "") if names and pname in names: return True - if names and any(pname == _norm(n) for n in names): - return True cmd = proc.info.get("cmdline") or [] - joined = " ".join(cmd).lower() - if appid and ( - f"appid={appid}" in joined - or f"steam_appid={appid}" in joined - or f"AppId={appid}" in joined - ): - return True - for n in game.process_names: - if n.lower() in joined: + # Match executable basenames only — never substring-search the full + # cmdline (e.g. `syncgames end eden-saves` must not look like Eden). + for part in cmd[:2]: + if not part: + continue + if _norm(Path(part).name) in names: + return True + if appid: + joined = " ".join(cmd).lower() + if ( + f"appid={appid}" in joined + or f"steam_appid={appid}" in joined + or f"appid={appid}" in joined + ): return True except (psutil.Error, TypeError): continue diff --git a/config/agent.toml.example b/config/agent.toml.example index 29999e6..36bb3d7 100644 --- a/config/agent.toml.example +++ b/config/agent.toml.example @@ -1,6 +1,7 @@ # Copy to ~/.config/syncgames/agent.toml or SyncGames/config/agent.toml -device_id = "pc-desk" +# Unique per machine (this desktop = Jorg, laptop = terra, …) +device_id = "Jorg" store = "minio" # or "filesystem" for Path 1 fallback # WAN HTTPS via Cloudflare → NGINX → MinIO (path-style) diff --git a/config/devices.toml b/config/devices.toml index f9779d8..1eb7ae6 100644 --- a/config/devices.toml +++ b/config/devices.toml @@ -1,12 +1,13 @@ # Registry of known devices (documentation / optional tooling) +# Each machine sets its own device_id in ~/.config/syncgames/agent.toml [[devices]] -id = "pc-desk" +id = "Jorg" os = "linux" role = "desktop" [[devices]] -id = "laptop" +id = "terra" os = "linux" role = "laptop" diff --git a/config/games/dark-souls-2-sotfs.toml b/config/games/dark-souls-2-sotfs.toml index a6480e3..e51c386 100644 --- a/config/games/dark-souls-2-sotfs.toml +++ b/config/games/dark-souls-2-sotfs.toml @@ -2,6 +2,9 @@ id = "dark-souls-2-sotfs" name = "Dark Souls II Scholar of the First Sin (Seamless Coop)" platform = "steam" versions_to_keep = 5 +# Seamless Coop uses a separate extension (.co2). Non-Steam launches use a +# random compatdata id — verify on each device: +# find ~/.steam/steam/steamapps/compatdata -iname '*.co2' | grep -i DarkSoulsII paths = [ "~/.steam/steam/steamapps/compatdata/335300/pfx/drive_c/users/steamuser/Application Data/DarkSoulsII", ] diff --git a/config/games/dark-souls-3.toml b/config/games/dark-souls-3.toml index 36fc6a4..e2b8538 100644 --- a/config/games/dark-souls-3.toml +++ b/config/games/dark-souls-3.toml @@ -2,8 +2,12 @@ id = "dark-souls-3" name = "Dark Souls III (Seamless Coop)" platform = "steam" versions_to_keep = 5 +# Seamless Coop save extension = .co2. Non-Steam / me3 shortcut prefix — NOT +# vanilla 374320. Find with: +# find ~/.steam/steam/steamapps/compatdata -name 'DS30000.co2' +# Note: Roaming\DarkSoulsIII (not Application Data) under Proton for this install. paths = [ - "~/.steam/steam/steamapps/compatdata/374320/pfx/drive_c/users/steamuser/Application Data/DarkSoulsIII", + "~/.steam/steam/steamapps/compatdata/2678480877/pfx/drive_c/users/steamuser/AppData/Roaming/DarkSoulsIII/0110000103f10730", ] steam_appid = 374320 process_names = [ diff --git a/config/games/dark-souls-remastered.toml b/config/games/dark-souls-remastered.toml index 126b492..b5e8279 100644 --- a/config/games/dark-souls-remastered.toml +++ b/config/games/dark-souls-remastered.toml @@ -2,7 +2,9 @@ id = "dark-souls-remastered" name = "Dark Souls Remastered (Seamless Coop)" platform = "steam" versions_to_keep = 5 -# Adjust USERNAME / Steam library path on each device after install. +# Seamless Coop .co2 lives under a non-Steam compatdata prefix when launched via +# me3 / shortcut. Find per device: +# find ~/.steam/steam/steamapps/compatdata -iname '*.co2' | grep -i 'DARK SOULS\|NBGI' paths = [ "~/.steam/steam/steamapps/compatdata/570940/pfx/drive_c/users/steamuser/Documents/NBGI/DARK SOULS REMASTERED", ] diff --git a/config/games/elden-ring-seamless.toml b/config/games/elden-ring-seamless.toml index a1cc72a..bf9f6d3 100644 --- a/config/games/elden-ring-seamless.toml +++ b/config/games/elden-ring-seamless.toml @@ -2,11 +2,14 @@ id = "elden-ring-seamless" name = "Elden Ring (Seamless Coop)" platform = "steam" versions_to_keep = 5 -# Seamless Coop writes alongside vanilla saves under EldenRing// -# Point at the SeamlessCoop subdirectory once present, or the parent character folder. +# Seamless Coop uses .co2 (not vanilla .sl2). Non-Steam / me3 launches create a +# NEW compatdata id — NOT vanilla 1245620. Find with: +# find ~/.steam/steam/steamapps/compatdata -name 'ER0000.co2' +# Sync the SteamID folder that contains ER0000.co2 (per-device path). paths = [ - "~/.steam/steam/steamapps/compatdata/1245620/pfx/drive_c/users/steamuser/AppData/Roaming/EldenRing", + "~/.steam/steam/steamapps/compatdata/3917582188/pfx/drive_c/users/steamuser/AppData/Roaming/EldenRing/76561198026393392", ] +# Vanilla appid kept for reference; watcher mainly uses process names. steam_appid = 1245620 process_names = [ "eldenring.exe", diff --git a/docs/add-game.md b/docs/add-game.md index 0c70f22..1a60a31 100644 --- a/docs/add-game.md +++ b/docs/add-game.md @@ -20,6 +20,18 @@ Effects: Multiple `--paths` allowed. Globs are expanded at session time. +### Seamless Coop (Souls / Elden Ring) + +Modded saves use **`.co2`**, not vanilla `.sl2`. Non-Steam / me3 launchers create a **new** Proton `compatdata/` — not the vanilla Steam AppID folder. + +See **[seamless-save-paths.md](seamless-save-paths.md)** for discovery steps and known Jorg desktop paths (reference for satellites — always re-find on each device). + +```bash +find ~/.steam/steam/steamapps/compatdata -name '*.co2' +``` + +Point `paths` at the directory that contains the `.co2` (per machine — the compatdata id can differ on `terra` vs `Jorg`). SteamID subfolders usually match across devices on the same Steam account. + ### Platforms | Value | Meaning | diff --git a/docs/seamless-save-paths.md b/docs/seamless-save-paths.md new file mode 100644 index 0000000..396b22e --- /dev/null +++ b/docs/seamless-save-paths.md @@ -0,0 +1,41 @@ +# Seamless Coop save locations + +Seamless Coop does **not** use the vanilla Steam save slot as the live character file. It uses a separate extension (usually **`.co2`**) so co-op progress cannot corrupt `.sl2`. + +When the game is launched via **me3**, a **non-Steam Steam shortcut**, or a copied Proton prefix, Steam creates a **new** `compatdata//` — **not** the vanilla AppID (`1245620`, `374320`, …). + +## Discover on any device + +```bash +find ~/.steam/steam/steamapps/compatdata ~/.local/share/Steam/steamapps/compatdata \ + -type f -name '*.co2' 2>/dev/null +``` + +Point SyncGames `paths` at the **directory that contains** the `.co2` (the SteamID / account subfolder). Do this **per device** — the numeric `compatdata` id often differs between `Jorg` and `terra` even for the same mod launch method. The SteamID folder name is usually stable on one Steam account. + +## Known locations (Jorg desktop — reference only) + +| Game id | Compatible discovery | Path used on Jorg | +|---------|----------------------|-------------------| +| `elden-ring-seamless` | `ER0000.co2` | `~/.steam/steam/steamapps/compatdata/3917582188/pfx/drive_c/users/steamuser/AppData/Roaming/EldenRing/76561198026393392` | +| `dark-souls-3` | `DS30000.co2` | `~/.steam/steam/steamapps/compatdata/2678480877/pfx/drive_c/users/steamuser/AppData/Roaming/DarkSoulsIII/0110000103f10730` | +| `dark-souls-2-sotfs` | *(no `.co2` on Jorg yet)* | Still template vanilla path — replace after first Seamless launch | +| `dark-souls-remastered` | *(no `.co2` on Jorg yet)* | Still template vanilla path — replace after first Seamless launch | + +Older / alternate prefixes may hold conversion leftovers (e.g. `ER0000.cnv.co2` under `3182145933` on Jorg). Prefer the newest live `.co2`, not `.cnv.co2`, unless you intentionally migrate. + +## Satellite checklist (`terra`, …) + +1. Install AppImage; Setup → same MinIO keys; **`device_id = "terra"`** (or that machine’s id). +2. Seed/add games; run the `find … '*.co2'` command above. +3. Edit each game’s path to **this** machine’s directory (GUI Load → Save, or `syncgames edit`). +4. **Start** → pulls SSOT into the local Seamless folder → play → **End**. + +Optional automation after paths are correct: + +```bash +./systemd/install-user-units.sh +# ensure syncgames is on PATH, e.g. symlink AppImage CLI or pip install -e +systemctl --user enable --now syncgames-watch@elden-ring-seamless.service +systemctl --user enable --now syncgames-watch@dark-souls-3.service +```