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>
- 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>
These own GPU objects, memory or refcounts in their destructors (or assert
there that they were torn down), so a copy would double-free. Nothing copies
them today; this keeps it that way. The manager base classes cover every
backend's subclass.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Put the anisotropy level in the sampler key, so changing it applies on
Vulkan and D3D11.
- Release CLUT textures at shutdown on GLES and D3D11.
- Fix the depth readback viewport, which squeezed the image whenever the
read rectangle was smaller than the fbo.
- Test the computed depth, not the unset result, in the equal-depth clear
check.
- Don't read back a CLUT from a framebuffer without an fbo.
- Tolerate null entries when releasing post-shader objects and CLUT
textures after a failed creation.
- ImGe: Don't crash on a framebuffer without an fbo, or on GetVFB under the
software renderer.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The blit cost remembered only the last buffer a decoder wrote into, forever.
Move the texture cache's video list (with its ageing out a few flips after
the last write) into GPUCommon, so the texture cache, the blit cost and
SoftGPU all share one. That also counts both of a double-buffered player's
frames, which exposed that a clear drawn with texturing still enabled was
being charged as a blit - skip clears and draws without texture coordinates.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
videos_ only learns about the CSC output, so the texture we actually draw is
just an ordinary 512x512 8888 texture whose contents happen to be different
every frame: hash, miss, throw it in the secondary cache, rebuild, forever.
So track the copy. NotifyVideoCopy marks the destination as video when the
source is, and the copy funnels call it: sceDmacMemcpy, sceKernelMemcpy, and
the four replaced memcpy/memmove variants. It sits outside their "is either
side VRAM" gate, since a RAM-to-RAM copy of a frame is still a frame.
Being a video texture also gets it forced linear filtering and keeps it out of
texture upscaling, which is what you want for a video either way.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The non-DXT decode paths read w texels per row from a source whose stride, range
check and unswizzle buffer were all sized from bufw alone. w and bufw are
independent GE registers, so w > bufw is reachable, and the last row then runs
off the end of both the validated guest range and the temp buffer.
The previous commit clamped w down to bufw, which stops the overrun but is the
wrong shape twice over: the game asked for w texels and the destination is sized
for w, so the tail of every row is left holding whatever was in the buffer, and
it silently narrows a texture the hardware would have decoded in full.
Size the checks from both instead. The extent a decode touches is bufw per row
plus however far the last row reaches past its own stride - the same adjustment
TextureReplacer::ComputeHash and the GE recorder already make - so the range
check, the height it falls back to when the range is short, and the unswizzle
buffer are all computed that way now.
Pull the five copies of "resize tmpTexBuf32_, unswizzle into it" into a helper
while we're here, since they all need the same sizing. It zeroes the buffer when
w reaches past bufw, as UnswizzleFromMem only fills the stride and the tail would
otherwise be stale heap.
ComputeTextureHash has the same bufw-only assumption, but it's left alone
deliberately - changing what goes into a texture hash isn't worth the risk here.
DXT is left alone too - it clamps to minw, but there the limit is the block index
within a bufw/4 block row, so its range check already covers what it reads.
Vuln 17: ReplacedTexture::LoadLevelData let a KTX2/DDS file at a higher
mip level resize the shared data_ vector to its own (attacker-controlled)
mip count, so data_[mipLevel + i] indexed out of bounds and the KTX2
branch resized a different element than it wrote to. Disallow mixing
image formats across mip levels, cap the container mip count, and resize
the same element that is used as the transcode destination.
Vuln 18: DecodeTextureLevel only validated the start address for
non-DXT textures, so guest-controlled w/h/bufw could drive reads past
mapped RAM. Validate the needed range like the DXT path does and clamp
the height; ReadIndexedTex now takes the clamped w/h.