#!/usr/bin/env python3 """Map each island prefab to its in-game display name (Toponym). The internal prefab name (e.g. island_Demo_Wunderinsel) often differs from the name shown in-game. The authoritative link is a child `Landmark` GameObject carrying a `LandmarkBehaviour` (Hologryph.Sand.Shared.World.DesignedEnvironment.Configuration) whose `name` field IS the Toponym key. The English display string is then i2 `Toponyms/`. Example: island_Demo_Wunderinsel -> Landmark.name "Strudel" -> Toponyms/Strudel. Writes extracted/island_names.json. """ import os, sys, json, UnityPy from UnityPy.helpers.TypeTreeGenerator import TypeTreeGenerator 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" I2 = "/home/downloadpizza/sand_tools/i2_terms_en.json" OUT = "/home/downloadpizza/sand_tools/extracted/island_names.json" LM_FULL = "Hologryph.Sand.Shared.World.DesignedEnvironment.Configuration.LandmarkBehaviour" def main(): gen = TypeTreeGenerator("6000.0.40f1") gen.load_il2cpp(open(DLL, "rb").read(), open(META, "rb").read()) env = UnityPy.load(os.path.join(BUNDLES, "islands_assets_all.bundle"), os.path.join(BUNDLES, "sand_monoscripts.bundle")) nodes = [None] def landmark_name(go, seen): for c in go.m_Components: try: co = c.read() except Exception: continue r = co.object_reader if r.type.name == "MonoBehaviour": try: sc = co.m_Script.read() if sc.m_ClassName == "LandmarkBehaviour": if nodes[0] is None: nodes[0] = json.loads(gen.get_nodes_as_json(sc.m_AssemblyName, LM_FULL)) nm = r.read_typetree(nodes[0]).get("name") if nm: return nm except Exception: pass elif r.type.name == "Transform": for ch in co.m_Children: try: g = ch.read().m_GameObject.read() pid = g.object_reader.path_id if pid not in seen: seen.add(pid) res = landmark_name(g, seen) if res: return res except Exception: pass return None i2 = json.load(open(I2)) if os.path.exists(I2) else {} rows = {} for path, obj in env.container.items(): n = path.split("/")[-1].replace(".prefab", "") if obj.type.name == "GameObject" and n.startswith("island_"): lm = landmark_name(obj.read(), set()) rows[n] = {"toponym": lm, "display_en": i2.get("Toponyms/%s" % lm) if lm else None} out = { "_source": "islands_assets_all.bundle island_* prefab -> child Landmark " "(LandmarkBehaviour.name) -> i2 Toponyms/", "islands": dict(sorted(rows.items())), } json.dump(out, open(OUT, "w"), indent=1, ensure_ascii=False) print("wrote %s\n" % OUT) for n, d in out["islands"].items(): print(" %-34s -> %s" % (n, d["toponym"])) if __name__ == "__main__": main()