From a222d02fbfccca0d709d26530ce03da9cee02b60 Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Wed, 21 Oct 2026 16:49:54 +0200 Subject: [PATCH] port remaining stale rr us hooks --- .../WiiCompiled.Setup.Windows/Installation.cs | 2 + .../WiiCompiled.Setup.Windows/SelfTests.cs | 6 +- translator/src/Translator.Cli/Program.cs | 6 +- .../Mods/Mkwii/RetroRewindRegionPatches.cs | 76 ++++++------ .../RetroRewindRegionPatchesTests.cs | 115 ++++++++++-------- 5 files changed, 111 insertions(+), 94 deletions(-) diff --git a/Launcher/WiiCompiled.Setup.Windows/Installation.cs b/Launcher/WiiCompiled.Setup.Windows/Installation.cs index 3b0c1af..d1e7040 100644 --- a/Launcher/WiiCompiled.Setup.Windows/Installation.cs +++ b/Launcher/WiiCompiled.Setup.Windows/Installation.cs @@ -131,6 +131,8 @@ internal sealed class Installation Workspace + "LocalBuild.ps1", Workspace + "NativeBuildFlags.ps1", Workspace + @"projects\mkwii\recomp.yml", + Workspace + @"projects\mkwii-ntsc-u\recomp.yml", + Workspace + @"projects\mkwii-ntsc-j\recomp.yml", Workspace + @"runtime\CMakeLists.txt", Workspace + @"runtime\assets\dsp\dsp_coef.bin" ]; diff --git a/Launcher/WiiCompiled.Setup.Windows/SelfTests.cs b/Launcher/WiiCompiled.Setup.Windows/SelfTests.cs index c751841..9536687 100644 --- a/Launcher/WiiCompiled.Setup.Windows/SelfTests.cs +++ b/Launcher/WiiCompiled.Setup.Windows/SelfTests.cs @@ -1446,7 +1446,8 @@ internal static class SelfTests Directory.CreateDirectory(Path.Combine(root, "Toolkit", "CMake", "bin")); Directory.CreateDirectory(Path.Combine(root, "Toolkit", "Ninja")); Directory.CreateDirectory(Path.Combine(root, "Toolkit", "llvm-mingw", "bin")); - Directory.CreateDirectory(Path.Combine(root, "BuildWorkspace", "projects", "mkwii")); + foreach (var project in new[] { "mkwii", "mkwii-ntsc-u", "mkwii-ntsc-j" }) + Directory.CreateDirectory(Path.Combine(root, "BuildWorkspace", "projects", project)); Directory.CreateDirectory(Path.Combine(root, "BuildWorkspace", "runtime", "src")); Directory.CreateDirectory(Path.Combine(root, "BuildWorkspace", "runtime", "assets", "wii", "shared2", "wc24")); @@ -1473,7 +1474,8 @@ internal static class SelfTests File.WriteAllText(Path.Combine(root, "Toolkit", "nodtool.exe"), "tool"); File.WriteAllText(Path.Combine(root, "BuildWorkspace", "LocalBuild.ps1"), "# build"); File.WriteAllText(Path.Combine(root, "BuildWorkspace", "NativeBuildFlags.ps1"), "# flags"); - File.WriteAllText(Path.Combine(root, "BuildWorkspace", "projects", "mkwii", "recomp.yml"), "profiles: {}"); + foreach (var project in new[] { "mkwii", "mkwii-ntsc-u", "mkwii-ntsc-j" }) + File.WriteAllText(Path.Combine(root, "BuildWorkspace", "projects", project, "recomp.yml"), "profiles: {}"); File.WriteAllText(Path.Combine(root, "BuildWorkspace", "runtime", "CMakeLists.txt"), "cmake_minimum_required(VERSION 3.20)"); File.WriteAllText(Path.Combine(root, "BuildWorkspace", "runtime", "src", "main.cpp"), "int main();"); File.WriteAllText(Path.Combine(root, "BuildWorkspace", "runtime", "assets", "wii", "shared2", "wc24", diff --git a/translator/src/Translator.Cli/Program.cs b/translator/src/Translator.Cli/Program.cs index 793e7e7..85224e9 100644 --- a/translator/src/Translator.Cli/Program.cs +++ b/translator/src/Translator.Cli/Program.cs @@ -1782,9 +1782,11 @@ int RunTranslateModCore(string[] argsTail, string? outputDirectoryOverride) var selected = pul.SelectRegion(region); if (string.Equals(profile?.Name, "retro-rewind", StringComparison.OrdinalIgnoreCase)) { - var corrected = RetroRewindRegionPatches.CorrectUsVoiceHook(selected, project?.Identity.GameId, image.Value); + var corrected = pul.IsCombined && !string.Equals(region, "P", StringComparison.OrdinalIgnoreCase) + ? RetroRewindRegionPatches.CorrectUnportedUsHooks(selected, pul.SelectRegion("P"), project?.Identity.GameId, image.Value) + : selected; if (!ReferenceEquals(corrected, selected)) - Console.WriteLine("[translator] Corrected Retro Rewind US voice vtable hook: 0x808DBCD8 -> 0x808D7580."); + Console.WriteLine("[translator] Moved Retro Rewind's unported PAL vtable hooks to their NTSC-U slots."); var esrb = RetroRewindRegionPatches.ApplyUsEsrbSkip(corrected, project?.Identity.GameId, image.Value); if (!ReferenceEquals(esrb, corrected)) Console.WriteLine("[translator] Applied Retro Rewind's NTSC-U ESRB skip statically at 0x80604094."); diff --git a/translator/src/Translator.Core/Mods/Mkwii/RetroRewindRegionPatches.cs b/translator/src/Translator.Core/Mods/Mkwii/RetroRewindRegionPatches.cs index 10468a2..04f7572 100644 --- a/translator/src/Translator.Core/Mods/Mkwii/RetroRewindRegionPatches.cs +++ b/translator/src/Translator.Core/Mods/Mkwii/RetroRewindRegionPatches.cs @@ -6,52 +6,54 @@ namespace Translator.Core.Mods.Mkwii; public static class RetroRewindRegionPatches { - private const uint PalVoiceHook = 0x808DBCD8; - private const uint UsVoiceHook = 0x808D7580; + private const uint PalRelStart = 0x805102E0; - // CustomCharacterVoice.cpp's HoldRosalinaLumaSound pointer retains its PAL address - // in the shipped US chunk. The clean US REL's relocated vtable identifies the slot; - // these surrounding entries distinguish it from other tables using HoldSoundLimited. - private static readonly (uint Address, uint Target)[] UsVoiceTable = + // Hooks the pack writes at PAL vtable slots that its NTSC-U port map leaves unported: + // PAL slot, NTSC-U slot, and the function that NTSC-U slot holds in the clean StaticR.rel. + private static readonly (uint Pal, uint Us, uint UsTarget)[] UnportedUsHooks = [ - (0x808D7540, 0x806FC03C), - (0x808D7544, 0x806FBC94), - (0x808D7570, 0x806FBC3C), - (0x808D7574, 0x806FBC34), - (0x808D7578, 0x806FA3A8), - (0x808D757C, 0x806FAA04), - (UsVoiceHook, 0x806FAB44), - (0x808D7584, 0x806FAD58), - (0x808D7588, 0x806FA7B0), - (0x808D758C, 0x806FA83C), - (0x808D7590, 0x806FA8D0), - (0x808D75BC, 0x808611E8), - (0x808D75C0, 0x806FA824) + (0x808B4D48, 0x808B0698, 0x80563BE8), // Kart::Damage::ApplyBananaDamage + (0x808B4DFC, 0x808B074C, 0x80563CD8), // Kart::Damage::ApplyZapperDamage + (0x808B4E14, 0x808B0764, 0x80563CCC), // Kart::Damage::ApplyShockDamage + (0x808CB02C, 0x808C5D24, 0x8072E068), // AI::EnemyRouteController::Update + (0x808DBCD8, 0x808D7580, 0x806FAB44), // Audio::RaceActor::HoldSoundLimited ]; - public static KamekChunk CorrectUsVoiceHook(KamekChunk chunk, string? gameId, ProgramImage image) + /// + /// Moves the NTSC-U chunk's unported PAL vtable hooks to their NTSC-U slots. A StaticR.rel + /// address the NTSC-U chunk shares with the PAL chunk is always unported, so an unknown one fails. + /// + public static KamekChunk CorrectUnportedUsHooks(KamekChunk usChunk, KamekChunk palChunk, string? gameId, + ProgramImage image) { if (!string.Equals(gameId, "RMCE01", StringComparison.OrdinalIgnoreCase)) - return chunk; + return usChunk; + if (usChunk.Commands.Count != palChunk.Commands.Count) + throw new InvalidDataException("Retro Rewind's PAL and NTSC-U chunks have different command lists."); - var matching = chunk.Commands.Where(c => c.AddressIsAbsolute && c.Address == PalVoiceHook).ToArray(); - if (matching.Length == 0) - return chunk; - if (matching.Length != 1 || matching[0].Id != KamekCommandId.Addr32 || - matching[0].Arguments.Count != 1 || matching[0].Arguments[0] >= chunk.CodeSize || - chunk.Commands.Any(c => c.AddressIsAbsolute && c.Address == UsVoiceHook)) - throw new InvalidDataException("Unexpected Retro Rewind US voice hook commands; cannot correct the PAL address."); - - foreach (var (address, target) in UsVoiceTable) + var commands = usChunk.Commands.ToArray(); + var changed = false; + for (var i = 0; i < commands.Length; i++) { - if (!image.Contains(address, 4) || - BinaryPrimitives.ReadUInt32BigEndian(image.Memory.AsSpan(image.GetOffset(address, 4), 4)) != target) - throw new InvalidDataException($"Retro Rewind US voice vtable check failed at 0x{address:X8}."); + var us = commands[i]; + var pal = palChunk.Commands[i]; + if (!us.AddressIsAbsolute || !pal.AddressIsAbsolute || us.Address != pal.Address || us.Address < PalRelStart) + continue; + var hook = UnportedUsHooks.FirstOrDefault(h => h.Pal == us.Address); + if (hook.Pal == 0) + throw new InvalidDataException($"Retro Rewind's NTSC-U chunk patches unported PAL address 0x{us.Address:X8}."); + if (us.Id != KamekCommandId.Addr32 || us.Arguments.Count != 1 || us.Arguments[0] >= usChunk.CodeSize) + throw new InvalidDataException($"Unexpected Retro Rewind command at PAL address 0x{us.Address:X8}."); + if (!image.Contains(hook.Us, 4) || + BinaryPrimitives.ReadUInt32BigEndian(image.Memory.AsSpan(image.GetOffset(hook.Us, 4), 4)) != hook.UsTarget) + throw new InvalidDataException($"NTSC-U vtable slot check failed at 0x{hook.Us:X8}."); + commands[i] = us with { Address = hook.Us }; + changed = true; } - - return new KamekChunk(chunk.Index, chunk.FileOffset, chunk.BssSize, chunk.CodeSize, - chunk.CtorStart, chunk.CtorEnd, chunk.ChunkSize, chunk.CodeBlob, - chunk.Commands.Select(c => ReferenceEquals(c, matching[0]) ? c with { Address = UsVoiceHook } : c).ToArray()); + return changed + ? new KamekChunk(usChunk.Index, usChunk.FileOffset, usChunk.BssSize, usChunk.CodeSize, + usChunk.CtorStart, usChunk.CtorEnd, usChunk.ChunkSize, usChunk.CodeBlob, commands) + : usChunk; } private const uint EsrbBranchSite = 0x80604094; diff --git a/translator/tests/Translator.Tests/RetroRewindRegionPatchesTests.cs b/translator/tests/Translator.Tests/RetroRewindRegionPatchesTests.cs index 4289946..feb1ece 100644 --- a/translator/tests/Translator.Tests/RetroRewindRegionPatchesTests.cs +++ b/translator/tests/Translator.Tests/RetroRewindRegionPatchesTests.cs @@ -7,19 +7,31 @@ namespace Translator.Tests; public sealed class RetroRewindRegionPatchesTests { - [Fact] - public void CorrectsOnlyTheStaleUsPointerAndPreservesItsModuleTarget() - { - var hook = Command(KamekCommandId.Addr32, 0x808DBCD8); - var other = Command(KamekCommandId.Branch, 0x807B9F34); - var chunk = Chunk(hook, other); - var corrected = RetroRewindRegionPatches.CorrectUsVoiceHook(chunk, "RMCE01", VoiceImage()); + private static readonly (uint Pal, uint Us, uint Target)[] UnportedHooks = + [ + (0x808B4D48, 0x808B0698, 0x80563BE8), + (0x808B4DFC, 0x808B074C, 0x80563CD8), + (0x808B4E14, 0x808B0764, 0x80563CCC), + (0x808CB02C, 0x808C5D24, 0x8072E068), + (0x808DBCD8, 0x808D7580, 0x806FAB44), + ]; - Assert.Equal(0x808D7580u, corrected.Commands[0].Address); - Assert.Equal(hook.Arguments, corrected.Commands[0].Arguments); - Assert.Same(other, corrected.Commands[1]); - Assert.Equal(0x808DBCD8u, chunk.Commands[0].Address); - Assert.Same(chunk.CodeBlob, corrected.CodeBlob); + [Fact] + public void MovesEveryUnportedHookToItsUsSlotAndKeepsPortedOnes() + { + var pal = Chunk([.. UnportedHooks.Select(h => Command(KamekCommandId.Addr32, h.Pal)), + Command(KamekCommandId.Branch, 0x807C8994)]); + var us = Chunk([.. UnportedHooks.Select(h => Command(KamekCommandId.Addr32, h.Pal)), + Command(KamekCommandId.Branch, 0x807B9F34)]); + var corrected = RetroRewindRegionPatches.CorrectUnportedUsHooks(us, pal, "RMCE01", SlotImage()); + + for (var i = 0; i < UnportedHooks.Length; i++) + { + Assert.Equal(UnportedHooks[i].Us, corrected.Commands[i].Address); + Assert.Equal(us.Commands[i].Arguments, corrected.Commands[i].Arguments); + } + Assert.Same(us.Commands[^1], corrected.Commands[^1]); + Assert.Same(us.CodeBlob, corrected.CodeBlob); } [Theory] @@ -28,44 +40,46 @@ public sealed class RetroRewindRegionPatchesTests public void LeavesOtherRegionsUntouched(string gameId) { var chunk = Chunk(Command(KamekCommandId.Addr32, 0x808DBCD8)); - Assert.Same(chunk, RetroRewindRegionPatches.CorrectUsVoiceHook(chunk, gameId, VoiceImage())); + Assert.Same(chunk, RetroRewindRegionPatches.CorrectUnportedUsHooks(chunk, chunk, gameId, SlotImage())); } [Fact] - public void AcceptsAnAlreadyCorrectedPackWithoutRewritingIt() + public void AcceptsAPackWhosePortMapCoversTheHooks() { - var chunk = Chunk(Command(KamekCommandId.Addr32, 0x808D7580)); - Assert.Same(chunk, RetroRewindRegionPatches.CorrectUsVoiceHook(chunk, "RMCE01", VoiceImage())); - } - - [Theory] - [InlineData(0x808D7540u)] - [InlineData(0x808D7580u)] - [InlineData(0x808D75C0u)] - public void RejectsTheCorrectionWhenTheVtableDoesNotMatch(uint address) - { - var image = VoiceImage(); - image.Memory[image.GetOffset(address)] ^= 1; - Assert.Throws(() => RetroRewindRegionPatches.CorrectUsVoiceHook( - Chunk(Command(KamekCommandId.Addr32, 0x808DBCD8)), "RMCE01", image)); - } - - [Theory] - [InlineData(KamekCommandId.Write32)] - [InlineData(KamekCommandId.Branch)] - public void RejectsAnUnexpectedCommandAtThePalAddress(KamekCommandId id) - { - Assert.Throws(() => RetroRewindRegionPatches.CorrectUsVoiceHook( - Chunk(Command(id, 0x808DBCD8)), "RMCE01", VoiceImage())); + var pal = Chunk(Command(KamekCommandId.Addr32, 0x808DBCD8)); + var us = Chunk(Command(KamekCommandId.Addr32, 0x808D7580)); + Assert.Same(us, RetroRewindRegionPatches.CorrectUnportedUsHooks(us, pal, "RMCE01", SlotImage())); } [Fact] - public void RejectsDuplicateOrConflictingHooks() + public void RejectsAnUnknownUnportedRelAddress() { - var hook = Command(KamekCommandId.Addr32, 0x808DBCD8); - foreach (var other in new[] { hook, Command(KamekCommandId.Addr32, 0x808D7580) }) - Assert.Throws(() => RetroRewindRegionPatches.CorrectUsVoiceHook( - Chunk(hook, other), "RMCE01", VoiceImage())); + var chunk = Chunk(Command(KamekCommandId.Addr32, 0x808B4D60)); + Assert.Throws(() => + RetroRewindRegionPatches.CorrectUnportedUsHooks(chunk, chunk, "RMCE01", SlotImage())); + } + + [Fact] + public void IgnoresSharedAddressesOutsideStaticR() + { + var chunk = Chunk(Command(KamekCommandId.Write32, 0x80001A80)); + Assert.Same(chunk, RetroRewindRegionPatches.CorrectUnportedUsHooks(chunk, chunk, "RMCE01", SlotImage())); + } + + [Fact] + public void RejectsAMismatchedSlotOrCommand() + { + var hook = Chunk(Command(KamekCommandId.Addr32, 0x808DBCD8)); + var image = SlotImage(); + image.Memory[image.GetOffset(0x808D7580)] ^= 1; + Assert.Throws(() => + RetroRewindRegionPatches.CorrectUnportedUsHooks(hook, hook, "RMCE01", image)); + var write = Chunk(Command(KamekCommandId.Write32, 0x808DBCD8)); + Assert.Throws(() => + RetroRewindRegionPatches.CorrectUnportedUsHooks(write, write, "RMCE01", SlotImage())); + Assert.Throws(() => RetroRewindRegionPatches.CorrectUnportedUsHooks( + hook, Chunk(Command(KamekCommandId.Addr32, 0x808DBCD8), Command(KamekCommandId.Branch, 0x80000100)), + "RMCE01", SlotImage())); } private static readonly uint[] EsrbHook = @@ -136,18 +150,13 @@ public sealed class RetroRewindRegionPatchesTests private static KamekChunk Chunk(params KamekCommand[] commands) => new(1, 0, 0x20, 0x200, 0, 0, 0x220, new byte[0x200], commands); - private static ProgramImage VoiceImage() + private static ProgramImage SlotImage() { - var memory = new byte[0x84]; - uint[] entries = - [ - 0x806FC03C, 0x806FBC94, 0x806FBC3C, 0x806FBC34, 0x806FA3A8, 0x806FAA04, - 0x806FAB44, 0x806FAD58, 0x806FA7B0, 0x806FA83C, 0x806FA8D0, 0x808611E8, 0x806FA824 - ]; - int[] offsets = [0, 4, 0x30, 0x34, 0x38, 0x3C, 0x40, 0x44, 0x48, 0x4C, 0x50, 0x7C, 0x80]; - for (var i = 0; i < entries.Length; i++) - BinaryPrimitives.WriteUInt32BigEndian(memory.AsSpan(offsets[i], 4), entries[i]); - var range = AddressRange.FromStartAndSize(0x808D7540, (uint)memory.Length); + const uint start = 0x808B0000; + var memory = new byte[0x28000]; + foreach (var (_, us, target) in UnportedHooks) + BinaryPrimitives.WriteUInt32BigEndian(memory.AsSpan((int)(us - start), 4), target); + var range = AddressRange.FromStartAndSize(start, (uint)memory.Length); return new ProgramImage(memory, range, default, range, "fixture", range.Start); } }