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:
DownloadPizza
2026-06-16 00:35:17 +02:00
parent 3df0797acc
commit fc6b270fa8
29 changed files with 61574 additions and 0 deletions

View 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()