From 8e9e905b8584d573c66f2509a8c701bebb3484ac Mon Sep 17 00:00:00 2001 From: Chris Sotraidis <56282582+chrissotraidis@users.noreply.github.com> Date: Mon, 5 Oct 2026 15:54:42 +0000 Subject: [PATCH] Decode Yaz0 through guarded host buffers Ported from chrissotraidis/wiicompiled (KartPad) 97cdc9a3609b6d3ad43009f76be17361b1a9e93a. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_019rLZ24cFcqTNBmb4w2dpSq --- runtime/src/hle/egg_decomp.cpp | 81 ++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 13 deletions(-) diff --git a/runtime/src/hle/egg_decomp.cpp b/runtime/src/hle/egg_decomp.cpp index 94ab9eb..7fcdc2a 100644 --- a/runtime/src/hle/egg_decomp.cpp +++ b/runtime/src/hle/egg_decomp.cpp @@ -1,20 +1,19 @@ #include "hle_stubs.h" #include +#include #include "memory.h" +#include "recomp_mod_loader.h" #include "runtime_log.h" +extern "C" void GxNotifyGuestRamDmaWrite(uint32_t addr, uint32_t size); + // Native because a crafted Yaz0 run writes past the caller's buffer // (github.com/vabold/szsHaxx) // https://github.com/vabold/Kinoko/blob/main/source/egg/core/Decomp.cc -extern "C" uint32_t EGG_Decomp_decodeSZS_80218c2c(uint32_t src, uint32_t dst) -{ - const uint32_t expandSize = (static_cast(MemoryInline::FlatRead8(src + 4)) << 24) | - (static_cast(MemoryInline::FlatRead8(src + 5)) << 16) | - (static_cast(MemoryInline::FlatRead8(src + 6)) << 8) | - static_cast(MemoryInline::FlatRead8(src + 7)); - +template +static uint32_t DecodeSZS(uint32_t src, uint32_t expandSize, Access& access) { uint32_t srcIdx = 16; uint32_t dstIdx = 0; uint32_t mask = 0; @@ -22,15 +21,15 @@ extern "C" uint32_t EGG_Decomp_decodeSZS_80218c2c(uint32_t src, uint32_t dst) while (static_cast(dstIdx) < static_cast(expandSize)) { if (mask == 0) { - flags = MemoryInline::FlatRead8(src + srcIdx++); + flags = access.ReadSource(srcIdx++); mask = 0x80; } if ((flags & mask) != 0) { - MemoryInline::FlatWrite8(dst + dstIdx++, MemoryInline::FlatRead8(src + srcIdx++)); + access.WriteOutput(dstIdx++, access.ReadSource(srcIdx++)); } else { - const uint32_t high = MemoryInline::FlatRead8(src + srcIdx); - const uint32_t low = MemoryInline::FlatRead8(src + srcIdx + 1); + const uint32_t high = access.ReadSource(srcIdx); + const uint32_t low = access.ReadSource(srcIdx + 1); srcIdx += 2; const uint32_t rep = (high << 8) | low; @@ -48,7 +47,7 @@ extern "C" uint32_t EGG_Decomp_decodeSZS_80218c2c(uint32_t src, uint32_t dst) uint32_t count = rep >> 12; count = count != 0 ? count + 2 - : static_cast(MemoryInline::FlatRead8(src + srcIdx++)) + 18; + : static_cast(access.ReadSource(srcIdx++)) + 18; for (uint32_t i = 0; i < count; ++i) { if (dstIdx >= expandSize) { @@ -59,7 +58,7 @@ extern "C" uint32_t EGG_Decomp_decodeSZS_80218c2c(uint32_t src, uint32_t dst) "The game stopped decoding a malformed Yaz0 file."); std::abort(); } - MemoryInline::FlatWrite8(dst + dstIdx++, MemoryInline::FlatRead8(dst + copyIdx++)); + access.WriteOutput(dstIdx++, access.ReadOutput(copyIdx++)); } } @@ -69,5 +68,61 @@ extern "C" uint32_t EGG_Decomp_decodeSZS_80218c2c(uint32_t src, uint32_t dst) return expandSize; } +struct GuestSZSAccess { + uint32_t src; + uint32_t dst; + + uint8_t ReadSource(uint32_t offset) const { return MemoryInline::FlatRead8(src + offset); } + uint8_t ReadOutput(uint32_t offset) const { return MemoryInline::FlatRead8(dst + offset); } + void WriteOutput(uint32_t offset, uint8_t value) const { + MemoryInline::FlatWrite8(dst + offset, value); + } +}; + +struct HostSZSAccess { + const uint8_t* src; + uint8_t* dst; + + uint8_t ReadSource(uint32_t offset) const { return src[offset]; } + uint8_t ReadOutput(uint32_t offset) const { return dst[offset]; } + void WriteOutput(uint32_t offset, uint8_t value) const { dst[offset] = value; } +}; + +static bool HasDeferredReadPages(uint32_t address, size_t length) { + const uint32_t first = address >> MemoryInline::kPageShift; + const uint32_t last = static_cast( + (static_cast(address) + length - 1) >> MemoryInline::kPageShift); + for (uint32_t page = first; page <= last; ++page) { + if (MemoryInline::g_deferredReadCoveredPages[page] != 0) return true; + } + return false; +} + +extern "C" uint32_t EGG_Decomp_decodeSZS_80218c2c(uint32_t src, uint32_t dst) +{ + const uint32_t expandSize = (static_cast(MemoryInline::FlatRead8(src + 4)) << 24) | + (static_cast(MemoryInline::FlatRead8(src + 5)) << 16) | + (static_cast(MemoryInline::FlatRead8(src + 6)) << 8) | + static_cast(MemoryInline::FlatRead8(src + 7)); + + // A token produces at least one byte; all-literal data is the largest + // valid input (one flag per eight output bytes). Three extra bytes cover + // the final malformed run before its output-overrun check aborts. + const size_t maxSourceBytes = 16ull + expandSize + (static_cast(expandSize) + 7) / 8 + 3; + if (expandSize != 0 && expandSize <= static_cast(std::numeric_limits::max()) && + !GuestFlat::RequiresCheckedAccess() && + Memory::Contains(src, maxSourceBytes) && Memory::Contains(dst, expandSize) && + !HasDeferredReadPages(src, maxSourceBytes) && !HasDeferredReadPages(dst, expandSize) && + !RecompMod::ExecutableWriteGuardMayHit(dst, expandSize)) { + HostSZSAccess access{Memory::GetPointer(src, maxSourceBytes), Memory::GetPointer(dst, expandSize)}; + const uint32_t decoded = DecodeSZS(src, expandSize, access); + GxNotifyGuestRamDmaWrite(dst, decoded); + return decoded; + } + + GuestSZSAccess access{src, dst}; + return DecodeSZS(src, expandSize, access); +} + PPC_NATIVE_OVERRIDE(80218C2C, EGG_Decomp_decodeSZS_80218c2c, uint32_t, (uint32_t src, uint32_t dst), (src, dst));