From 64c6df119fa025480922bf2d1aa7cc1bddaa5d09 Mon Sep 17 00:00:00 2001 From: DownloadPizza Date: Thu, 11 Jun 2026 20:03:12 +0200 Subject: [PATCH] islands: extract in-game names (Landmark -> Toponym), incl. Wunderinsel = Strudel Definitive island prefab -> in-game name link: each island_* prefab has a child Landmark GameObject (LandmarkBehaviour.name) = the Toponym key, localized via i2 Toponyms/. New bundle/extract_island_names.py -> extracted/island_names.json. Key results: Demo_Wunderinsel=Strudel, Demo_Marktinsel=Segen, Gartenfreude=Insel St. Clemens, DeusExMashineSmall=Rauchwolke, and island_Factorio = Sprengstofffabrik (the explosives factory). The LittleFactory/LittleFactoryArmory prefabs are the Forts. Updates docs/PRODUCTION_LINES.md with in-game names and corrects the earlier Sprengstofffabrik note (it IS the explosives factory -> Factorio, not the Armory islands). Supersedes the name-guesses (e.g. DeusExMashine is Rauchwolke, not Maschineninsel). --- bundle/extract_island_names.py | 85 ++++++++++++++++++++ docs/PRODUCTION_LINES.md | 29 ++++--- extracted/island_names.json | 137 +++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+), 12 deletions(-) create mode 100644 bundle/extract_island_names.py create mode 100644 extracted/island_names.json diff --git a/bundle/extract_island_names.py b/bundle/extract_island_names.py new file mode 100644 index 0000000..aece456 --- /dev/null +++ b/bundle/extract_island_names.py @@ -0,0 +1,85 @@ +#!/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() diff --git a/docs/PRODUCTION_LINES.md b/docs/PRODUCTION_LINES.md index 3e044f1..1c41059 100644 --- a/docs/PRODUCTION_LINES.md +++ b/docs/PRODUCTION_LINES.md @@ -68,20 +68,25 @@ venv/bin/python bundle/extract_conveyor_placements.py # -> extracted/conveyor_ This walks every island prefab, collects the `game_conveyor_` children (with local position) and cross-references each against `production_lines.json`. Result for this build: -| Island | Recipe conveyors placed | -|---|---| -| `island_Factorio` | energyRods, 80mmT3Cannon, contactGrenades, armorPiercingRocket* | -| `island_Kaiserplatz` | energyRods, 40mmT3Cannon | -| `island_Demo_Wunderinsel` | 70mmT3Cannon | -| `island_DeusExMashineSmall` | computingModules, coralDust | -| `island_testIsland`, `island_testIslandTramplers` | the 4 `base*` conveyors (inv↔large test combos, incl. the `1x wineBox → 1000x coinCrown` one) | +| Island prefab | In-game name (Toponym) | Recipe conveyors placed | +|---|---|---| +| `island_Factorio` | **Sprengstofffabrik** | energyRods, 80mmT3Cannon, contactGrenades, armorPiercingRocket* | +| `island_Kaiserplatz` | Kaiserplatz | energyRods, 40mmT3Cannon | +| `island_Demo_Wunderinsel` | **Strudel** | 70mmT3Cannon | +| `island_DeusExMashineSmall` | Rauchwolke | computingModules, coralDust | +| `island_testIsland`, `island_testIslandTramplers` | (test, no Landmark) | the 4 `base*` conveyors (inv↔large test combos, incl. `1x wineBox → 1000x coinCrown`) | + +In-game names come from each island prefab's child `Landmark` (`LandmarkBehaviour.name`) → +i2 `Toponyms/`; see `bundle/extract_island_names.py` / `extracted/island_names.json`. Notes / corrections: -- The earlier guess that the **`…Armory` (Sprengstofffabrik)** islands host the explosive / - grenade / rocket conveyors is **not supported by the placements**: `island_LittleFactory01-03` - and `island_LittleFactoryArmory01-03` exist (each has an `energy_grid_*`) but place **no** - recipe conveyors at all. The grenade (`contactGrenades`) and rocket (`armorPiercingRocket`) - lines are on **Factorio**. Factorio is the main multi-product factory here. +- The **Sprengstofffabrik** (explosives factory) is literally **`island_Factorio`** — its + in-game Landmark name is `Sprengstofffabrik`, and it makes the grenades + AP rockets + + cannons. So the original "Sprengstofffabrik = explosives" intuition was right; it's just + that the prefab is `Factorio`, not an `…Armory` island. +- The `island_LittleFactory01-03` / `…Armory01-03` prefabs are actually the **Forts** + in-game (Fort Istria/Arpad/Tarnopol and Fort Zimmer/Metternich/Starhemberg) and place **no** + recipe conveyors. - `*` Factorio's child is named `game_conveyor_armorPiercingRocket` (singular) while the recipe blueprint is `game_conveyor_armorPiercingRockets` (plural) — a name mismatch; same line (→ `3x item_rocketLauncherAmmoArmorPiercing`). diff --git a/extracted/island_names.json b/extracted/island_names.json new file mode 100644 index 0000000..8a02764 --- /dev/null +++ b/extracted/island_names.json @@ -0,0 +1,137 @@ +{ + "_source": "islands_assets_all.bundle island_* prefab -> child Landmark (LandmarkBehaviour.name) -> i2 Toponyms/", + "islands": { + "island_Achilleon": { + "toponym": "Achilleon", + "display_en": null + }, + "island_Archipel": { + "toponym": "Archipel von Leopold", + "display_en": null + }, + "island_Demo_Fort": { + "toponym": "Fort", + "display_en": null + }, + "island_Demo_KleinesHaus": { + "toponym": "Eckzahn", + "display_en": null + }, + "island_Demo_Lanzelin": { + "toponym": "Lanzelin", + "display_en": null + }, + "island_Demo_Marktinsel": { + "toponym": "Segen", + "display_en": null + }, + "island_Demo_Schlangeninsel": { + "toponym": "Schlangeninsel", + "display_en": null + }, + "island_Demo_Tieftauchparadies": { + "toponym": "Felsig", + "display_en": null + }, + "island_Demo_Windig": { + "toponym": "Windig", + "display_en": null + }, + "island_Demo_Wunderinsel": { + "toponym": "Strudel", + "display_en": null + }, + "island_DeusExMashineSmall": { + "toponym": "Rauchwolke", + "display_en": null + }, + "island_Factorio": { + "toponym": "Sprengstofffabrik", + "display_en": null + }, + "island_FortArpad": { + "toponym": "Fort Arpad", + "display_en": null + }, + "island_FortIstria": { + "toponym": "Fort Istria", + "display_en": null + }, + "island_FortTarnopol": { + "toponym": "Fort Tarnopol", + "display_en": null + }, + "island_Gartenfreude": { + "toponym": "Insel St. Clemens", + "display_en": null + }, + "island_Kaiserplatz": { + "toponym": "Kaiserplatz", + "display_en": null + }, + "island_LittleFactory01": { + "toponym": "Fort Istria", + "display_en": null + }, + "island_LittleFactory02": { + "toponym": "Fort Arpad", + "display_en": null + }, + "island_LittleFactory03": { + "toponym": "Fort Tarnopol", + "display_en": null + }, + "island_LittleFactoryArmory01": { + "toponym": "Fort Zimmer", + "display_en": null + }, + "island_LittleFactoryArmory02": { + "toponym": "Fort Metternich", + "display_en": null + }, + "island_LittleFactoryArmory03": { + "toponym": "Fort Starhemberg", + "display_en": null + }, + "island_MeereSauge": { + "toponym": "MeereSauge", + "display_en": null + }, + "island_Petro": { + "toponym": "Kaiserplatz", + "display_en": null + }, + "island_Raumnadel": { + "toponym": "Raumnadel", + "display_en": null + }, + "island_ScharfeSpitzen": { + "toponym": "ScharfeSpitzen", + "display_en": null + }, + "island_Schwalbenlnsel": { + "toponym": "Schwalbenberg", + "display_en": null + }, + "island_StufenInsel": { + "toponym": "StufenInsel", + "display_en": null + }, + "island_Venedig": { + "toponym": "Venedig", + "display_en": null + }, + "island_VenedigSmall": { + "toponym": "Venedig", + "display_en": null + }, + "island_testIsland": { + "toponym": null, + "display_en": null + }, + "island_testIslandTramplers": { + "toponym": null, + "display_en": null + } + } +} \ No newline at end of file