Organize the 16 loose scripts by concern:
walker/ -- .wbt save tooling (sand, build_wbt, walker_hashes,
harvest_hashes, recover_key)
wikigen/ -- MediaWiki page generators (make_*_wiki, render_wiki)
bundle/ -- Unity/Odin asset extraction (unitybundle, odin_read,
extract_*, loot_probe, dump_loot_bytes)
The only cross-script imports (build_wbt->walker_hashes,
extract_loot->odin_read) live within the same folder, so each
script's dir on sys.path[0] keeps them resolving with no code
changes. All data paths are absolute, so the moves don't affect
I/O. Named the code dir wikigen/ to avoid colliding with the
generated wiki/ output dir; ignore the regenerable wiki_site/ render.
48 lines
1.9 KiB
Python
48 lines
1.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Dump the raw Odin SerializedBytes for the two LootTablesConfig assets, and do a
|
|
first-pass analysis: hexdump head, and list ASCII strings (>=3 chars) found."""
|
|
import os, sys, json, re, UnityPy
|
|
from UnityPy.helpers.TypeTreeGenerator import TypeTreeGenerator
|
|
|
|
GAME = "/mnt/d/SteamLibrary/steamapps/common/Sand Playtest"
|
|
BD = os.path.join(GAME, "Sand_Data/StreamingAssets/aa/StandaloneWindows64")
|
|
META = os.path.join(GAME, "Sand_Data/il2cpp_data/Metadata/global-metadata.dat")
|
|
DLL = os.path.join(GAME, "GameAssembly.dll")
|
|
OUT = "/home/downloadpizza/sand_tools/extracted"
|
|
|
|
gen = TypeTreeGenerator("6000.0.40f1")
|
|
gen.load_il2cpp(open(DLL, "rb").read(), open(META, "rb").read())
|
|
env = UnityPy.load(os.path.join(BD, "configuration_assets_all.bundle"),
|
|
os.path.join(BD, "sand_monoscripts.bundle"))
|
|
|
|
for o in env.objects:
|
|
if o.type.name != "MonoBehaviour":
|
|
continue
|
|
try:
|
|
d = o.read(); nm = getattr(d, "m_Name", "") or ""
|
|
except Exception:
|
|
continue
|
|
if "LootTables" not in nm:
|
|
continue
|
|
script = d.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))
|
|
tree = o.read_typetree(nodes)
|
|
sb = tree["serializationData"]["SerializedBytes"]
|
|
data = bytes(sb)
|
|
out_bin = os.path.join(OUT, f"{nm}.odin.bin")
|
|
open(out_bin, "wb").write(data)
|
|
print(f"\n=== {nm}: {len(data)} bytes -> {out_bin}")
|
|
print("head hex:", data[:64].hex())
|
|
# ascii strings
|
|
strings = re.findall(rb"[\x20-\x7e]{3,}", data)
|
|
uniq = []
|
|
seen = set()
|
|
for s in strings:
|
|
t = s.decode()
|
|
if t not in seen:
|
|
seen.add(t); uniq.append(t)
|
|
print(f"{len(strings)} ascii runs, {len(uniq)} unique. First 60 unique:")
|
|
for t in uniq[:60]:
|
|
print(" ", repr(t))
|