SVSimLoader
BepInEx 5 / HarmonyX plugin injected into the Steam Shadowverse client. Two jobs:
- Redirect the client to a local emulated server (or a capture proxy).
- Observe the live client/server conversation and dump structured artifacts that feed
SVSim.Bootstrapand 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
-
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.
-
From this repo, build the plugin:
cd ClientLoader/SVSimLoader dotnet build -
Copy
SVSimLoader/bin/Debug/net46/SVSimLoader.dll→<game-dir>/BepInEx/plugins/. -
Launch the game once to generate
<game-dir>/BepInEx/config/SVSimLoader.cfg, then close. -
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 = falseis correct against the local server and against any other compliant emulator. LeaveDisableEncryptionalone 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. -
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.