From 407f8a7190832745069d71f7e60ab019467c71a2 Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:41:58 +0200 Subject: [PATCH] https payload (#198) --- .gitattributes | 1 + Launcher/Test-PinnedFacts.ps1 | 8 +- .../RetroWfcPayload.cs | 2 +- Launcher/macos/setup.command | 2 +- projects/mkwii/recomp.yml | 2 +- .../RetroWfcPayloadLoweringTests.cs | 78 ++++++++++++++++++ .../binary/payload.RMCPD00.bin | Bin 0 -> 28992 bytes .../Translator.Tests/Translator.Tests.csproj | 5 ++ 8 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 translator/tests/Translator.Tests/TestAssets/RetroWfcPayload/binary/payload.RMCPD00.bin diff --git a/.gitattributes b/.gitattributes index 903e5cd..6344b3b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,3 +3,4 @@ # Patch files must stay LF: git apply matches context bytes against LF upstream sources *.patch -text +translator/tests/Translator.Tests/TestAssets/**/*.bin binary diff --git a/Launcher/Test-PinnedFacts.ps1 b/Launcher/Test-PinnedFacts.ps1 index bd65c84..d7b14bd 100644 --- a/Launcher/Test-PinnedFacts.ps1 +++ b/Launcher/Test-PinnedFacts.ps1 @@ -1,6 +1,6 @@ # Fails the release build when a fact duplicated across the repo stops agreeing with the copy # that owns it (recomp.yml). Scripts read pinned facts through Get-MkwProjectPins, but three -# consumers can't read YAML (the C++ runtime header, the C# constants, hand-written lists on +# consumers can't read YAML (the C++ runtime header, the C# constants, shell scripts, and hand-written lists on # both sides of the C#/PowerShell boundary), so those are checked here instead. [CmdletBinding()] param([string]$RepositoryRoot) @@ -58,6 +58,12 @@ $hostUri = Get-CapturedValue $retroWfcPayload 'CurrentRetroWfcPayloadUri\s*=\s*" if ($hostUri -cne $pins.RetroWfcPayloadUri) { Add-Failure "InputValidation.CurrentRetroWfcPayloadUri is '$hostUri' but recomp.yml pins '$($pins.RetroWfcPayloadUri)'." } +$macosSetup = Read-SourceFile (Join-Path $launcher 'macos\setup.command') 'macOS setup.command' +$macosUri = Get-CapturedValue $macosSetup "'([^']*/api/wfc/payload\?g=RMCPD00)'" ` + 'The macOS Retro-WFC endpoint' +if ($macosUri -cne $pins.RetroWfcPayloadUri) { + Add-Failure "macOS setup.command downloads '$macosUri' but recomp.yml pins '$($pins.RetroWfcPayloadUri)'." +} # --- The game identity: the manifest carries it, but the host also compiles a fallback for a # --- manifest that predates the field, and that fallback decides which disc is accepted. diff --git a/Launcher/WiiCompiled.Setup.Common/RetroWfcPayload.cs b/Launcher/WiiCompiled.Setup.Common/RetroWfcPayload.cs index 22d1c23..258fb85 100644 --- a/Launcher/WiiCompiled.Setup.Common/RetroWfcPayload.cs +++ b/Launcher/WiiCompiled.Setup.Common/RetroWfcPayload.cs @@ -24,7 +24,7 @@ public static class RetroWfcPayload private static readonly TimeSpan RetroWfcDownloadTimeout = TimeSpan.FromSeconds(30); private static readonly TimeSpan RetroWfcRetryDelay = TimeSpan.FromSeconds(1); - public const string CurrentRetroWfcPayloadUri = "http://nas.play.rwfc.net/payload?g=RMCPD00"; + public const string CurrentRetroWfcPayloadUri = "https://rwfc.net/api/wfc/payload?g=RMCPD00"; private static readonly string RetroWfcOfflinePayloadFile = Path.Combine("binary", "payload.RMCPD00.bin"); diff --git a/Launcher/macos/setup.command b/Launcher/macos/setup.command index 8676e7a..09c2915 100755 --- a/Launcher/macos/setup.command +++ b/Launcher/macos/setup.command @@ -90,7 +90,7 @@ if [[ -n "$retro_dir" ]]; then trap 'rm -rf "$payload_stage"' EXIT /usr/bin/curl --fail --silent --show-error --connect-timeout 10 --max-time 30 \ --retry 1 --output "$temporary_payload" \ - 'http://nas.play.rwfc.net/payload?g=RMCPD00' || fail 'could not download the Retro-WFC payload needed for online play' + 'https://rwfc.net/api/wfc/payload?g=RMCPD00' || fail 'could not download the Retro-WFC payload needed for online play' "$translator" validate-retro-wfc-payload --directory "$payload_stage" || \ fail 'downloaded Retro-WFC payload failed signature validation' mkdir -p "$retro_wfc_dir/binary" diff --git a/projects/mkwii/recomp.yml b/projects/mkwii/recomp.yml index bd8f727..20056ad 100644 --- a/projects/mkwii/recomp.yml +++ b/projects/mkwii/recomp.yml @@ -58,7 +58,7 @@ profiles: module_link_base: 0x803992E0 output: build/mods/retro_rewind_full_cpp enable_retro_wfc: true - retro_wfc_payload: http://nas.play.rwfc.net/payload?g=RMCPD00 + retro_wfc_payload: https://rwfc.net/api/wfc/payload?g=RMCPD00 retro_wfc_legacy_bootstrap_hook: 0x800ED6E8 riivolution: xml: xml/RetroRewind6.xml diff --git a/translator/tests/Translator.Tests/RetroWfcPayloadLoweringTests.cs b/translator/tests/Translator.Tests/RetroWfcPayloadLoweringTests.cs index 4a11b0d..e5fe310 100644 --- a/translator/tests/Translator.Tests/RetroWfcPayloadLoweringTests.cs +++ b/translator/tests/Translator.Tests/RetroWfcPayloadLoweringTests.cs @@ -35,6 +35,39 @@ public class RetroWfcPayloadLoweringTests Assert.Equal("moduleFunction", pointer.TargetKind); } + [Fact] + public void ProductionPayloadValidatesAndTranslatesEverySupportedPatch() + { + var payloadRoot = Path.Combine( + AppContext.BaseDirectory, + "TestAssets", + "RetroWfcPayload"); + WiiCompiled.Setup.Common.RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(payloadRoot); + var payloadPath = Path.Combine( + payloadRoot, + "binary", + "payload.RMCPD00.bin"); + var payload = File.ReadAllBytes(payloadPath); + + var result = RetroWfcPayload.Parse( + payload, + ProductionPayloadManifest(), + 0x81800000u, + 0x00200000u, + "TestAssets/RetroWfcPayload/binary/payload.RMCPD00.bin"); + + Assert.Equal("RMCPD00", result.Summary.Game); + Assert.Equal(payload.Length, result.Summary.PayloadImageSize); + Assert.True(result.LoweringPlan.IsPlannable); + Assert.Empty(result.LoweringPlan.Issues); + Assert.NotEmpty(result.LoweringPlan.StaticBytePatches); + Assert.NotEmpty(result.LoweringPlan.ExecutableHooks); + Assert.NotEmpty(result.LoweringPlan.StaticPointers); + Assert.All(result.LoweringPlan.ExecutableHooks, hook => Assert.NotNull(hook.TargetAddress)); + Assert.All(result.LoweringPlan.StaticPointers, pointer => Assert.NotNull(pointer.TargetAddress)); + Assert.NotEmpty(result.Summary.InitializationCallbacks); + } + private static BaseManifest TestManifest() => new( "test", @@ -52,6 +85,51 @@ public class RetroWfcPayloadLoweringTests ], "ranges.json"); + // The payload parser needs the base image's address classes and containing + // function ranges to prove every patch can be lowered. A single synthetic + // executable and writable ranges are sufficient here: the assertions above + // test the real production payload without checking proprietary game bytes + // into CI. The split also proves pointer patches lower as data writes. + private static BaseManifest ProductionPayloadManifest() => + new( + "test", + 1, + "RMCP01", + "P", + "", + 0, + [ + new BaseSectionMetadata( + ".synthetic-text", + "synthetic.dol", + 0x80000000u, + 0x80800000u, + true, + false, + "synthetic_text.bin", + 0), + new BaseSectionMetadata( + ".synthetic-data", + "synthetic.dol", + 0x80800000u, + 0x81000000u, + false, + true, + "synthetic_data.bin", + 0) + ], + [ + new BaseFunctionRangeMetadata( + 0x80000000u, + 0x80800000u, + "synthetic_base", + ".synthetic-text", + 0, + "test", + ["Executable"]) + ], + "ranges.json"); + private static byte[] BuildSharedPayloadFixture() { var payload = new byte[0x240]; diff --git a/translator/tests/Translator.Tests/TestAssets/RetroWfcPayload/binary/payload.RMCPD00.bin b/translator/tests/Translator.Tests/TestAssets/RetroWfcPayload/binary/payload.RMCPD00.bin new file mode 100644 index 0000000000000000000000000000000000000000..fd7ecd8b8a3e2e01ce337449caf800649580863e GIT binary patch literal 28992 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 VYSKS{|C0j$q`*HZ@DEbp{{u$bcK-kX literal 0 HcmV?d00001 diff --git a/translator/tests/Translator.Tests/Translator.Tests.csproj b/translator/tests/Translator.Tests/Translator.Tests.csproj index 4e37ccd..dccdafe 100644 --- a/translator/tests/Translator.Tests/Translator.Tests.csproj +++ b/translator/tests/Translator.Tests/Translator.Tests.csproj @@ -26,6 +26,11 @@ + + + + +