Compare commits

..

3 Commits

Author SHA1 Message Date
gamer147
8f2ddeab96 Additional functionality 2026-05-28 11:21:37 -04:00
gamer147
b1206c874d loader: patch GetDeckBuilderServerURL to point at the app server
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>
2026-05-28 09:11:33 -04:00
gamer147
76b7e59489 loader: emit traffic body as nested JSON, not escaped string
Body field in traffic.ndjson / battle-traffic.ndjson is now a real nested
JSON object instead of an escaped JSON string. Parse-on-write is wrapped in
ParseBodyOrKeep with a string-fallback so a future non-JSON caller doesn't
crash the line write.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 17:42:55 -04:00
6 changed files with 69 additions and 8 deletions

View File

@@ -19,7 +19,8 @@ Built against the official Cygames build (decompiled source in `Shadowverse_Code
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/`
- `[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 `<game-dir>/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/<yyyy-MM-dd_HH-mm-ss>_<host>/
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

View File

@@ -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, object>
string envelope = JsonMapper.ToJson(new Dictionary<string, object>
{
{ "ts", DateTime.UtcNow.ToString("o") },
{ "direction", direction },
{ "url", url },
{ "encrypted", encrypted },
{ "body", body },
});
AppendLineWithBody(_trafficPath, envelope, body);
}
public static void AppendBattleTraffic(string direction, string uri, string body)
{
AppendNdjson(_battleTrafficPath, new Dictionary<string, object>
string envelope = JsonMapper.ToJson(new Dictionary<string, object>
{
{ "ts", DateTime.UtcNow.ToString("o") },
{ "direction", direction },
{ "uri", uri },
{ "body", body },
});
AppendLineWithBody(_battleTrafficPath, envelope, body);
}
/// <summary>
@@ -278,9 +278,28 @@ internal static class CaptureWriter
if (classes.Count > 0) dump["classes"] = classes;
}
private static void AppendNdjson(string path, Dictionary<string, object> entry)
// Splice the body into the envelope as nested JSON (parseable) or escaped string
// (fallback). Cannot route this through Dictionary<string,object> → 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)
{
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");

View File

@@ -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();
}
}

View File

@@ -14,4 +14,20 @@ public static class UrlPatches
__result = SvSimConfig.ApplicationUrl;
return false;
}
/// <summary>
/// Redirects the deck-builder server (shadowverse-portal.com in prod) to our app server so
/// the deck-code mint/resolve pair lands on the emulator's <c>DeckBuilderController</c>.
/// Both client tasks (<c>GenerateDeckCodeTask</c>, <c>GetDeckDataFromCodeTask</c>) build
/// their URL as <c>GetDeckBuilderServerURL() + ApiList[type]</c> where the right-hand side
/// is the bare <c>deck_code?format=msgpack</c> / <c>deck?format=msgpack</c> path — matches
/// our controller routes once this prefix returns the local server's base URL.
/// </summary>
[HarmonyPatch(typeof(CustomPreference), nameof(CustomPreference.GetDeckBuilderServerURL))]
[HarmonyPrefix]
public static bool GetDeckBuilderServerURL(ref string __result)
{
__result = SvSimConfig.ApplicationUrl;
return false;
}
}

View File

@@ -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}");

View File

@@ -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; }
}