Extract UO static art (trees/obstacles) for map rendering #16

Closed
opened 2026-07-17 07:03:26 +02:00 by marco · 4 comments
Owner

Extend tools/IsoMmo.AssetExtractor to export UO static art (trees, rocks, walls, …) from art.mul/artidx.mul, so the map obstacles introduced in #4 (M2) can render with real UO art instead of placeholders.

Mirrors the existing human-animation extraction (which reads anim.mul): same "extract once, output git-ignored, placeholder fallback when absent" model. M2 ships the map + walkability + client obstacle rendering against a placeholder tree; this issue plugs in the real statics.

Scope

  • Read static art from art.mul + artidx.mul (static indices, i.e. art id + 0x4000) and export selected statics (e.g. a tree, a rock) to PNGs.
  • A way to specify which static IDs to export (config/args), with the tree(s) used by the M2 map included.
  • Output under a git-ignored client assets folder, consumed at runtime by the obstacle renderer with the existing placeholder fallback.

Definition of Done

Base DoD applies on top; the delta below:

  • Running the extractor against a real UO install produces obstacle PNGs, and the M2 map renders real UO trees where it currently shows placeholders — visually confirmed.
  • With no extracted statics present, the client still falls back to the placeholder obstacle (repo stays shippable without art).
Extend `tools/IsoMmo.AssetExtractor` to export UO **static art** (trees, rocks, walls, …) from `art.mul`/`artidx.mul`, so the map obstacles introduced in #4 (M2) can render with real UO art instead of placeholders. Mirrors the existing human-animation extraction (which reads `anim.mul`): same "extract once, output git-ignored, placeholder fallback when absent" model. M2 ships the map + walkability + client obstacle rendering against a **placeholder tree**; this issue plugs in the real statics. ## Scope - [ ] Read static art from `art.mul` + `artidx.mul` (static indices, i.e. art id + 0x4000) and export selected statics (e.g. a tree, a rock) to PNGs. - [ ] A way to specify which static IDs to export (config/args), with the tree(s) used by the M2 map included. - [ ] Output under a git-ignored client assets folder, consumed at runtime by the obstacle renderer with the existing placeholder fallback. ## Definition of Done _Base DoD applies on top; the delta below:_ - [ ] Running the extractor against a real UO install produces obstacle PNGs, and the M2 map renders real UO trees where it currently shows placeholders — visually confirmed. - [ ] With no extracted statics present, the client still falls back to the placeholder obstacle (repo stays shippable without art).
Author
Owner

Update: local extraction of uoml_setup.exe on macOS (Apple Silicon) — not feasible

Attempted to obtain the .mul art directly from uoml_setup.exe (Ultima Online: Mondain's Legacy, EA Games — InstallShield 11, ~798 MB) on the macOS dev machine. Chronology and findings:

  • FOSS extractors fail. 7z/sevenzip reports "Cannot open as archive"; unshield reports "Failed to open as InstallShield Cabinet File" even against the carved cabinet — InstallShield 11 container format is unsupported by unshield.
  • Wine runs the installer but 32-bit crashes. Wine 11 (new WoW64) launches the self-extractor and unpacks the setup bootstrap (setup.exe + data1.hdr/data1.cab, setup.ini confirms AppName=Ultima Online: Mondain's Legacy, EngineVersion 11.0.0.28844). The InstallShield GUI then dies immediately after the splash with:
    err:seh:NtRaiseException Exception frame is not in stack limits => unable to dispatch exception.
    
    Root cause: Apple Silicon has no native 32-bit x86, and Wine's WoW64 SEH dispatch is incomplete for the 32-bit InstallShield engine. The wizard window never appears, so no product data is ever written to Program Files.
  • Silent install (/s) does nothing — InstallShield 11 silent mode requires a pre-recorded setup.iss response file, and recording one needs a working GUI pass (which crashes as above).
  • The ~700 MB of actual .mul data is embedded in the outer exe and only extracted during a completed install, so the bootstrap data1.cab (664 KB) is not useful on its own.

All tooling installed for this attempt (sevenzip, unshield, wine-stable, dedicated prefix, logs) was rolled back.

Recommendation

Run uoml_setup.exe on a native Windows machine or a Windows VM/Parallels to install UO:ML, then copy the resulting .mul files (art.mul/artidx.mul, anim.mul, etc.) to the dev box. Extraction/conversion then proceeds via IsoMmo.AssetExtractor. (Extracted art stays out of git per the assets policy — local + manifest only.) This issue stays blocked on obtaining the .mul set through that path.

### Update: local extraction of `uoml_setup.exe` on macOS (Apple Silicon) — not feasible Attempted to obtain the `.mul` art directly from `uoml_setup.exe` (Ultima Online: Mondain's Legacy, EA Games — InstallShield 11, ~798 MB) on the macOS dev machine. Chronology and findings: - **FOSS extractors fail.** `7z`/`sevenzip` reports "Cannot open as archive"; `unshield` reports "Failed to open as InstallShield Cabinet File" even against the carved cabinet — InstallShield **11** container format is unsupported by `unshield`. - **Wine runs the installer but 32-bit crashes.** Wine 11 (new WoW64) launches the self-extractor and unpacks the setup bootstrap (`setup.exe` + `data1.hdr`/`data1.cab`, `setup.ini` confirms *AppName=Ultima Online: Mondain's Legacy*, EngineVersion 11.0.0.28844). The InstallShield GUI then dies immediately after the splash with: ``` err:seh:NtRaiseException Exception frame is not in stack limits => unable to dispatch exception. ``` Root cause: Apple Silicon has no native 32-bit x86, and Wine's WoW64 SEH dispatch is incomplete for the 32-bit InstallShield engine. The wizard window never appears, so no product data is ever written to Program Files. - **Silent install (`/s`) does nothing** — InstallShield 11 silent mode requires a pre-recorded `setup.iss` response file, and recording one needs a working GUI pass (which crashes as above). - The ~700 MB of actual `.mul` data is embedded in the outer exe and only extracted during a completed install, so the bootstrap `data1.cab` (664 KB) is not useful on its own. All tooling installed for this attempt (`sevenzip`, `unshield`, `wine-stable`, dedicated prefix, logs) was rolled back. ### Recommendation Run `uoml_setup.exe` on a **native Windows machine or a Windows VM/Parallels** to install UO:ML, then copy the resulting `.mul` files (`art.mul`/`artidx.mul`, `anim.mul`, etc.) to the dev box. Extraction/conversion then proceeds via `IsoMmo.AssetExtractor`. (Extracted art stays out of git per the assets policy — local + manifest only.) This issue stays blocked on obtaining the `.mul` set through that path.
Author
Owner

Unblocked: full legacy UO client obtained

A complete legacy UO client (Mondain's Legacy, 5241 files / 1.9 GB) was copied to the dev machine (~/Games/UO) from a homelab share and integrity-verified (file count + total bytes + MD5 of the critical .mul all match source). All needed data is present: art.mul/artidx.mul (statics — trees, coins, swords), anim*.mul, hues.mul, tiledata.mul, gumpart.mul/gumpidx.mul, map0.mul/statics0.mul/staidx0.mul.

This supersedes the Wine/uoml_setup.exe dead-end above — no installer needed. Next step is extending IsoMmo.AssetExtractor to export statics from art.mul (using tiledata.mul + hues.mul) and produce a versioned art-ID manifest, replacing the placeholder diamonds. Extracted art stays git-ignored per the assets policy; only the source .mul stay local. Run with dotnet run --project tools/IsoMmo.AssetExtractor -- ~/Games/UO.

### Unblocked: full legacy UO client obtained A complete legacy UO client (Mondain's Legacy, 5241 files / 1.9 GB) was copied to the dev machine (`~/Games/UO`) from a homelab share and integrity-verified (file count + total bytes + MD5 of the critical `.mul` all match source). All needed data is present: `art.mul`/`artidx.mul` (statics — trees, coins, swords), `anim*.mul`, `hues.mul`, `tiledata.mul`, `gumpart.mul`/`gumpidx.mul`, `map0.mul`/`statics0.mul`/`staidx0.mul`. This supersedes the Wine/`uoml_setup.exe` dead-end above — no installer needed. Next step is extending `IsoMmo.AssetExtractor` to export statics from `art.mul` (using `tiledata.mul` + `hues.mul`) and produce a versioned art-ID manifest, replacing the placeholder diamonds. Extracted art stays git-ignored per the assets policy; only the source `.mul` stay local. Run with `dotnet run --project tools/IsoMmo.AssetExtractor -- ~/Games/UO`.
Author
Owner

Static art extraction landed on branch feat/static-art-extraction. Added ArtMulReader (art.mul static decode, unit-tested against synthetic bytes) + a statics extractor mode (discovery dump + named extraction via --map). Art-id manifest lives in the justfile (just extract-statics / extract-all): tree_0=0x0CCE (leafy tree), item_coin=0x0EED (gold coin), item_sword=0x0F5E (longsword) — all verified visually. Client renders real tree statics (zero-change via existing StaticArtLoader) and resolves ground-item art per kind (new ItemArt), falling back to a tinted placeholder when art is absent. Character sprites also now real (ran the existing human extractor against the local client). Deferred: land terrain tiles (UO 44x44 vs our 64x32 grid — needs a grid decision, tracked separately); item/skin hue.

Static art extraction landed on branch feat/static-art-extraction. Added ArtMulReader (art.mul static decode, unit-tested against synthetic bytes) + a statics extractor mode (discovery dump + named extraction via --map). Art-id manifest lives in the justfile (just extract-statics / extract-all): tree_0=0x0CCE (leafy tree), item_coin=0x0EED (gold coin), item_sword=0x0F5E (longsword) — all verified visually. Client renders real tree statics (zero-change via existing StaticArtLoader) and resolves ground-item art per kind (new ItemArt), falling back to a tinted placeholder when art is absent. Character sprites also now real (ran the existing human extractor against the local client). Deferred: land terrain tiles (UO 44x44 vs our 64x32 grid — needs a grid decision, tracked separately); item/skin hue.
Author
Owner

Done and merged to main (43c9098). Real UO art now renders in the client: tree statics on obstacles, per-kind ground-item art (coin/sword), and real grass land tiles (44x44 scaled to our 64x32 tile). Character sprites also real (existing human extractor run against the local client). ArtMulReader (static + land decode) is unit-tested; art-id manifest + one-command re-extraction live in the justfile (extract-all). Placeholder fallbacks keep the repo shippable without assets. Deferred as separate concerns: gump/paperdoll art (#19, parked branch), skin/clothing hue, softening the out-of-map void edge.

Done and merged to main (43c9098). Real UO art now renders in the client: tree statics on obstacles, per-kind ground-item art (coin/sword), and real grass land tiles (44x44 scaled to our 64x32 tile). Character sprites also real (existing human extractor run against the local client). ArtMulReader (static + land decode) is unit-tested; art-id manifest + one-command re-extraction live in the justfile (extract-all). Placeholder fallbacks keep the repo shippable without assets. Deferred as separate concerns: gump/paperdoll art (#19, parked branch), skin/clothing hue, softening the out-of-map void edge.
marco closed this issue 2026-07-17 10:51:19 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
marco/IsoMmo#16
No description provided.