- Fixes#18746
Packs are mostly made on Windows, where "portraits/x.png" in textures.ini finds Portraits/x.png. On a
case sensitive file system (Steam Deck, Linux) it didn't, while zipped packs already ignored case.
DirectoryReader gets an opt-in ignoreCase mode, used for texture packs. On the first miss it indexes
the directory by lowercased path and retries there, so packs where every name matches never pay for
it, and the walk happens on the IO thread that loads the texture.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The cheat tests left their ini files in the working directory: on Windows the
delete failed while CheatFileParser still held the file open.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
saveIndexed = true in [options] saves the CLUT4/CLUT8 textures listed in
[indexed] as palette PNGs, saveIndexed = all saves every one of them.
The file is named <hash>_indexed.png, with the CLUT hash zeroed so one
file serves every palette of the texture. The other textures are dumped
as before.
The indices are decoded from PSP memory and the palette comes from the
current CLUT, so the dump looks like the game's texture. Add the file to
[hashes] and [indexed] to use it as a replacement.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An [indexed] entry set to gpu uploads the indices as an R8 image, and
the CLUT8_INDEXED depal shader applies the palette. A texture whose
palette changes a lot then costs no CPU time per palette, and keeps one
texture instead of one per palette.
Once such a replacement is found, the texture is looked up without the
CLUT hash, like with the TextureCLUTInShader compat flag. Only the first
mip level is used, and the replacer's filtering overrides are skipped
since filtering indices mixes up palette entries. Without shader bitwise
ops it falls back to coloring on the CPU.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Indexed replacements can now also be KTX2 files in the R8_UNORM format,
uncompressed or zstd supercompressed, with the mip levels stored in the
file. Lossy formats (Basis, BC) are not usable, since an error in an index
picks the wrong color.
The template textures.ini documents the supported files.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The unit test target doesn't have libpng on its include path, so
<png.h> failed to compile. Write the palette and grayscale test images
with a small stored-block PNG writer instead.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Fixes#15145
A replacement listed in a new [indexed] section of textures.ini holds
palette indices instead of colors: a palette PNG (the file's palette index)
or an 8-bit grayscale PNG (the gray value), at any resolution. When the
texture is built, the indices go through the game's current CLUT, including
the CLUT index shift, mask and start position, and the result is uploaded as
an ordinary RGBA texture. A game that swaps or customizes a palette keeps
working with a high-resolution replacement.
[indexed] keys use the [hashes] format and are matched with the same
wildcards, so a key with a zero CLUT hash covers every palette of the
texture. The contents hash only covers the indices, so one image is shared
by all palettes (the loaded data is shared, each palette still has its own
texture cache entry).
Saving does not dump indexed textures yet.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Reloading the ini clears the per-key lookup caches, which could keep
saying "no replacement" for textures the new ini replaces.
- With ignoreAddress, hash ranges were skipped when sizing the
replacement, though ComputeHash applies them. cache_ is now keyed by
the full key, so its lookups hit too.
- Textures sharing files but differing in size, hash range or filtering
no longer share one ReplacedTexture (the first one's settings won).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Add unittest/TestTextureReplacer which creates a fictive texture pack
(textures.ini with readable invented hashes plus real PNG files), loads
it via the replacer, and verifies lookups, filtering, hashranges, mip
levels, and missing/ignored entries.
To make the replacer runnable outside the emulator:
- The constructor now accepts a null DrawContext (formats just default
to unsupported).
- FindReplacement/FindFiltering use the replaceEnabled_ member instead
of the global config.
- Added TextureReplacer::LoadPackForTesting() to load an ini from a
path directly.