Skip to content

Support word_swap parameter on image segments (TMEM odd-row interleaving) #534

@marijnvdwerf

Description

@marijnvdwerf

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions