Problem
Some N64 games store textures with TMEM interleaving applied: on odd rows, 32-bit words within each 64-bit TMEM word are swapped. This is a well-known N64 hardware behavior used by the SGI sprite tools (mksprite, mkisprite, etc.) and affects textures loaded via SP_TEXSHUF / sprite library routines.
When extracting these textures to PNG, splat produces garbled images because it doesn't un-swap the odd rows. There is currently no way to tell splat that an image segment needs word-swapping.
This affects standalone image segments like rgba16, ia16, ci8, i8, etc. — any format that may have been processed by the N64 sprite tools.
Context
pigment64 already supports --word-swap on both to-png (un-swap during extraction) and to-bin (re-swap during rebuild). See decompals/pigment64#22 / PR #23.
n64img (which splat uses for image extraction) does not support word-swap. PR #8 has been open as a draft since 2022 and was never merged.
- The Pokemon Snap decomp project works around this with a custom
snap_sprite segment type that manually un-swaps odd rows in Python after calling n64img's parse(). See ethteck/pokemonsnap PR #33.
Desired YAML syntax
Following the existing flip_x / flip_y pattern:
- {start: 0x8564E0, type: rgba16, width: 92, height: 21, word_swap: true}
What would need to change
Extraction (N64SegImg.split in src/splat/segtypes/n64/img.py):
Since n64img doesn't support word-swap, splat would need to un-swap the raw ROM bytes before passing them to n64img. The un-swap operation is: on odd rows, swap the two 4-byte halves of each 8-byte chunk. This is a pre-processing step on the binary data, independent of pixel format.
Alternatively, splat could invoke pigment64 to-png --word-swap instead of / in addition to n64img, but that would be a larger architectural change.
__init__ (N64SegImg.__init__):
Parse word_swap from YAML (like flip_x / flip_y) and store it on the segment object.
Build system integration:
The word_swap flag should be accessible on the segment object so that project build systems can pass --word-swap to pigment64 to-bin during rebuild. Currently, projects iterate over splat's linker entries and read seg.n64img.flip_h / seg.n64img.flip_v to build the pigment64 command line. A seg.word_swap field would allow the same pattern for word-swap.
Affected formats
The word-swap applies at the byte level (swap 4-byte halves of 8-byte chunks on odd rows), so it is format-independent. It could apply to rgba16, ia16, ci8, i8, ia8, ci4, i4, etc. Note that for RGBA32, the swap unit should be 8-byte halves of 16-byte chunks — see decompals/pigment64#30.
Real-world example
Pokemon Snap has ~33 standalone rgba16 textures (92×21 pixels each) that are word-swapped in ROM. Without splat support, these must either remain as raw bin segments or use a custom segment type that manually implements the un-swap.
Problem
Some N64 games store textures with TMEM interleaving applied: on odd rows, 32-bit words within each 64-bit TMEM word are swapped. This is a well-known N64 hardware behavior used by the SGI sprite tools (
mksprite,mkisprite, etc.) and affects textures loaded viaSP_TEXSHUF/ sprite library routines.When extracting these textures to PNG, splat produces garbled images because it doesn't un-swap the odd rows. There is currently no way to tell splat that an image segment needs word-swapping.
This affects standalone image segments like
rgba16,ia16,ci8,i8, etc. — any format that may have been processed by the N64 sprite tools.Context
pigment64already supports--word-swapon bothto-png(un-swap during extraction) andto-bin(re-swap during rebuild). See decompals/pigment64#22 / PR #23.n64img(which splat uses for image extraction) does not support word-swap. PR #8 has been open as a draft since 2022 and was never merged.snap_spritesegment type that manually un-swaps odd rows in Python after callingn64img'sparse(). See ethteck/pokemonsnap PR #33.Desired YAML syntax
Following the existing
flip_x/flip_ypattern:- {start: 0x8564E0, type: rgba16, width: 92, height: 21, word_swap: true}What would need to change
Extraction (
N64SegImg.splitinsrc/splat/segtypes/n64/img.py):Since n64img doesn't support word-swap, splat would need to un-swap the raw ROM bytes before passing them to n64img. The un-swap operation is: on odd rows, swap the two 4-byte halves of each 8-byte chunk. This is a pre-processing step on the binary data, independent of pixel format.
Alternatively, splat could invoke
pigment64 to-png --word-swapinstead of / in addition to n64img, but that would be a larger architectural change.__init__(N64SegImg.__init__):Parse
word_swapfrom YAML (likeflip_x/flip_y) and store it on the segment object.Build system integration:
The
word_swapflag should be accessible on the segment object so that project build systems can pass--word-swaptopigment64 to-binduring rebuild. Currently, projects iterate over splat's linker entries and readseg.n64img.flip_h/seg.n64img.flip_vto build the pigment64 command line. Aseg.word_swapfield would allow the same pattern for word-swap.Affected formats
The word-swap applies at the byte level (swap 4-byte halves of 8-byte chunks on odd rows), so it is format-independent. It could apply to
rgba16,ia16,ci8,i8,ia8,ci4,i4, etc. Note that for RGBA32, the swap unit should be 8-byte halves of 16-byte chunks — see decompals/pigment64#30.Real-world example
Pokemon Snap has ~33 standalone
rgba16textures (92×21 pixels each) that are word-swapped in ROM. Without splat support, these must either remain as rawbinsegments or use a custom segment type that manually implements the un-swap.