# 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` → `<game-dir>/BepInEx/plugins/`.
4. Launch the game once to generate `<game-dir>/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 `<game-dir>/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/<yyyy-MM-dd_HH-mm-ss>_<host>/
  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.
