mirror of
https://github.com/valentderah/totk-pc-recomp.git
synced 2026-11-11 01:41:02 +00:00
Boot 1.4.3 to its first NVN call with the 1.4.3 patch table, loader values and SDK 20.5 swkbd layout
recomp/patches-v143.txt ports MacNativeGames' 1.4.3 fps60, real-time step, cloth and bounce sites; the real-time producer reads the nominal interval from patch page +8, which frame_rate.c now writes as seconds * 60. make_romfs_patches.py takes --version (Common.Product.140 on 1.4.3). SDK 20.5 grew KeyboardConfig to 0x518 bytes, so ShowKeyboardArg's work buffer is at +0x518; swkbd_win.c and the services probe pick the offset by version. 1.4.3's movie::Decoder vtable names the complete-object destructor D1, which gets the D2 override. The loader probe has the measured 1.4.3 import and relocation counts, and probe-ipc expects REV15 from the 1.4.3 SDK and skips the 1.2.1 sf heap constructors (1.4.3 creates the audio heap lazily). A non-1.2.1 build defaults to its own profile, build/profile-<version>: 1.4.3 rewrites the saves it loads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c8105294ed
commit
cd60d20a7f
10 files changed
+101
-34
No files matched your search
@@ -0,0 +1,46 @@
|
||||
# Code patches for TotK 1.4.3 main (recomp/patches.py). Ported from MacNativeGames
|
||||
# games/totk/runtime/game_patches_v143.c (MIT, Copyright (c) 2026 MacNativeGames contributors) at upstream 73c4821,
|
||||
# each original word checked against the 1.4.3 main. MacNativeGames branches to veneers after the text; here a
|
||||
# veneer's body is the site's replacement. The patch page holds three floats the runtime writes at every present
|
||||
# (frame_rate.c): +0 the 30 Hz ratio (seconds * 30), +4 the frame step in seconds, +8 the step in 60 Hz ticks.
|
||||
|
||||
set fps60 TOTK_FPS60 1
|
||||
set realtime TOTK_REALTIME 1
|
||||
set cloth TOTK_CLOTH_REALTIME 1
|
||||
set bounce TOTK_HIGHFPS_FIX 1
|
||||
|
||||
# 60 fps: the adaptive present-interval controller (main+0x2db174, 1.2.1 0x8f26c8) gets the minimum interval 1.
|
||||
site fps60 main 2db204 6b08015f 7100055f # clamp: cmp w10, #1 (was cmp w10, w8 = minimum)
|
||||
site fps60 main 2db208 1a89b10a 1a9fa52a # clamp: csinc w10, w9, wzr, ge (desired = max(desired, 1))
|
||||
site fps60 main 2db22c b940468a d503201f # lower trigger: nop the stored-candidate load
|
||||
site fps60 main 2db360 6b0801bf 710005bf # raise path: cmp w13, #1
|
||||
site fps60 main 2db36c 1a88c1ac 1a9fc5ac # raise path: csinc w12, w13, wzr, gt (next lower candidate floors at 1)
|
||||
site fps60 main 2db340 b9400288 52800028 # lower path: mov w8, #1 (minimum interval 1)
|
||||
site fps60 main 2db34c 2a0a03f5 1a9fa555 # lower path: csinc w21, w10, wzr, ge (result never below 1)
|
||||
|
||||
# Real-time step. The frame-step producer feeds both FrameRateMgr call sites with s8 = nominal interval * a 0.5/1.0
|
||||
# coefficient (sys+0x1da bit 0, fcsel above the site); the nominal interval comes from the patch page, the coefficient
|
||||
# and FrameRateMgr's own arithmetic stay. x8 is free: the ldxr after the site loads it. The global ratio after the
|
||||
# controller call (1.2.1 0x8f2394) reads the ratio from the page instead of 60 / interval * 0.5.
|
||||
site realtime main 3e5658 1e210808 @page:x8 bd400900 1e210808 # fmul s8, s0, s1: s0 = [page+8] first
|
||||
site realtime main 2daa54 bd425260 @page:x9 # ldr s0, [x19, #0x250] -> adrp x9, page
|
||||
site realtime main 2daa58 52a84e08 bd400120 # mov w8, #60.0 -> ldr s0, [x9] (global ratio)
|
||||
site realtime main 2daa6c 5e21d800 d503201f # scvtf s0, s0 -> nop
|
||||
site realtime main 2daa70 1e201820 d503201f # fdiv s0, s1, s0 -> nop
|
||||
site realtime main 2daa74 1e2c1001 d503201f # fmov s1, #0.5 -> nop
|
||||
site realtime main 2daa78 1e210800 d503201f # fmul s0, s0, s1 -> nop
|
||||
|
||||
# Hair and cloth: the actor animation update builds a second clock {seconds = factor / 30, ratio = factor} per
|
||||
# rendered frame. 1.4.3 inlines it in two actor paths, where the factor was just stored to the ratio slot: the
|
||||
# replacement stores measured ratio * factor there and computes measured seconds * factor. Two other callers share the
|
||||
# out-of-line update (main+0x2a0e40), whose s2 (seconds) and s3 (ratio) are scaled by the measured ratio; its third
|
||||
# caller already passes measured time and stays. x8 and s4 are free at all four sites.
|
||||
site cloth main 246eac 1e200940 @page:x8 bd400100 1e200940 bd0367e0 bd400500 1e200940 # fmul s0, s10, s0
|
||||
site cloth main 345904 1e210801 @page:x8 bd400101 1e210801 bd0117e1 bd400501 1e210801 # fmul s1, s0, s1
|
||||
site cloth main 24944c 94015e7d @page:x8 bd400104 1e240842 1e240863 94015e7d # bl 0x2a0e40
|
||||
site cloth main 28bc98 9400546a @page:x8 bd400104 1e240842 1e240863 9400546a # bl 0x2a0e40
|
||||
|
||||
# Trampolines: the contact code scales the bounce vector by 0.1 when time object +0x28 (30 / (ratio * time scale)) is
|
||||
# over 100; compare +0x28 * +0x38 (30 / time scale) instead, the same value at 30 Hz and independent of the frame rate.
|
||||
# s1 is set again by the fmov two instructions on.
|
||||
site bounce main 562a64 bd402920 bd402920 bd403921 1e210800 # ldr s0, [x9, #0x28]; ldr s1, [x9, #0x38]; fmul s0, s0, s1
|
||||
+15
-10
@@ -2,22 +2,23 @@
|
||||
// contributors): runtime_frame_step_update (2338bb0: no 1/120 s floor) and the speed survey's player chain
|
||||
// (world_survey.c player_actor).
|
||||
// Frame rate: 60 fps and the real-time frame step, the host half of the patch sets fps60, realtime and cloth
|
||||
// (build table patches-v121.txt; docs/research/macnativegames.md section 6.2).
|
||||
// (build tables patches-v121.txt, patches-v143.txt; docs/research/macnativegames.md section 6.2).
|
||||
//
|
||||
// TotK's adaptive present-interval controller (main+0x8f26c8) turns the main thread's measured work into a number of
|
||||
// 60 Hz ticks clamped to [minimum, maximum]; shipping data sets the minimum to 2 (30 fps), the set fps60 lowers it to
|
||||
// 1. The controller still raises the interval when a frame does not fit. Shipping code then steps the game by whole
|
||||
// vsyncs (VFR step main+0x7ede6c: ratio = interval * 0.5, seconds = interval / 60; global ratio at main+0x8f2394), so
|
||||
// game speed is right only when frames take exactly their interval. With the set realtime both read the patch page
|
||||
// instead: +0 ratio = seconds * 30, +4 seconds, the moving average of the last 5 display intervals, at most 1/15 s
|
||||
// instead: +0 ratio = seconds * 30, +4 seconds (1.4.3: +8 interval = seconds * 60, the producer's nominal interval
|
||||
// before its half-step coefficient), the moving average of the last 5 display intervals, at most 1/15 s
|
||||
// (slower frames slow the game down, as on the console), no lower bound, so a higher frame-rate cap only changes the
|
||||
// pacer (nvn_api_obj.c VSYNC_HZ). The set cloth gives hair and cloth the same clock and needs realtime.
|
||||
//
|
||||
// The sample is the display time the pacer gives each present (nvnQueuePresentTexture), so the step follows the
|
||||
// frames as shown, pacing waits included. With the step on, the pacer ignores the game's interval and holds a frame
|
||||
// only to the frame-rate cap (nvn_api_obj.c present_period): the controller's 2 or 3 vsyncs would only add latency.
|
||||
// One 64-bit store per present; the guest reads the two floats on its own
|
||||
// threads, and both stay finite and in range whichever store they see.
|
||||
// One 64-bit and one 32-bit store per present; the guest reads the floats on its own threads, and each stays finite
|
||||
// and in range whichever store it sees.
|
||||
//
|
||||
// TOTK_TRACE=perf adds a line per 120 presents: fps, the game seconds stepped against wall seconds, and with
|
||||
// TOTK_SPEED_PROBE=1 the player's position and ground speed (an independent check: Link runs at the same speed at
|
||||
@@ -40,16 +41,19 @@
|
||||
#define STEP_FIRST_S (1.0 / 60.0)
|
||||
|
||||
static _Atomic uint64_t *step_word; // patch page +0: ratio (low float), seconds (high float)
|
||||
static _Atomic uint32_t *step_interval; // patch page +8: seconds * 60 (patches-v143.txt realtime)
|
||||
static uint64_t last_ns;
|
||||
static double samples[STEP_SAMPLES];
|
||||
static unsigned sample_count, sample_next;
|
||||
|
||||
static uint64_t step_bits(float seconds) {
|
||||
float ratio = seconds * 30.0f;
|
||||
uint32_t lo, hi;
|
||||
static void step_store(float seconds) {
|
||||
float ratio = seconds * 30.0f, interval = seconds * 60.0f;
|
||||
uint32_t lo, hi, iv;
|
||||
memcpy(&lo, &ratio, 4);
|
||||
memcpy(&hi, &seconds, 4);
|
||||
return (uint64_t)lo | (uint64_t)hi << 32;
|
||||
memcpy(&iv, &interval, 4);
|
||||
atomic_store_explicit(step_word, (uint64_t)lo | (uint64_t)hi << 32, memory_order_release);
|
||||
atomic_store_explicit(step_interval, iv, memory_order_release);
|
||||
}
|
||||
|
||||
void frame_step_init(void) {
|
||||
@@ -64,7 +68,8 @@ void frame_step_init(void) {
|
||||
}
|
||||
if (rt_patch_enabled(realtime)) {
|
||||
step_word = (_Atomic uint64_t *)rt_patch_page();
|
||||
atomic_store(step_word, step_bits((float)STEP_FIRST_S));
|
||||
step_interval = (_Atomic uint32_t *)((uint8_t *)rt_patch_page() + 8);
|
||||
step_store((float)STEP_FIRST_S);
|
||||
}
|
||||
rt_log("frame rate: present interval %s, %s", rt_patch_enabled(fps60) ? "1..3 (60 fps)" : "2..3 (shipping 30 fps)",
|
||||
step_word ? "real-time step (5-frame average of display intervals, at most 1/15 s)" : "whole-vsync steps");
|
||||
@@ -177,7 +182,7 @@ void frame_step_present(uint64_t display_ns, int interval) {
|
||||
double sum = 0;
|
||||
for (unsigned i = 0; i < sample_count; i++) sum += samples[i];
|
||||
step = sum / sample_count;
|
||||
atomic_store_explicit(step_word, step_bits((float)step), memory_order_release);
|
||||
step_store((float)step);
|
||||
}
|
||||
}
|
||||
last_ns = display_ns;
|
||||
|
||||
+3
-3
@@ -721,7 +721,7 @@ static void sdk_prepare(Cpu *c) {
|
||||
ST32(LD64(c->tpidrro + 0x1F8) + 0x1B0, kern_main_thread(&again));
|
||||
const RtLoaded *sdk = ldr_module(2);
|
||||
static const uint64_t heap_ctors[] = {0x1dcbd0, 0x1de8a0};
|
||||
for (unsigned k = 0; k < sizeof heap_ctors / sizeof *heap_ctors; k++) {
|
||||
for (unsigned k = 0; !RT_V143 && k < sizeof heap_ctors / sizeof *heap_ctors; k++) {
|
||||
int found = 0;
|
||||
for (uint64_t e = 0; e < sdk->init_array_size; e += 8)
|
||||
found |= LD64(sdk->init_array + e) == sdk->base + heap_ctors[k];
|
||||
@@ -740,8 +740,8 @@ static void probe_sdk_client(Cpu *c) {
|
||||
rt_call(c, stub(SYM_INIT_PARAM), 1, (uint64_t)(uintptr_t)param);
|
||||
printf("sdk: InitializeAudioRendererParameter -> %d Hz x%d, mix buffers %d, revision word %#x (REV%d)\n", param[0],
|
||||
param[1], param[2], (uint32_t)param[13], revision_number_of((uint32_t)param[13]));
|
||||
CHECK(param[0] == 48000 && param[1] == 240 && (uint32_t)param[13] == g_param.revision,
|
||||
"SDK default parameter (TotK 1.2.1 builds REV11 requests)");
|
||||
CHECK(param[0] == 48000 && param[1] == 240 && (uint32_t)param[13] == AUDREN_REV(RT_V143 ? 15 : 11),
|
||||
"SDK default parameter (TotK 1.2.1 builds REV11 requests, 1.4.3 REV15)");
|
||||
param[2] = g_param.mix_buffer_count, param[3] = g_param.submix_count, param[4] = g_param.voice_count;
|
||||
param[5] = g_param.sink_count, param[6] = g_param.effect_count;
|
||||
uint64_t work = rt_call(c, stub(SYM_WORK_SIZE), 1, (uint64_t)(uintptr_t)param);
|
||||
|
||||
+3
-3
@@ -578,9 +578,9 @@ static const Expect expect_121[] = {
|
||||
{"sdk", {10917, 13976, 1207, 8932}, 35032, 16, 24099, 13, {8, 0, 0}, 5, 0x30, 116},
|
||||
};
|
||||
static const Expect expect_143[] = {
|
||||
{"main", {499078, 1393, 28, 650}, 501149, 0, 0, 0, {0, 0, 0}, 0, 0x10b4f10, 10717},
|
||||
{"subsdk0", {3424, 10927, 905, 3817}, 19073, 0, 0, 0, {0, 0, 0}, 0, 0x20, 45},
|
||||
{"sdk", {15013, 10135, 1079, 7832}, 34059, 0, 0, 0, {0, 0, 0}, 0, 0x400, 83},
|
||||
{"main", {499078, 1393, 28, 650}, 501149, 2071, 0, 680, {0, 16, 656}, 8, 0x10b4f10, 10717},
|
||||
{"subsdk0", {3424, 10927, 905, 3817}, 19073, 6557, 9092, 663, {9, 0, 654}, 0, 0x20, 45},
|
||||
{"sdk", {15013, 10135, 1079, 7832}, 34059, 10, 19036, 8, {8, 0, 0}, 0, 0x400, 83},
|
||||
};
|
||||
#define expect (RT_V143 ? expect_143 : expect_121)
|
||||
|
||||
|
||||
@@ -854,6 +854,8 @@ static void ov_get_output_md(Cpu *c) {
|
||||
|
||||
HOST_OVERRIDE("_ZN5movie7DecoderC1ENS_11DecoderTypeENS_19DecoderOutputFormatENS_11DecoderModeEi", ov_ctor);
|
||||
HOST_OVERRIDE("_ZN5movie7DecoderD2Ev", ov_d2);
|
||||
// 1.4.3's vtable and a PLT call name the complete-object destructor; without virtual bases it is D2.
|
||||
HOST_OVERRIDE("_ZN5movie7DecoderD1Ev", ov_d2);
|
||||
HOST_OVERRIDE("_ZN5movie7DecoderD0Ev", ov_d0);
|
||||
HOST_OVERRIDE("_ZN5movie7Decoder9ConfigureEPNS_9MediaDataE", ov_configure);
|
||||
HOST_OVERRIDE("_ZN5movie7Decoder20RegisterClientEventsEPNS_13DecoderEventsE", ov_register_events);
|
||||
|
||||
+5
-3
@@ -628,10 +628,12 @@ static void probe_media_gfx(void) {
|
||||
"vsync event %s\n", display, layer, window, vsync ? "bound (auto-clear)" : "FAILED");
|
||||
}
|
||||
|
||||
// ---- software keyboard (swkbd_win.c): ShowKeyboardArg built by the 1.2.1 SDK's own setters, as main+0x1b2b3a0 does
|
||||
// ---- software keyboard (swkbd_win.c): ShowKeyboardArg built by the SDK's own setters, as the horse naming does
|
||||
enum { KB_TYPE = 0x00, KB_DISABLED = 0x1c, KB_HEADER = 0x24, KB_GUIDE = 0x1a8, KB_MAX_LEN = 0x3ac, KB_MIN_LEN = 0x3b0,
|
||||
KB_UTF8 = 0x3bd, KB_INIT_LEN = 0x3c4, ARG_WORK = 0x4d0, ARG_WORK_SIZE = 0x4d8, ARG_SIZE = 0x500,
|
||||
STRING_SIZE = 0x7d4, SWKBD_CANCELED = 0x29f };
|
||||
KB_UTF8 = 0x3bd, KB_INIT_LEN = 0x3c4, STRING_SIZE = 0x7d4, SWKBD_CANCELED = 0x29f };
|
||||
#define ARG_WORK (RT_V143 ? 0x518 : 0x4d0) // SDK 20.5 grew KeyboardConfig (swkbd_win.c)
|
||||
#define ARG_WORK_SIZE (ARG_WORK + 8)
|
||||
#define ARG_SIZE (RT_V143 ? 0x548 : 0x500)
|
||||
|
||||
static uint64_t sdk(const char *name) {
|
||||
uint64_t a = ldr_symbol(name);
|
||||
|
||||
+2
-1
@@ -6,7 +6,8 @@
|
||||
//
|
||||
// The patch page is the first 4 KiB page after main's image, inside the module window memory.c reserved: replacement
|
||||
// code reaches it with one adrp, as MacNativeGames' data page after main (games/totk/runtime/game_patches.c). Its
|
||||
// layout belongs to the sets that use it (frame_rate.c: +0 the 30 Hz ratio, +4 the frame step in seconds).
|
||||
// layout belongs to the sets that use it (frame_rate.c: +0 the 30 Hz ratio, +4 the frame step in seconds, +8 the
|
||||
// step in 60 Hz ticks).
|
||||
// Probe modes never call rt_patches_init: every set stays off.
|
||||
#include "cfg.h"
|
||||
#include "platform/platform.h"
|
||||
|
||||
+5
-3
@@ -30,12 +30,14 @@
|
||||
#include "ui_text.h"
|
||||
#include "vk.h"
|
||||
|
||||
// KeyboardConfig (0x4d0 bytes) then the work buffer {pointer, size}: offsets read from the SDK setters (MNG) and
|
||||
// checked against the 1.2.1 SDK by --probe-services.
|
||||
// KeyboardConfig then the work buffer {pointer, size}: offsets read from the SDK setters (MNG) and checked against
|
||||
// the SDK by --probe-services. SDK 20.5 (1.4.3) put a text-check callback at +0x4c8 and grew KeyboardConfig from
|
||||
// 0x4d0 to 0x518 bytes (SetInitialText, SetUserWordList read the work buffer there); the fields below +0x3d8 stay.
|
||||
#define ARG_WORK (RT_V143 ? 0x518 : 0x4d0)
|
||||
#define ARG_WORK_SIZE (ARG_WORK + 8)
|
||||
enum {
|
||||
KB_TYPE = 0x00, KB_OK_TEXT = 0x04, KB_DISABLED = 0x1c, KB_HEADER = 0x24, KB_SUB = 0xa6, KB_GUIDE = 0x1a8,
|
||||
KB_MAX_LEN = 0x3ac, KB_MIN_LEN = 0x3b0, KB_PASSWORD = 0x3b4, KB_UTF8 = 0x3bd, KB_INIT_OFF = 0x3c0, KB_INIT_LEN = 0x3c4,
|
||||
ARG_WORK = 0x4d0, ARG_WORK_SIZE = 0x4d8,
|
||||
KB_TYPE_NUMPAD = 1, KB_LIMIT = 500, SWKBD_CANCELED = 0x29f,
|
||||
};
|
||||
enum { B_A = 0, B_B = 1, B_PLUS = 10 }; // input.h bits
|
||||
|
||||
+7
-3
@@ -213,7 +213,8 @@ def module_glue(layout, linked, hashes, paths, version):
|
||||
out += ['};', f'const int rt_exefs_sha256_count = {len(hashes)};', '']
|
||||
for name in ('exefs', 'romfs', 'profile'):
|
||||
out.append(f'const char rt_default_{name}[] = {c_string(paths[name].as_posix())};')
|
||||
out += [f'const int rt_game_version = {int(DISPLAY_VERSIONS[version].replace(".", "")) if version in DISPLAY_VERSIONS else 0};', '']
|
||||
number = int(DISPLAY_VERSIONS[version].replace('.', '')) if version in DISPLAY_VERSIONS else 0 # 1.4.3 -> 143
|
||||
out += [f'const int rt_game_version = {number};', '']
|
||||
return '\n'.join(out)
|
||||
|
||||
|
||||
@@ -584,7 +585,9 @@ def main():
|
||||
help="totk.exe's default ExeFS directory (default build/extracted/<version>/exefs)")
|
||||
ap.add_argument('--romfs', type=Path, help="totk.exe's default RomFS directory with romfs.bin and index.json "
|
||||
'(default build/extracted/<version>/romfs)')
|
||||
ap.add_argument('--profile', type=Path, help="totk.exe's default profile directory (default build/profile)")
|
||||
ap.add_argument('--profile', type=Path, help="totk.exe's default profile directory (default build/profile for v121, "
|
||||
"build/profile-<version> otherwise: a newer game version rewrites the "
|
||||
"saves it loads, and an older one cannot read them back)")
|
||||
ap.add_argument('--toolchain', type=Path, help='llvm-mingw directory (with bin/clang.exe) to build with instead of '
|
||||
'MSYS2 UCRT64 clang')
|
||||
ap.add_argument('--ninja', type=Path, help='ninja.exe to build with (default: MSYS2 UCRT64, else bin/ninja.exe of the '
|
||||
@@ -604,7 +607,8 @@ def main():
|
||||
extracted = ROOT / 'build' / 'extracted' / args.version
|
||||
paths = {'exefs': (args.exefs or extracted / 'exefs').resolve(),
|
||||
'romfs': (args.romfs or extracted / 'romfs').resolve(),
|
||||
'profile': (args.profile or ROOT / 'build' / 'profile').resolve()}
|
||||
'profile': (args.profile or ROOT / 'build' / ('profile' if args.version == 'v121'
|
||||
else f'profile-{args.version}')).resolve()}
|
||||
expected = args.expected
|
||||
if not expected:
|
||||
if args.version not in DISPLAY_VERSIONS:
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
# Adapted from MacNativeGames games/totk/scripts/make_romfs_patches.py @ ebe621d (MIT, Copyright (c) 2026
|
||||
# MacNativeGames contributors).
|
||||
"""Build the 60 fps RomFS overlay for TotK 1.2.1 (runtime/romfs.c, TOTK_ROMFS_PATCHES).
|
||||
"""Build the 60 fps RomFS overlay for TotK 1.2.1 or 1.4.3 (runtime/romfs.c, TOTK_ROMFS_PATCHES).
|
||||
|
||||
UI/LayoutArchive/Common.Product.110.Nin_NX_NVN.blarc.zs: the weapon / shield / material quick menus capture the game
|
||||
UI/LayoutArchive/Common.Product.<110 or 140>.Nin_NX_NVN.blarc.zs: the weapon / shield / material quick menus capture the game
|
||||
picture for their background when the panes C_CaptureBG_00, C_CaptureClearBG_00 and P_CaptureClearBG_00 become
|
||||
visible at animation frame 1.0 (anim/ShortCutBG_00_In.bflan), AutoBuilder the same with C_CaptureBG_00
|
||||
(anim/AutoBuilder_00_In.bflan). UI animations advance 0.5 frames per game frame at 60 fps, so the capture lands after
|
||||
the world stopped drawing and the background can be black. The visibility keys move to frame 0.0: the live world is
|
||||
captured at once (upstream checked weapon, shield, material and ability wheel at 60 fps, and 30 fps).
|
||||
The game-over flow fix of the same upstream script is 1.0.0 only (1.2.1 runs DmF_SY_GameOver.110) and is left out.
|
||||
The game-over flow fix of the same upstream script is 1.0.0 only (1.2.1 and 1.4.3 run DmF_SY_GameOver.110) and is
|
||||
left out.
|
||||
|
||||
Input: build/extracted/v121/romfs (romfs.bin and index.json, verified by tools/extract.py). Output: <out>/<path> and
|
||||
<out>/manifest.tsv (path, size, SHA-256 of the overlay, SHA-256 of the original). Run with .venv-py314.
|
||||
Input: build/extracted/<version>/romfs (romfs.bin and index.json, verified by tools/extract.py). Output:
|
||||
build/<version>/romfs-patches/<path> by default, and <out>/manifest.tsv (path, size, SHA-256 of the overlay, SHA-256 of the original). Run with .venv-py314.
|
||||
"""
|
||||
import argparse
|
||||
import compression.zstd as zstd
|
||||
@@ -22,9 +23,12 @@ from pathlib import Path
|
||||
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
ap.add_argument('--romfs', type=Path, default=root / 'build/extracted/v121/romfs')
|
||||
ap.add_argument('--out', type=Path, default=root / 'build/v121/romfs-patches')
|
||||
ap.add_argument('--version', choices=('v121', 'v143'), default='v121')
|
||||
ap.add_argument('--romfs', type=Path, help='default build/extracted/<version>/romfs')
|
||||
ap.add_argument('--out', type=Path, help='default build/<version>/romfs-patches')
|
||||
args = ap.parse_args()
|
||||
args.romfs = args.romfs or root / 'build/extracted' / args.version / 'romfs'
|
||||
args.out = args.out or root / 'build' / args.version / 'romfs-patches'
|
||||
index = json.loads((args.romfs / 'index.json').read_text())
|
||||
assert index['status'] == 'pass'
|
||||
files = {f['path']: f for f in index['files']}
|
||||
@@ -73,7 +77,8 @@ def flvi_keys(data, base):
|
||||
return out
|
||||
|
||||
|
||||
ARCHIVE = 'UI/LayoutArchive/Common.Product.110.Nin_NX_NVN.blarc.zs'
|
||||
COMMON = {'v121': '110', 'v143': '140'} # the version in the name of each game's Common archive
|
||||
ARCHIVE = f'UI/LayoutArchive/Common.Product.{COMMON[args.version]}.Nin_NX_NVN.blarc.zs'
|
||||
MEMBERS = {
|
||||
'anim/ShortCutBG_00_In.bflan': ('C_CaptureBG_00', 'C_CaptureClearBG_00', 'P_CaptureClearBG_00'),
|
||||
'anim/AutoBuilder_00_In.bflan': ('C_CaptureBG_00',),
|
||||
|
||||
Reference in new issue
Block a user