Send Ocarina 1.1.1's one-shot zstd decodes to the host decoder, switched on by zstd_native = 1 until a check run.

A profile of a play session showed the game's own zstd decoder, lifted from ARM64, taking 16 to 19% of all running CPU time on the loading threads: the patch set that sends decodes to the host's libzstd had sites for 1.4.3 only. recomp/patches-v111.txt now has the three calls of ZSTD_decompressMultiFrame (main+0xeb380) of this build, checked against its main by the generator; a lift with them changes three chunks. ov_zstd.c takes the guest function's address by game version.

The sites have not been through a check run on this build, and 1.4.3 has a caller that relies on the game decoder's own result, so on 1.1.1 the host function passes every call on to the game's decoder unless zstd_native = 1 is set or zstd_check = 1 runs both and logs what differs. The loading threads' stream feed (main+0xf5c80), which decodes most of the data, is not replaced yet.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-09 20:52:48 -05:00
parent 14969fc671
commit c32b9c40a8
3 changed files with 38 additions and 3 deletions

View File

@@ -154,7 +154,8 @@
# made again within a few frames. The first start after changing it builds the pipelines again. 0: such textures stay.
# tex_disuse = 1
# Host zstd decoder in place of the game's (0 = the game's), and a check that decodes every call both ways and logs
# the differences (1 = on; slow).
# the differences (1 = on; slow). Ocarina of Time 1.1.1 keeps the game's decoder while zstd_native is unset: its host
# decoding has not been through a check run yet, so there it takes zstd_native = 1 (or the check) to turn it on.
# zstd_native = 1
# zstd_check = 0

View File

@@ -17,6 +17,7 @@ set wide_hud NX2NAVI_PATCH_WIDE_HUD 0
set wide_movie NX2NAVI_PATCH_WIDE_MOVIE 0
set res_fixed NX2NAVI_PATCH_RES_FIXED 0
set ostep NX2NAVI_OCARINA_REALTIME 1
set zstd NX2NAVI_ZSTD_NATIVE 1
# Real-time step: the framework's per-frame update (main+0x6e6be0) gives the time manager **[main+0x4c7e890] its
# nominal frame through main+0x3b4e300 (+0x10 frames per update, +0x14 the inverse, +0x18 seconds, +0x1c the rate),
@@ -88,3 +89,13 @@ site wide_hud main 774368 ad401041 ad401041 @page:x16 fd404a10 6e040431 6e0c2491
# Pre-rendered movies: the movie layer's draw (main+0x3b5c71c) builds a viewport over the whole picture (sead viewport
# at x29 - 40: min x +8, max x +16); it moves in by the page's margin on each side, the middle 16:9 of the picture.
site wide_movie main 3b5c758 a941a269 d100a3b0 bd400a10 bd401211 1e303a32 @page:x17 bd40c233 1e330a52 1e322a10 1e323a31 bd000a10 bd001211 a941a269 # movie layer viewport: ldp x9, x8, [x19, #0x18]
# The game's zstd decoder (BSD zstd, statically linked into main) through the host's libzstd (runtime/ov_zstd.c): the
# three calls of ZSTD_decompressMultiFrame (main+0xeb380: dctx, dst, capacity, src, size, dict, dict size, ddict) go to
# the host function on the patch page (+0xff0). main+0xe910c is the file loader's call for a file that fits one read
# chunk. Until a check run has compared the two decoders on this build, the host function passes every call on to the
# game's decoder unless zstd_native = 1 is set (ov_zstd.c trusted). The loading threads' stream feed (main+0xf5c80,
# called at main+0xf5c2c) decodes most of the data and is not replaced yet.
site zstd main e910c 9400089d 9536dfb9 # bl ZSTD_decompressMultiFrame -> page+0xff0
site zstd main bf72e0 97d3d028 950aa744 # bl ZSTD_decompressMultiFrame -> page+0xff0
site zstd main f50164 97c66c87 94fd43a3 # bl ZSTD_decompressMultiFrame -> page+0xff0

View File

@@ -11,6 +11,12 @@
// - Inputs of at least AWAY_BYTES give the virtual core up while they decode (other guest threads run on it).
// NX2NAVI_ZSTD_NATIVE=0 keeps the guest decoder (the patch set switch); NX2NAVI_ZSTD_CHECK=1 decodes each call both ways,
// keeps the guest's result and logs every call where the two differ.
//
// Ocarina of Time 1.1.1 has the same decoder at other addresses (recomp/patches-v111.txt: three calls of
// ZSTD_decompressMultiFrame, main+0xeb380). Its sites have not been through a check run yet, and 1.4.3 has a caller
// whose result the game relies on being the game decoder's own, so on that build the host decodes only when
// zstd_native = 1 is set outright or the check runs (trusted); an unset key sends every call on to the game's decoder.
// Its loading threads decode most data through the game's stream feed (main+0xf5c80), which is not replaced yet.
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@@ -28,8 +34,8 @@
#ifdef RT_PATCH_PAGE
#define GUEST_MULTIFRAME 0x700003a1f0ull // 1.4.3 main: ZSTD_decompressMultiFrame (only 1.4.3 has the patch sites)
#define GUEST_USING_DDICT 0x700003a1e0ull // ZSTD_decompress_usingDDict
#define GUEST_MULTIFRAME (rt_game_version == 111 ? 0x70000eb380ull : 0x700003a1f0ull) // ZSTD_decompressMultiFrame: 1.1.1, 1.4.3
#define GUEST_USING_DDICT 0x700003a1e0ull // 1.4.3 main: ZSTD_decompress_usingDDict (1.1.1 has no site for it)
#define HOST_MULTIFRAME (RT_PATCH_PAGE + 0xff0)
#define HOST_USING_DDICT (RT_PATCH_PAGE + 0xfe0)
#define AWAY_BYTES (64u << 10)
@@ -165,12 +171,29 @@ static void count(uint64_t in, uint64_t out, uint64_t ns) {
(unsigned long long)S.inplace, (unsigned long long)S.away);
}
// Whether this build's sites may decode on the host: always but on 1.1.1, where it takes zstd_native = 1 in the
// settings or the check (see the top of the file).
static int trusted(void) {
static int v = -1;
if (v < 0) {
const char *e = cfg_get("NX2NAVI_ZSTD_NATIVE");
v = rt_game_version != 111 || (e && *e && strcmp(e, "0") != 0) || check_mode();
if (!v) rt_log("zstd: the game's own decoder (the host decoder is not checked on this build yet: zstd_native = 1 "
"turns it on, zstd_check = 1 compares the two)");
}
return v;
}
// x0 dctx, x1 dst, x2 capacity, x3 src, x4 size, then (dict, dict size, ddict) as the entry has them. guest: the
// guest function with the same arguments, n of them.
static void decode(Cpu *c, uint64_t dict, uint64_t dict_size, uint64_t ddict, uint64_t guest, int n) {
uint64_t a[8];
memcpy(a, c->x, sizeof a);
uint64_t dst = a[1], cap = a[2], src = a[3], size = a[4];
if (!trusted()) {
rt_call(c, guest, n, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]);
return;
}
// In place (the input inside the output buffer): the result depends on how the decoder's writes overtake its
// reads, which libzstd does not promise to do as the game's version does, and a host decode that failed half way
// would leave the guest decoder a damaged input. Such calls stay in the guest.