#!/usr/bin/env python3 """Fully decode one or more EntityBlueprints (by base name) from epb_assets_all.bundle. Prints every Odin component ($type) and every scalar field, so we can see firsthand whether a weapon blueprint carries any damage magnitude. Usage: python bundle/dump_blueprint.py item_revolverSmall_dusters [other_base ...] """ import os, sys, json, UnityPy from UnityPy.helpers.TypeTreeGenerator import TypeTreeGenerator sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import odin_read GAME = "/mnt/d/SteamLibrary/steamapps/common/Sand Playtest" META = os.path.join(GAME, "Sand_Data/il2cpp_data/Metadata/global-metadata.dat") DLL = os.path.join(GAME, "GameAssembly.dll") BUNDLES = "/home/downloadpizza/sand_tools/bundles" UNITY = "6000.0.40f1" def entity_blueprint(go): for c in go.m_Components: co = c.read(); r = co.object_reader if r.type.name == "MonoBehaviour" and co.m_Script.read().m_ClassName == "EntityBlueprint": return r return None def walk(node, prefix=""): """Yield (path, type_label, scalar) for every node; flags floats.""" if isinstance(node, dict): t = node.get("$type") if t: yield (prefix, t, None) for k, v in node.items(): if k == "$type": continue yield from walk(v, prefix + "." + str(k)) elif isinstance(node, list): for i, v in enumerate(node): yield from walk(v, prefix + f"[{i}]") else: yield (prefix, None, node) def main(): targets = sys.argv[1:] or ["item_revolverSmall_dusters"] gen = TypeTreeGenerator(UNITY) gen.load_il2cpp(open(DLL, "rb").read(), open(META, "rb").read()) env = UnityPy.load(os.path.join(BUNDLES, "epb_assets_all.bundle"), os.path.join(BUNDLES, "sand_monoscripts.bundle")) want = {t: None for t in targets} for path, obj in env.container.items(): base = path.split("/")[-1].replace("_epb.prefab", "") if base in want and obj.type.name == "GameObject": want[base] = obj for base, obj in want.items(): print("\n" + "=" * 70) print("BLUEPRINT:", base, "" if obj else " *** NOT FOUND ***") print("=" * 70) if not obj: continue eb = entity_blueprint(obj.read()) if eb is None: print(" no EntityBlueprint component") continue script = eb.read().m_Script.read() full = (script.m_Namespace + "." if script.m_Namespace else "") + script.m_ClassName nodes = json.loads(gen.get_nodes_as_json(script.m_AssemblyName, full)) sb = eb.read_typetree(nodes).get("serializationData", {}).get("SerializedBytes") if not sb: print(" no SerializedBytes") continue parsed = odin_read.parse(bytes(sb)) # list components comps = set() floats = [] for root in ("roots", "items"): for p, t, sc in walk(parsed.get(root)): if t: comps.add(t) if isinstance(sc, float) and sc not in (0.0,) : floats.append((p, sc)) print(" components (%d):" % len(comps)) for c in sorted(comps): print(" -", c) print(" non-zero float fields (%d):" % len(floats)) for p, v in floats: print(" %-60s = %s" % (p, v)) if __name__ == "__main__": main()