Additional functionality
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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", ParseBodyOrKeep(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", ParseBodyOrKeep(body) },
|
||||
});
|
||||
AppendLineWithBody(_battleTrafficPath, envelope, body);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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<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)
|
||||
{
|
||||
if (string.IsNullOrEmpty(body)) return body;
|
||||
try { return JsonMapper.ToObject(body); }
|
||||
catch { return body; }
|
||||
}
|
||||
|
||||
private static void AppendNdjson(string path, Dictionary<string, object> 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");
|
||||
|
||||
14
SVSimLoader/IdentityWipe.cs
Normal file
14
SVSimLoader/IdentityWipe.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user