mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-10 09:11:52 -04:00
178 lines
6.7 KiB
C#
178 lines
6.7 KiB
C#
using System.Buffers.Binary;
|
|
using System.Text;
|
|
using Translator.Core.Mods;
|
|
using Translator.Core.Mods.Mkwii;
|
|
using Xunit;
|
|
|
|
namespace Translator.Tests;
|
|
|
|
public class RetroWfcPayloadLoweringTests
|
|
{
|
|
[Fact]
|
|
public void SharedPayloadDescriptorDrivesRelocationAndStaticLowering()
|
|
{
|
|
const uint moduleBase = 0x81700000u;
|
|
const uint payloadModuleOffset = 0x2000u;
|
|
var payload = BuildSharedPayloadFixture();
|
|
|
|
var result = RetroWfcPayload.Parse(payload, TestManifest(), moduleBase, payloadModuleOffset, "fixture");
|
|
|
|
Assert.Equal("RMCPD00", result.Summary.Game);
|
|
Assert.Equal(3u, result.Summary.FormatVersion);
|
|
Assert.Equal(moduleBase + payloadModuleOffset + 0x1E4u, result.Summary.InitializationTargetAddress);
|
|
Assert.Equal(moduleBase + payloadModuleOffset + 0x210u, ReadU32(result.RelocatedImage, 0x1B4));
|
|
Assert.Equal(moduleBase + payloadModuleOffset + 0x214u, ReadU32(result.RelocatedImage, 0x220));
|
|
|
|
var callback = Assert.Single(result.Summary.InitializationCallbacks);
|
|
Assert.Equal(moduleBase + payloadModuleOffset + 0x208u, callback.TargetAddress);
|
|
|
|
var hook = Assert.Single(result.LoweringPlan.ExecutableHooks);
|
|
Assert.Equal(moduleBase + payloadModuleOffset + 0x200u, hook.TargetAddress);
|
|
Assert.Equal("moduleFunction", hook.TargetKind);
|
|
|
|
var pointer = Assert.Single(result.LoweringPlan.StaticPointers);
|
|
Assert.Equal(moduleBase + payloadModuleOffset + 0x204u, pointer.TargetAddress);
|
|
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",
|
|
1,
|
|
"RMCP01",
|
|
"P",
|
|
"",
|
|
0,
|
|
[
|
|
new BaseSectionMetadata(".text", "main.dol", 0x80001000u, 0x80001020u, true, false, "base_text.bin", 0),
|
|
new BaseSectionMetadata(".data", "main.dol", 0x80002000u, 0x80002020u, false, true, "base_data.bin", 0)
|
|
],
|
|
[
|
|
new BaseFunctionRangeMetadata(0x80001000u, 0x80001020u, "func_80001000", ".text", 0, "test", ["Executable"])
|
|
],
|
|
"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];
|
|
Encoding.ASCII.GetBytes("WWFC/Payload").CopyTo(payload, 0);
|
|
WriteU32(payload, 0x0C, (uint)payload.Length);
|
|
WriteU32(payload, 0x130, 3);
|
|
WriteU32(payload, 0x134, 1);
|
|
Encoding.ASCII.GetBytes("RMCPD00").CopyTo(payload, 0x138);
|
|
WriteU32(payload, 0x144, 0x00010000);
|
|
WriteU32(payload, 0x148, 0x1B4);
|
|
WriteU32(payload, 0x14C, 0x1B8);
|
|
WriteU32(payload, 0x150, 0x1B8);
|
|
WriteU32(payload, 0x154, 0x1BC);
|
|
WriteU32(payload, 0x158, 0x1BC);
|
|
WriteU32(payload, 0x15C, 0x1DC);
|
|
WriteU32(payload, 0x1A0, 0x1A4);
|
|
|
|
WriteU32(payload, 0x1A4, 1);
|
|
WriteU32(payload, 0x1A8, 0x1E4);
|
|
WriteU32(payload, 0x1AC, 0x1E4);
|
|
WriteU32(payload, 0x1B0, 0x230);
|
|
|
|
WriteU32(payload, 0x1B4, 0x210);
|
|
WriteU32(payload, 0x1B8, 0x220);
|
|
|
|
payload[0x1BC + 1] = 3;
|
|
WriteU32(payload, 0x1BC + 4, 0x80001000);
|
|
WriteU32(payload, 0x1BC + 8, 0x200);
|
|
|
|
payload[0x1CC + 1] = 6;
|
|
WriteU32(payload, 0x1CC + 4, 0x80002000);
|
|
WriteU32(payload, 0x1CC + 8, 0x204);
|
|
|
|
WriteU32(payload, 0x1DC, 0xFFFFFFFF);
|
|
WriteU32(payload, 0x1E0, 0x208);
|
|
WriteU32(payload, 0x220, 0x214);
|
|
return payload;
|
|
}
|
|
|
|
private static uint ReadU32(byte[] image, int offset) =>
|
|
BinaryPrimitives.ReadUInt32BigEndian(image.AsSpan(offset, 4));
|
|
|
|
private static void WriteU32(byte[] image, int offset, uint value) =>
|
|
BinaryPrimitives.WriteUInt32BigEndian(image.AsSpan(offset, 4), value);
|
|
}
|