#!/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] VERIFIED ConnectionsHash = ComputeHash(blueprint.Connections) [List] VERIFIED <- "hash #3" DefinitionsHash = ComputeHash() 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": "", "State": ""} """ return md5_hash(connections) def definitions_hash(definitions: list) -> str: """#2 DefinitionsHash = MD5(JSON()). 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()