#!/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()
