diff --git a/README.md b/README.md index 5c0934f..fa21ae9 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,8 @@ Built against the official Cygames build (decompiled source in `Shadowverse_Code 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/` - - `[Connection] DisableEncryption = true` + + 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/`. @@ -37,7 +38,7 @@ Settings live in `BepInEx/config/SVSimLoader.cfg`, generated on first launch. Th | 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. | +| `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) @@ -76,6 +77,8 @@ BepInEx/svsim-captures/_/ 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/` is a curated paste of one such session, used as the seed source for `SVSim.Bootstrap/Data/prod-captures/`. ## Code layout diff --git a/SVSimLoader/CaptureWriter.cs b/SVSimLoader/CaptureWriter.cs index 900b437..8ebf2a7 100644 --- a/SVSimLoader/CaptureWriter.cs +++ b/SVSimLoader/CaptureWriter.cs @@ -85,25 +85,25 @@ internal static class CaptureWriter public static void AppendTraffic(string direction, string url, bool encrypted, string body) { - AppendNdjson(_trafficPath, new Dictionary + string envelope = JsonMapper.ToJson(new Dictionary { { "ts", DateTime.UtcNow.ToString("o") }, { "direction", direction }, { "url", url }, { "encrypted", encrypted }, - { "body", ParseBodyOrKeep(body) }, }); + AppendLineWithBody(_trafficPath, envelope, body); } public static void AppendBattleTraffic(string direction, string uri, string body) { - AppendNdjson(_battleTrafficPath, new Dictionary + string envelope = JsonMapper.ToJson(new Dictionary { { "ts", DateTime.UtcNow.ToString("o") }, { "direction", direction }, { "uri", uri }, - { "body", ParseBodyOrKeep(body) }, }); + AppendLineWithBody(_battleTrafficPath, envelope, body); } /// @@ -278,20 +278,28 @@ internal static class CaptureWriter if (classes.Count > 0) dump["classes"] = classes; } - // Parse body JSON so it serializes nested in the NDJSON line rather than as an - // escaped string. Falls back to the original string on parse failure — no current - // caller produces non-JSON, but a future caller passing raw text shouldn't crash - // the line write. - private static object ParseBodyOrKeep(string body) + // Splice the body into the envelope as nested JSON (parseable) or escaped string + // (fallback). Cannot route this through Dictionary → JsonMapper.ToJson: + // a LitJson.JsonData value inside such a dict makes the reflection serializer + // mis-bracket and throw "Can't close an object here". + private static void AppendLineWithBody(string path, string envelopeJson, string body) { - if (string.IsNullOrEmpty(body)) return body; - try { return JsonMapper.ToObject(body); } - catch { return body; } - } - - private static void AppendNdjson(string path, Dictionary entry) - { - string line = JsonMapper.ToJson(entry); + string bodyJson; + if (body == null) + { + bodyJson = "null"; + } + else if (body.Length == 0) + { + bodyJson = "\"\""; + } + else + { + try { bodyJson = JsonMapper.ToObject(body).ToJson(); } + catch { bodyJson = JsonMapper.ToJson(body); } + } + string trimmed = envelopeJson.Substring(0, envelopeJson.Length - 1); + string line = trimmed + ",\"body\":" + bodyJson + "}"; lock (_lock) { File.AppendAllText(path, line + "\n"); diff --git a/SVSimLoader/IdentityWipe.cs b/SVSimLoader/IdentityWipe.cs new file mode 100644 index 0000000..82a3752 --- /dev/null +++ b/SVSimLoader/IdentityWipe.cs @@ -0,0 +1,14 @@ +extern alias game; + +using PlayerPrefs = game::UnityEngine.PlayerPrefs; + +namespace SVSimLoader; + +public static class IdentityWipe +{ + public static void Execute() + { + PlayerPrefs.DeleteAll(); + PlayerPrefs.Save(); + } +} diff --git a/SVSimLoader/Plugin.cs b/SVSimLoader/Plugin.cs index 7b137b2..6d33e38 100644 --- a/SVSimLoader/Plugin.cs +++ b/SVSimLoader/Plugin.cs @@ -60,8 +60,16 @@ namespace SVSimLoader SvSimConfig.StorySectionIdFilter = Config.Bind("Sweeps", "StorySectionIdFilter", "", "Optional comma-separated list of section IDs to restrict the story sweep to (e.g. '14,19,20'). Empty = sweep all sections. Useful for resuming a previous run that hit MAX_PASSES_PER_PAIR on specific sections without re-sweeping everything.").Value; + SvSimConfig.NukeIdentityOnStartup = + Config.Bind("Identity", "NukeIdentityOnStartup", false, + "On plugin Awake (before the game reads PlayerPrefs), wipe all PlayerPrefs via PlayerPrefs.DeleteAll(). Clears the obscured UDID/VIEWER_ID/SHORT_UDID keys that Cute.Certification reads on login, so the next launch behaves like a brand-new install and re-runs SignUpTask. Use this when switching Steam accounts gives a linking error. SIDE EFFECT: also resets language/sound/RES_VER prefs — they're rebuilt from defaults next boot. Recovery files and capture sessions are NOT touched.").Value; SvSimConfig.ApplicationUrl = _applicationUrl.Value; SvSimConfig.DisableEncryption = _disableEncryption.Value; + if (SvSimConfig.NukeIdentityOnStartup) + { + Logger.LogWarning("NukeIdentityOnStartup is enabled — wiping all PlayerPrefs (identity + settings)."); + IdentityWipe.Execute(); + } CaptureWriter.Initialize(); Logger.LogInfo($"Capture session directory: {CaptureWriter.SessionDirectory}"); Logger.LogInfo($"Connecting to application server at {_applicationUrl.Value}"); diff --git a/SVSimLoader/SvSimConfig.cs b/SVSimLoader/SvSimConfig.cs index 9da9074..10ce8c4 100644 --- a/SVSimLoader/SvSimConfig.cs +++ b/SVSimLoader/SvSimConfig.cs @@ -16,4 +16,5 @@ public static class SvSimConfig public static bool ProbeLimitedSection { get; set; } public static bool ProbeEventSection { get; set; } public static string StorySectionIdFilter { get; set; } + public static bool NukeIdentityOnStartup { get; set; } }