master-server replay + trampler RE: protocol, hashes, footprints, map renderer
- master_scrape.py: live master-server (ger.hologryph.com) ClientMessage replay over the two-socket /login + /connect handshake (PlayFab ticket auth). Pulled compartment defs, shop prices, research tree, storage, characters, expedition -> extracted/master_*.json - PlayFab confirmed auth-only for this title (Economy disabled); docs corrected - trampler_hashes.py: blueprint hash algo MD5(UTF8(compact-JSON)); CompartmentsHash(#1) and ConnectionsHash(#3) verified & generatable from scratch - walkerdto_to_blueprint.py: WalkerDto(expedition) -> WalkerBlueprintDto, enum int<->name, verified by storage->WS->storage round-trip - render_trampler.py: per-floor map from CompartmentsDatabase cell footprints (rotation solved via overlap check) + doors/hatches from Connections + turret arcs + cargo C1-C8 in game order - docs/MASTER_SERVER.md, docs/TRAMPLER.md; ghidra address-offset bug fixed (no -0x1000)
This commit is contained in:
76
reverse/trampler_hashes.py
Normal file
76
reverse/trampler_hashes.py
Normal file
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate the SAND trampler/walker blueprint hashes FROM SCRATCH (no harvesting needed).
|
||||
|
||||
RE'd from GameAssembly.dll (2026-06-15 build):
|
||||
MD5Utility.ComputeHash(obj) = MD5( UTF8( JsonConvert.SerializeObject(obj) ) ), formatted as
|
||||
uppercase hex with no separators (each byte -> ToString("X2")). [ghidra: MD5Utility$$ObjectToByteArray,
|
||||
$$ComputeHash]
|
||||
|
||||
The three top-level WalkerBlueprintDto hashes are MD5 of the *compact* JSON (Newtonsoft default:
|
||||
no whitespace, PascalCase keys in C# declaration order, nulls included, enums as STRING names,
|
||||
CellCoordinate as {x,y,z}) of:
|
||||
CompartmentsHash = ComputeHash(blueprint.Compartments) [List<CompartmentBlueprintDto>] VERIFIED
|
||||
ConnectionsHash = ComputeHash(blueprint.Connections) [List<ConnectionBlueprintDto>] VERIFIED <- "hash #3"
|
||||
DefinitionsHash = ComputeHash(<the CompartmentDefinitionDto list for the compartments>) PROVISIONAL
|
||||
(hashes the actual definitions, not blueprint-internal fields; verify live
|
||||
against a fresh GetTrampler + GetCompartmentDefinitions once unfrozen.)
|
||||
|
||||
To match Newtonsoft byte-for-byte, build the dicts with keys already in declaration order and
|
||||
serialize with json.dumps(obj, separators=(",",":"), ensure_ascii=False). Do NOT sort keys.
|
||||
"""
|
||||
import json, hashlib
|
||||
|
||||
|
||||
def md5_hash(obj) -> str:
|
||||
"""MD5Utility.ComputeHash(obj): MD5 of compact-JSON(obj), uppercase hex."""
|
||||
blob = json.dumps(obj, separators=(",", ":"), ensure_ascii=False)
|
||||
return hashlib.md5(blob.encode("utf-8")).hexdigest().upper()
|
||||
|
||||
|
||||
def compartments_hash(compartments: list) -> str:
|
||||
"""#1 CompartmentsHash = MD5(JSON(Compartments list)). VERIFIED."""
|
||||
return md5_hash(compartments)
|
||||
|
||||
|
||||
def connections_hash(connections: list) -> str:
|
||||
"""#3 ConnectionsHash = MD5(JSON(Connections list)). VERIFIED.
|
||||
|
||||
Each connection must serialize as (declaration order):
|
||||
{"Id": int, "EpbId": str|None, "ActualDirection": {"x":,"y":,"z":},
|
||||
"GridCoordinate": {"x":,"y":,"z":}, "SlotType": "<name>", "State": "<name>"}
|
||||
"""
|
||||
return md5_hash(connections)
|
||||
|
||||
|
||||
def definitions_hash(definitions: list) -> str:
|
||||
"""#2 DefinitionsHash = MD5(JSON(<CompartmentDefinitionDto list>)). PROVISIONAL — verify live."""
|
||||
return md5_hash(definitions)
|
||||
|
||||
|
||||
def apply_hashes(blueprint: dict, definitions: list = None) -> dict:
|
||||
"""Set the three top-level hashes on a blueprint dict in place. Returns it.
|
||||
definitions: ordered CompartmentDefinitionDto list for #2 (optional until verified)."""
|
||||
blueprint["CompartmentsHash"] = compartments_hash(blueprint["Compartments"])
|
||||
blueprint["ConnectionsHash"] = connections_hash(blueprint["Connections"])
|
||||
if definitions is not None:
|
||||
blueprint["DefinitionsHash"] = definitions_hash(definitions)
|
||||
return blueprint
|
||||
|
||||
|
||||
def _selftest():
|
||||
import os
|
||||
p = os.path.join(os.path.dirname(__file__), "..", "extracted",
|
||||
"playfab_titledata_TramplerBlueprint3.json")
|
||||
b = json.load(open(p))
|
||||
c1 = compartments_hash(b["Compartments"])
|
||||
c3 = connections_hash(b["Connections"])
|
||||
assert c1 == b["CompartmentsHash"], "CompartmentsHash %s != %s" % (c1, b["CompartmentsHash"])
|
||||
assert c3 == b["ConnectionsHash"], "ConnectionsHash %s != %s" % (c3, b["ConnectionsHash"])
|
||||
print("selftest OK:")
|
||||
print(" CompartmentsHash #1:", c1, "== stored")
|
||||
print(" ConnectionsHash #3:", c3, "== stored <- generatable from scratch")
|
||||
print(" DefinitionsHash #2: provisional (verify live vs current GetTrampler).")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
_selftest()
|
||||
Reference in New Issue
Block a user