fix(signup): prestore client SID→UDID mapping so game_start can decrypt

After /tool/signup the client switches to SID-only headers (no UDID), so
the next request's body can't be decrypted unless the server already
knows the SID's UDID. ShadowverseSessionService now mirrors the client's
Cute/Cryptographer.MakeMd5(viewerId + udid) formula (salt
"r!I@ws8e5i="), and ToolController.Signup prestores the mapping at the
end. Verified against a live signup capture: viewerId=1 +
udid=62747917-93bc-454c-abb4-ef423b3c9317 produces the captured SID
dc4aac79d35fe15dfb6262e0071bb03c.

Note: this only fixes the fresh-signup path. Clients restarting with a
cached viewer_id (which skip /tool/signup entirely) still hit the same
issue — separate follow-up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-05-28 13:34:05 -04:00
parent 190b50cbaf
commit 6e6c8ee779
3 changed files with 88 additions and 1 deletions

View File

@@ -1,9 +1,19 @@
using System.Collections.Concurrent;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
namespace SVSim.EmulatedEntrypoint.Services;
public class ShadowverseSessionService
{
/// <summary>
/// Salt the client's <c>Cute/Cryptographer.MakeMd5</c> appends to every input before hashing.
/// Must match the decompiled client exactly — the server computes SIDs that the client
/// also computes locally for its outgoing request headers, and any mismatch breaks decrypt.
/// </summary>
private const string MakeMd5Salt = "r!I@ws8e5i=";
private readonly ConcurrentDictionary<string, Guid> _sessionIdToUdid;
public ShadowverseSessionService()
@@ -25,4 +35,30 @@ public class ShadowverseSessionService
{
_sessionIdToUdid.AddOrUpdate(sid, _ => udid, (_, _) => udid);
}
/// <summary>
/// Replicates the client's <c>Cute/Certification.SessionId</c> getter:
/// <c>MakeMd5(viewerId.ToString() + udid.ToString("D"))</c>. Returned as lowercase hex.
/// The client computes this once after signup and sends it as the SID header on every
/// subsequent request — the server must produce the same value to map back to the UDID.
/// </summary>
public string ComputeClientSessionId(long viewerId, Guid udid)
{
string input = viewerId.ToString(CultureInfo.InvariantCulture)
+ udid.ToString("D")
+ MakeMd5Salt;
byte[] hash = MD5.HashData(Encoding.UTF8.GetBytes(input));
return Convert.ToHexString(hash).ToLowerInvariant();
}
/// <summary>
/// Pre-stores the SID→UDID mapping the client will use for its first SID-only request
/// after <c>/tool/signup</c>. Without this, the translation middleware can't decrypt the
/// next request body (no UDID header, no mapping, falls back to <c>Guid.Empty</c>).
/// </summary>
public void StoreSessionForViewer(long viewerId, Guid udid)
{
string sid = ComputeClientSessionId(viewerId, udid);
StoreUdidForSessionId(sid, udid);
}
}