Skip to content

UOP Support fixes - #178

Merged
AsYlum- merged 7 commits into
masterfrom
fix/uop-support
Aug 19, 2026
Merged

UOP Support fixes#178
AsYlum- merged 7 commits into
masterfrom
fix/uop-support

Conversation

@AsYlum-

@AsYlum- AsYlum- commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator
  • Multi tile flags survive a UOP <> mul round trip: the visibility word maps onto both multi.mul int32s instead of one boolean (8207 of 186695 shipped tiles used to lose a bit)
  • Per-tile component ids are kept in a multi-components.txt sidecar, so repacking no longer strips a boat's tiller man, hatch and planks or a house's doors
  • Loading a multi no longer deletes invisible tiles or reorders the tile list (this dropped 122 tiles and reshuffled 119 multis on shipped data)
  • housing.bin is no longer parsed as multi 7, and the multi id ceiling now covers the ids up to 9000 that ship in MultiCollection.uop
  • MultiCollection.uop is written in the client's real shape: version 4 container, 12-byte entry headers, header Adler32, zlib level 7
  • UOP gump width and height are no longer swapped
  • EA's 0x0 placeholder gumps no longer list as valid (and then fail to draw) on compressed clients
  • Gump ids up to 0x12000 are supported, including 69971..69985 shipped by 7.0.98.1 and later
  • Compressed UOP entries are read at their real on-disk size; animations, maps and gumps were being read short
  • Animation frames using zlib-wrapped Mythic compression are now decoded instead of parsed as pixels
  • Compressed map UOPs are rejected with a clear message instead of silently misread
  • Sounds no longer get 8 bytes of junk prefixed as samples (the name block is 0x28 bytes, not 32)
  • UOP Packer: container layout matches the newest client per file type, and repacking the same input is now byte-identical
  • UOP Packer: empty or out-of-range idx rows are skipped and logged instead of packed from truncated data
  • UOP Packer: unpacked art keeps High Seas status, and large custom maps are no longer truncated to the stock facet size
  • UOP Packer: compression is validated per type, failures delete the partial file, and missing housing.bin / component sidecar are prompted for
  • Compare plugin: matches the main reader, so no more phantom differences; wider id range, released file handles and bitmap caches, and unreadable maps report once instead of throwing from paint handlers
  • Multi CSV/XML exports carry the trailing High Seas int32; gumpidx.mul and multi.idx are truncated at the last real entry instead of padded
  • UOP name hashing rewritten as a readable lookup3 port, verified bit-for-bit over 82k inputs

- Map the uop tile visibility word onto both multi.mul int32s in both
  directions (0x0001 -> flags, 0x0100 -> extra) instead of collapsing
  them into one boolean; 8207 of 186695 shipped tiles carry 0x0100
- Skip build/multicollection/housing.bin when loading the uop - its
  first two DWORDs parse as a valid (multiId 7, 1 tile) header
- RebuildTiles now only guarantees a tile on the anchor: it no longer
  deletes invisible (0x1) tiles or reorders the list, which on shipped
  data dropped 122 tiles and reshuffled 119 multis
- Raise MaximumMultiIndex to 0x2710; the shipped uop holds ids up to
  9000, which the old 0x2200 bound silently dropped
- Write multi.idx only up to the highest populated multi, with extra 0
  instead of -1, matching the client files and the packer
- Keep per-tile component ids in a multi-components.txt sidecar so a
  mul -> uop repack does not strip a boat's tiller man, hatch and
  planks or a customisable house's doors
- Write MultiCollection.uop as a version 4 container (first block at
  0x28) with the 12 byte per-entry header, and set the entry hash to
  the Adler32 of that header as the shipped files do
- Compress multi entries at zlib level 7 to land within 0.2% of the
  size the client's own packer produced
- Require housing.bin and force Zlib for MultiCollection; reject
  Mythic, throw on compression failure and delete a partial output
  instead of leaving a half written uop behind
- Use the compressed length as the on-disk byte count for animation,
  map and gump uop entries; decompressedLength only equals it while
  the entry is stored, so compressed entries were read short
- Decode animation frames stored as flag 3 (zlib wrapped Mythic)
  instead of handing Mythic bytes to the frame parser as pixels
- Fix Entry6D.Extra: getter and setter disagreed on the packing order,
  so every UOP gump reported its width and height swapped
- Add FileIndex.CacheDimensions - Seek and the indexer hand out a boxed
  copy, so writing dimensions to entry.Extra1 was silently discarded
- Probe compressed gump entries for real content, so EA's 0x0
  placeholder gumps (29, 33, 34, 37, 47, 49, 98 ...) stop listing as
  valid and failing to draw on a compressed client
- Raise the gump id ceiling to 0x12000: 7.0.98.1 and later ship ids
  69971..69985 above the old 0xFFFF bound. Gumps.Save now truncates
  gumpidx.mul to the last real row rather than padding with zeroes
- Read and write the full 0x28 byte sound name block; the previous 32
  bytes prefixed 8 bytes of per-build junk onto every sound
- Fix the staidx tail loop in TileMatrix, which compared bytes against
  a block count and left short-file tail blocks zeroed instead of -1
- Reject compressed map uop entries with a clear message - the block
  reader slices straight into the file and can only handle stored ones
- Packer: pick the container shape the newest client uses (sound moves
  to version 4), write 1000 entry blocks for version 5, emit the
  version 4 entry header for every type using that layout, and stamp
  one timestamp per file so a repack of the same input is identical
- Packer: drop idx rows that are empty or point past the end of the
  mul rather than packing truncated data, and log what was dropped
- Packer: pad artidx.mul to the 0x13FDC High Seas threshold so an
  unpacked art set is not downgraded to pre-Stygian-Abyss limits
- Packer: seek map chunks through BaseStream (a large custom facet
  overflows int) and leave a genuinely larger custom map untrimmed
- Export and import the trailing High Seas int32 of a multi tile in
  the CSV and XML formats, replacing the always-empty Cliloc column
- Reuse UopUtils.HashFileName in the packer instead of a second copy
  of hashlittle2
- UopPacker UI: default compression per file type, warn before an
  unusual choice, and confirm before packing multis with no component
  sidecar; batch mode now uses multi.mul/multi.idx so single-file and
  batch modes interoperate
- Replace the register-named transcription of HashFileName with a
  direct port of Bob Jenkins' lookup3 hashlittle2, the function the
  client runs at 0x0042C9B2. Behaviour is unchanged - verified
  bit-for-bit against the previous implementation over 82k inputs
  covering every block-boundary length class
- Add HashWord2, the word-oriented lookup3 sibling used for 32 bit
  word input, returning the client's low output word
- Mirror the gump content probe and dimension write-back from
  Ultima.Gumps, so both sides agree on which ids are valid; otherwise
  the compare tabs report differences that do not exist
- Raise the second client's gump ceiling to 0x12000 and drive the
  compared id range from both loaded clients instead of a hardcoded
  0x10000, so ids above 0xFFFF are reachable
- Fix SecondEntry6D.Extra, which had a second pair of backing fields
  that only Extra wrote to, making a write through one view invisible
  to the other
- Make SecondFileIndex disposable and re-open the mul stream in one
  place; SecondArt no longer closes the index's shared handle after
  every tile, which forced a re-open for the next one
- Dispose the outgoing index and bitmap cache when a second client is
  loaded, after the tabs drop the cached instances they parked in
  PictureBox.BackgroundImage
- Guard the SecondArt and SecondGump entry points against being called
  before a second client is loaded
- Report an unreadable or compressed map uop once in a message box and
  unload it, instead of rethrowing from OnPaint and OnMouseMove
- Re-apply the "differences only" filter after loading a second client
- A list box addresses rows through a 16 bit index in LB_ITEMFROMPOINT
  and in its scroll bar, so once the gump id ceiling moved to 0x12000
  every row above 65535 aliased onto a row near the start of the list.
  With free slots shown the list is 73728 rows, so scrolling or paging
  into its top jumped back to around row 8192
- Row identity now lives in an ascending list of ids rather than in the
  control's items, keeping a row position and a gump id distinct when
  free slots are hidden
- HasGumpId and Search binary search that list instead of walking every
  row - HasGumpId ran on each selection change for ids >= 50000
- The four insert paths collapse into one that keeps the list ordered
- Paint the selection from the tracked row: the item handed to DrawItem
  is a fresh one built in RetrieveVirtualItem, so its State never
  carries the selection and every row drew as selected
- Keep the list pane out of the form's resizing (FixedPanel.Panel1, as
  the other tabs do) and clamp it to 450 pixels; a row is a 105 pixel
  thumbnail plus two lines of text, the rest was empty background
- Guard the extract image entries against an empty selection
@AsYlum-
AsYlum- marked this pull request as ready for review August 19, 2026 18:22
@AsYlum-
AsYlum- merged commit e515476 into master Aug 19, 2026
1 check passed
@AsYlum-
AsYlum- deleted the fix/uop-support branch August 19, 2026 18:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant