# SVSimLoader BepInEx 5 / HarmonyX plugin injected into the Steam Shadowverse client. Two jobs: 1. **Redirect** the client to a local emulated server (or a capture proxy). 2. **Observe** the live client/server conversation and dump structured artifacts that feed `SVSim.Bootstrap` and the api-spec docs. Built against the official Cygames build (decompiled source in `Shadowverse_Code_2026-05-23/`). Lives upstream of the rest of the SVSim project — everything else in the repo consumes what this plugin captures. ## Quick start 1. Install **BepInEx 5** (Mono build, matching the game's bitness) into the Shadowverse game directory. Launch the game once so BepInEx generates its folder tree, then close. 2. From this repo, build the plugin: ```sh cd ClientLoader/SVSimLoader dotnet build ``` 3. Copy `SVSimLoader/bin/Debug/net46/SVSimLoader.dll` → `/BepInEx/plugins/`. 4. Launch the game once to generate `/BepInEx/config/SVSimLoader.cfg`, then close. 5. Edit the config. For local server work: - `[Connection] ApplicationUrl = http://localhost:5148/` That's it — DCGEngine speaks the same AES-encrypted wire format as the prod server, so the default `DisableEncryption = false` is correct against the local server and against any other compliant emulator. Leave `DisableEncryption` alone unless you're debugging the wire layer itself (see below). For prod capture, leave `[Connection]` at defaults and toggle whichever `[Capture]` / `[Sweeps]` flags you need. 6. Launch the game. A capture session directory appears under `/BepInEx/svsim-captures/`. ## Build notes Targets `net46` (Unity 5.6 / Mono). The csproj references `Assembly-CSharp.dll` and `UnityEngine.CoreModule.dll` out of `SVSimLoader/lib/` — populate from a working game install if those aren't already present. No `dotnet restore` quirks; a plain `dotnet build` is enough. ## Configuration Settings live in `BepInEx/config/SVSimLoader.cfg`, generated on first launch. Three sections: ### `[Connection]` — where and how to talk to the server | Key | Default | Purpose | |---|---|---| | `ApplicationUrl` | prod URL | Overrides `CustomPreference.GetApplicationServerURL`. Point at `http://localhost:5148/` for the local DCGEngine. | | `DisableEncryption` | `false` | Forces the `encrypt` arg on `NetworkManager.Connect` to false, so request/response bodies go over the wire as plain base64(msgpack(...)) instead of base64(AES(msgpack(...))). **You do NOT need to enable this for any server — DCGEngine and any other compliant emulator handle the standard AES path the same way prod does.** It exists for wire-format debugging: makes `traffic.ndjson` and proxy-side inspection readable without round-tripping through `CryptAES`. Leave it at `false` for normal use. | ### `[Capture]` — passive observe-and-record (safe to leave on) | Key | Default | Output file | |---|---|---| | `EnableTrafficCapture` | `true` | `traffic.ndjson` — every HTTP request + response body | | `EnableBattleCapture` | `true` | `battle-traffic.ndjson` — every Socket.IO battle frame | | `DumpCardDB` | `false` | `cards.json` — full card master, one-shot on load | | `DumpUserData` | `false` | `user-data.json` — viewer fields from `/load/index`, shaped for `/admin/import_viewer` | ### `[Sweeps]` — active prod-API traffic (deliberate opt-in, hits prod) Probes (one extra request) and sweeps (many) fire automatically once the gating screen loads. All responses land in `traffic.ndjson` via the normal capture hook. | Key | Default | Effect | |---|---|---| | `ProbeLimitedSection` | `false` | Fires `/limited_story/section` once on first `/mypage/refresh` | | `ProbeEventSection` | `false` | Same for `/event_story/section`, 1s after the limited probe | | `SweepLeaderSkinPools` | `false` | Walks every parent gacha id from `/pack/info`; ~18s for 35 packs | | `SweepMainStory` | `false` | Walks every (section, chara, chapter); captures master `special_battle_setting` payloads. **Side effect**: unfinished chapters become `is_skipped=true` (blue "Cleared", no rewards). Use a throwaway account. | | `SweepLimitedStory` | `false` | Same shape for limited-story sections | | `SweepEventStory` | `false` | Same shape for event-story sections | | `StorySweepPacingSeconds` | `5.0` | Seconds between requests (min 1s). 5s = ~6h for full main-story tree. | | `StorySectionIdFilter` | `""` | Comma-separated section IDs to scope down. Empty = sweep all. Used to resume runs that hit `MAX_PASSES_PER_PAIR`. | ## Capture output Each game launch creates a fresh session directory: ``` BepInEx/svsim-captures/_/ traffic.ndjson # HTTP req/resp (always, when EnableTrafficCapture) battle-traffic.ndjson # Socket.IO frames cards.json # one-shot card master dump user-data.json # /admin/import_viewer-shaped viewer extract special-battle-settings.ndjson # deduped sbs payloads from story sweeps ``` The capture hook decrypts each response before writing, so `traffic.ndjson` is always readable JSON regardless of whether the underlying connection used AES. `DisableEncryption` is therefore not required to make captures inspectable; it only affects what flows over the wire itself. The `traffic_prod.ndjson` checked into `data_dumps/captures/` is a curated paste of one such session, used as the seed source for `SVSim.Bootstrap/Data/prod-captures/`. ## Code layout ``` SVSimLoader/ Plugin.cs # BepInEx entry point, Config.Bind, Harmony.PatchAll SvSimConfig.cs # plain static fields populated from Config.Bind CaptureWriter.cs # session dir, ndjson writers, user-data extraction Patches/ UrlPatches.cs # GetApplicationServerURL -> SvSimConfig.ApplicationUrl DecryptPatch.cs # NetworkManager.Connect: optionally clear `encrypt` flag ExaminationPatches.cs# NetworkTask.SetResponseData: traffic-capture fan-out hub LeaderSkinPoolSweep.cs # /pack/info -> /pack/get_gacha_point_rewards sweep StorySectionProbe.cs # /mypage/refresh -> /{limited,event}_story/section probes StorySweep.cs # /*/section -> /{main,limited,event}_story/{start,finish} sweep DummyLogging.cs # short-circuits LocalLog.MakeTreceLogToSend (no telemetry) ExceptionLogging.cs # Unity exception/error -> BepInEx log, with last-response JSON ``` `ExaminationPatches.SetResponseData` is the central hub: every response goes through it, and it dispatches into the dump / probe / sweep modules based on URL + config. Adding a new passive extractor usually means adding one `if (...EndsWith("/some/url")) { ... }` block there plus a writer in `CaptureWriter`. ## Background Live Cygames servers shut down end of June 2026 — this plugin's main reason for existing is to capture as much server-only data (special battle settings, gacha pool composition, reward tables) as possible before then. Spec fidelity downstream depends on the artifacts it dumps. See `docs/audits/` for the per-endpoint audit reports that drive what gets captured next.