Redirects shadowverse-portal.com deck-builder calls to SvSimConfig.ApplicationUrl so the GenerateDeckCodeTask / GetDeckDataFromCodeTask URLs land on the emulator's DeckBuilderController routes (/deck_code, /deck). Both client call sites already pass encrypt:false to NetworkManager.Connect, so no additional encryption-side patch is needed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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/[Connection] DisableEncryption = true
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. Local server understands plaintext; prod does not. |
[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 traffic_prod.ndjson checked into data_dumps/ 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.