Compare commits

..

1 Commits

Author SHA1 Message Date
patchzyy 3186e5f511 fix native index packaging and aurora fingerprint 2026-10-18 18:45:13 +02:00
6 changed files with 23 additions and 489 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<!-- The one product version. Release tags must match it. -->
<Version>0.2.36</Version>
<Version>0.2.34</Version>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
</PropertyGroup>
</Project>
@@ -372,9 +372,6 @@ static bool HIDAPI_DriverWii_InitDevice(SDL_HIDAPI_Device *device)
[==[ x = ((data->rgucAccelerometer[0] << 2) | ((data->rgucBaseButtons[0] >> 5) & 0x03));
y = ((data->rgucAccelerometer[1] << 2) | ((data->rgucBaseButtons[1] >> 4) & 0x02));
z = ((data->rgucAccelerometer[2] << 2) | ((data->rgucBaseButtons[1] >> 5) & 0x02));
if (x == 0 && y == 0 && z == 0) {
return;
}
values[0] = -(((float)x - ctx->m_fAccelZeroG[0]) / ctx->m_fAccelPerG[0]) * SDL_STANDARD_GRAVITY;
values[1] = (((float)z - ctx->m_fAccelZeroG[2]) / ctx->m_fAccelPerG[2]) * SDL_STANDARD_GRAVITY;
+4 -12
View File
@@ -93,20 +93,12 @@ extern "C" int32_t WUDGetStatus_HLE()
}
PPC_NATIVE_OVERRIDE(801CDB84, WUDGetStatus_HLE, int32_t, (), ());
// WPADGetDataFormat: the format KPAD runs the attached extension in (WPAD_FMT_*_ACC_DPD).
// WPADClampStick keys off it. Can't reuse the translated SDK implementation: it dereferences
// Bluetooth control blocks that HLE'd WPADInit never constructs.
// WPADGetDataFormat reads the per-channel format set by WPADSetDataFormat. Can't reuse the
// translated SDK implementation: it dereferences Bluetooth control blocks that HLE'd WPADInit
// never constructs.
extern "C" int32_t WPADGetDataFormat_HLE(uint32_t chan)
{
const int32_t status = g_state.contract.GetDataFormat(chan);
if (status < 0) {
return status;
}
switch (WiiRemoteInput::EffectiveKind(chan)) {
case WiiRemoteInput::Kind::RemoteWithNunchuk: return 5;
case WiiRemoteInput::Kind::RemoteWithClassic: return 8;
default: return 2;
}
return g_state.contract.GetDataFormat(chan);
}
PPC_NATIVE_OVERRIDE(801C0B54, WPADGetDataFormat_HLE, int32_t, (uint32_t chan), (chan));
+15 -11
View File
@@ -144,8 +144,8 @@ struct LastAcc {
std::array<LastAcc, PAD_MAX_CONTROLLERS> g_lastAcc{};
std::array<LastAcc, PAD_MAX_CONTROLLERS> g_lastNunchukAcc{};
// A zeroed remote report decodes to minus its zero-g point, at least this far out on every axis.
constexpr float kZeroReportMinG = 4.0f;
// Any axis beyond this is not a reading the remote's +-3 g sensor can produce.
constexpr float kMaxPlausibleRemoteG = 4.0f;
// Case-sensitive substring test that tolerates a null name.
bool NameContains(const char* name, const char* needle) {
@@ -250,11 +250,10 @@ bool ReadSdlAccel(SDL_Gamepad* gamepad, SDL_SensorType sensor, float* sdl) {
// True for a sample that cannot have come from the sensor. Over Bluetooth on
// Windows the remote delivers, a few times a minute, a report whose
// accelerometer bytes are all zero; SDL decodes that as (+a, -b, -c) g with
// a, b, c the zero-g point over the units per g of each axis: about 5 g for the
// remote (factory calibration) and exactly 2.56 g for the Nunchuk (200 units/g).
// Handed to the game as is, one such frame is a full-lock steer plus a 9 g
// "shake". Single axes past 4 g are real readings of a hard shake. `g` is the
// accelerometer bytes are all zero; SDL decodes that as -0x200 on every axis,
// i.e. (+5.12, -5.12, -5.12) g for the remote (100 units/g) and
// (+2.56, -2.56, -2.56) g for the Nunchuk (200 units/g). Handed to the game as
// is, one such frame is a full-lock steer plus a 9 g "shake". `g` is the
// uncorrected SDL sample.
bool IsGlitchedSample(SDL_SensorType sensor, const float* g) {
// An exact zero vector is SDL's sensor buffer before the first report, not
@@ -262,11 +261,16 @@ bool IsGlitchedSample(SDL_SensorType sensor, const float* g) {
if (g[0] == 0.0f && g[1] == 0.0f && g[2] == 0.0f) {
return true;
}
if (sensor == SDL_SENSOR_ACCEL) {
return g[0] > kZeroReportMinG && g[1] < -kZeroReportMinG && g[2] < -kZeroReportMinG;
const float zero = sensor == SDL_SENSOR_ACCEL ? 5.12f : 2.56f;
if (std::fabs(g[0] - zero) < 0.03f && std::fabs(g[1] + zero) < 0.03f && std::fabs(g[2] + zero) < 0.03f) {
return true;
}
constexpr float zero = 2.56f;
return std::fabs(g[0] - zero) < 0.03f && std::fabs(g[1] + zero) < 0.03f && std::fabs(g[2] + zero) < 0.03f;
if (sensor == SDL_SENSOR_ACCEL) {
for (int i = 0; i < 3; ++i) {
if (std::fabs(g[i]) > kMaxPlausibleRemoteG) return true;
}
}
return false;
}
// One accelerometer sample in g, SDL's sensor frame. The remote's own axes are
@@ -69,24 +69,9 @@ public static class ModFunctionDiscovery
// Kamek modules frequently register callbacks through static tables or lists that are
// walked indirectly by module startup code. Seed those native translations ahead of time.
// The Kamek blob is linked at offset zero, so a word inside it only holds a module address
// when a pointer relocation wrote one there; anything else is an instruction or data that
// happens to encode one. Images appended past the blob are not described by chunk commands.
var pointerRelocations = chunk.Commands
.Where(command => command.AddressIsRelative &&
command.Arguments.Count > 0 &&
command.Id is KamekCommandId.Addr32 or KamekCommandId.CondWritePointer)
.Select(command => (command.Address, KamekAddress.Resolve(command.Arguments[0], moduleGuestBase)))
.ToHashSet();
for (var offset = 0; offset + 4 <= relocatedModuleImage.Length; offset += 4)
{
var value = BinaryPrimitives.ReadUInt32BigEndian(relocatedModuleImage.AsSpan(offset, 4));
// Comparing the value also rejects conditional writes that were skipped.
if ((uint)offset < chunk.CodeSize && !pointerRelocations.Contains(((uint)offset, value)))
{
continue;
}
if (LooksLikeFunctionPointerTarget(value, moduleGuestBase, moduleImageEnd, relocatedModuleImage))
{
AddImageAddress(value, "module data pointer");
@@ -28,7 +28,7 @@ public sealed class ModFunctionDiscoveryTests
ctorEnd: 0,
chunkSize: (uint)(KamekChunk.HeaderSize + code.Length),
codeBlob: code,
commands: [PointerRelocation(0x40, 0x50)]);
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
@@ -368,7 +368,7 @@ public sealed class ModFunctionDiscoveryTests
ctorEnd: 0,
chunkSize: (uint)(code.Length + KamekChunk.HeaderSize),
codeBlob: code,
commands: [PointerRelocation(0x20, 0x100)]);
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
@@ -381,7 +381,7 @@ public sealed class ModFunctionDiscoveryTests
{
const uint moduleBase = 0x81200000u;
var relocatedImage = new byte[0x300];
WriteU32(relocatedImage, 0x280, moduleBase + 0x200); // appended pointer table
WriteU32(relocatedImage, 0x20, moduleBase + 0x200);
WriteU32(relocatedImage, 0x200, 0x38600001u); // li r3,1
WriteU32(relocatedImage, 0x204, 0x4E800020u); // blr
@@ -655,450 +655,6 @@ public sealed class ModFunctionDiscoveryTests
Assert.Equal("module leaf boundary scan", starts[moduleBase + 0x43004]);
}
[Fact]
public void DiscoversVtableOnlyTailCallFunctionNextToInstructionThatEncodesItsTail()
{
const uint moduleBase = 0x81840000u;
var code = new byte[0x10000];
// Virtual call whose lwz encodes the address of the tail branch below.
WriteU32(code, 0x0FF0, 0x9421FFF0u); // stwu r1,-0x10(r1)
WriteU32(code, 0x0FF4, 0x7C0802A6u); // mflr r0
WriteU32(code, 0x0FF8, 0x8061000Cu); // lwz r3,0xc(r1)
WriteU32(code, 0x0FFC, 0x8184BB50u); // lwz r12,-0x44b0(r4)
WriteU32(code, 0x1000, 0x7D8903A6u); // mtctr r12
WriteU32(code, 0x1004, 0x4E800421u); // bctrl
WriteU32(code, 0x1008, 0x38210010u); // addi r1,r1,0x10
WriteU32(code, 0x100C, 0x4E800020u); // blr
WriteU32(code, 0xBAF0, 0x9421FFF0u); // stwu r1,-0x10(r1)
WriteU32(code, 0xBAF4, 0x7C0802A6u); // mflr r0
WriteU32(code, 0xBAF8, 0x80010014u); // lwz r0,0x14(r1)
WriteU32(code, 0xBAFC, 0x7C0803A6u); // mtlr r0
WriteU32(code, 0xBB00, 0x38210010u); // addi r1,r1,0x10
WriteU32(code, 0xBB04, 0x4E800020u); // blr
// Frameless virtual reached only through the vtable; it ends in a tail call.
WriteU32(code, 0xBB08, 0x38000008u); // li r0,8
WriteU32(code, 0xBB0C, 0x3880FFFFu); // li r4,-1
WriteU32(code, 0xBB10, 0x90832AB4u); // stw r4,0x2ab4(r3)
WriteU32(code, 0xBB14, 0x38A00000u); // li r5,0
WriteU32(code, 0xBB18, 0x80C3072Cu); // lwz r6,0x72c(r3)
WriteU32(code, 0xBB1C, 0x7C0903A6u); // mtctr r0
WriteU32(code, 0xBB20, 0x80860000u); // lwz r4,0(r6)
WriteU32(code, 0xBB24, 0x38C60004u); // addi r6,r6,4
WriteU32(code, 0xBB28, 0x98A401E9u); // stb r5,0x1e9(r4)
WriteU32(code, 0xBB2C, 0x4200FFF4u); // bdnz -0xc
WriteU32(code, 0xBB30, 0x3C80818Au); // lis r4,0x818a
WriteU32(code, 0xBB34, 0x808441F8u); // lwz r4,0x41f8(r4)
WriteU32(code, 0xBB38, 0x80040024u); // lwz r0,0x24(r4)
WriteU32(code, 0xBB3C, 0x5400103Au); // slwi r0,r0,2
WriteU32(code, 0xBB40, 0x7C000034u); // cntlzw r0,r0
WriteU32(code, 0xBB44, 0x5400D97Eu); // srwi r0,r0,5
WriteU32(code, 0xBB48, 0x980328E1u); // stb r0,0x28e1(r3)
WriteU32(code, 0xBB4C, 0x98032699u); // stb r0,0x2699(r3)
WriteU32(code, 0xBB50, EncodeBranch(moduleBase + 0xBB50, 0x80841110u)); // b base code
// Hook target that follows directly.
WriteU32(code, 0xBB54, 0x3CA0818Au); // lis r5,0x818a
WriteU32(code, 0xBB58, 0x80A541F8u); // lwz r5,0x41f8(r5)
WriteU32(code, 0xBB5C, 0x80050024u); // lwz r0,0x24(r5)
WriteU32(code, 0xBB60, 0x5400103Bu); // slwi. r0,r0,2
WriteU32(code, 0xBB64, 0x41820008u); // beq +8
WriteU32(code, 0xBB68, 0x38800001u); // li r4,1
WriteU32(code, 0xBB6C, EncodeBranch(moduleBase + 0xBB6C, 0x805F0DB4u)); // b base code
WriteU32(code, 0xBB70, 0x9421FFC0u); // stwu r1,-0x40(r1)
WriteU32(code, 0xBB74, 0x7C0802A6u); // mflr r0
WriteU32(code, 0xBB78, 0x4E800020u); // blr
WriteU32(code, 0xF000, moduleBase + 0xBB70); // vtable
WriteU32(code, 0xF004, moduleBase + 0xBB08);
var chunk = CodeChunk(
code,
PointerRelocation(0xF000, 0xBB70),
PointerRelocation(0xF004, 0xBB08),
AbsoluteCommand(KamekCommandId.BranchLink, 0x80601234u, 0xBB54));
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module leaf boundary scan", starts[moduleBase + 0xBB08]);
Assert.Equal("BranchLink", starts[moduleBase + 0xBB54]);
Assert.Equal("module data pointer", starts[moduleBase + 0xBB70]);
Assert.False(starts.ContainsKey(moduleBase + 0xBB50));
}
[Fact]
public void IgnoresUnrelocatedInstructionWordThatEncodesTrampolineAddress()
{
const uint moduleBase = 0x81840000u;
var code = new byte[0x10000];
WriteU32(code, 0x100, 0x8184BB50u); // lwz r12,-0x44b0(r4)
WriteU32(code, 0xBB4C, 0x98032699u); // stb r0,0x2699(r3)
WriteU32(code, 0xBB50, EncodeBranch(moduleBase + 0xBB50, 0x80841110u)); // b base code
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(CodeChunk(code), moduleBase, code);
Assert.DoesNotContain(starts, start => start.Address == moduleBase + 0xBB50);
}
[Fact]
public void IgnoresUnrelocatedWordThatEncodesFunctionEpilogueAddress()
{
const uint moduleBase = 0x81840000u;
var code = new byte[0x400];
WriteU32(code, 0x100, 0x81840220u); // lwz r12,0x220(r4)
WriteU32(code, 0x200, 0x9421FFF0u); // stwu r1,-0x10(r1)
WriteU32(code, 0x204, 0x7C0802A6u); // mflr r0
WriteU32(code, 0x208, 0x93C10008u); // stw r30,8(r1)
WriteU32(code, 0x20C, 0x90010014u); // stw r0,0x14(r1)
WriteU32(code, 0x210, 0x38630001u); // addi r3,r3,1
WriteU32(code, 0x214, 0x38630001u); // addi r3,r3,1
WriteU32(code, 0x218, 0x80010014u); // lwz r0,0x14(r1)
WriteU32(code, 0x21C, 0x38630001u); // addi r3,r3,1
WriteU32(code, 0x220, 0x83C10008u); // lwz r30,8(r1)
WriteU32(code, 0x224, 0x7C0803A6u); // mtlr r0
WriteU32(code, 0x228, 0x38210010u); // addi r1,r1,0x10
WriteU32(code, 0x22C, 0x4E800020u); // blr
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(CodeChunk(code), moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module prologue scan", starts[moduleBase + 0x200]);
Assert.False(starts.ContainsKey(moduleBase + 0x220));
}
[Fact]
public void DoesNotSplitFunctionAtAnyAddressEncodedByUnrelocatedInstructions()
{
const uint moduleBase = 0x81840000u;
var code = new byte[0x3000];
WriteU32(code, 0x2000, 0x9421FFF0u); // stwu r1,-0x10(r1)
WriteU32(code, 0x2004, 0x7C0802A6u); // mflr r0
WriteU32(code, 0x2008, 0x90010014u); // stw r0,0x14(r1)
for (var offset = 0x200C; offset < 0x20F0; offset += 4)
{
WriteU32(code, offset, 0x38630001u); // addi r3,r3,1
}
WriteU32(code, 0x20F0, 0x80010014u); // lwz r0,0x14(r1)
WriteU32(code, 0x20F4, 0x7C0803A6u); // mtlr r0
WriteU32(code, 0x20F8, 0x38210010u); // addi r1,r1,0x10
WriteU32(code, 0x20FC, 0x4E800020u); // blr
// lwz r12,disp(r4) for every word of the function body.
for (var offset = 0x2004; offset < 0x2100; offset += 4)
{
WriteU32(code, 0x100 + (offset - 0x2004), 0x81840000u | (uint)offset);
}
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(CodeChunk(code), moduleBase, code);
Assert.Contains(starts, start => start.Address == moduleBase + 0x2000);
Assert.DoesNotContain(starts, start => start.Address > moduleBase + 0x2000 && start.Address < moduleBase + 0x2100);
}
[Fact]
public void DiscoversRelocatedPointerToFunctionThatOnlyLooksLikeAnEpilogue()
{
const uint moduleBase = 0x81840000u;
var code = new byte[0x400];
WriteU32(code, 0x100, moduleBase + 0x220);
WriteU32(code, 0x21C, 0x38630001u); // addi r3,r3,1
WriteU32(code, 0x220, 0x83C10008u); // lwz r30,8(r1)
WriteU32(code, 0x224, 0x38210010u); // addi r1,r1,0x10
WriteU32(code, 0x228, 0x4E800020u); // blr
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(CodeChunk(code, PointerRelocation(0x100, 0x220)), moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module data pointer", starts[moduleBase + 0x220]);
}
[Fact]
public void DiscoversConditionallyWrittenModulePointer()
{
const uint moduleBase = 0x81200000u;
var code = new byte[0x80];
WriteU32(code, 0x40, moduleBase + 0x50);
WriteU32(code, 0x4C, 0x38630001u); // addi r3,r3,1
WriteU32(code, 0x50, 0x4E800020u); // blr
var chunk = CodeChunk(code, RelativeCommand(KamekCommandId.CondWritePointer, 0x40, 0x50, 0));
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module data pointer", starts[moduleBase + 0x50]);
}
[Fact]
public void IgnoresSkippedConditionalPointerWrite()
{
const uint moduleBase = 0x81840000u;
var code = new byte[0x400];
WriteU32(code, 0x100, 0x81840220u); // lwz r12,0x220(r4), expected value did not match
WriteU32(code, 0x21C, 0x38630001u); // addi r3,r3,1
WriteU32(code, 0x220, 0x38600001u); // li r3,1
WriteU32(code, 0x224, 0x4E800020u); // blr
WriteU32(code, 0x300, 0x38600002u); // li r3,2
WriteU32(code, 0x304, 0x4E800020u); // blr
var chunk = CodeChunk(code, RelativeCommand(KamekCommandId.CondWritePointer, 0x100, 0x300, 0x12345678u));
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code);
Assert.DoesNotContain(starts, start => start.Address == moduleBase + 0x220);
Assert.DoesNotContain(starts, start => start.Address == moduleBase + 0x300);
}
[Fact]
public void IgnoresPointerRelocationOverwrittenByLaterWrite()
{
const uint moduleBase = 0x81840000u;
var code = new byte[0x400];
WriteU32(code, 0x100, 0x81840220u); // Write32 result applied after the relocation
WriteU32(code, 0x21C, 0x38630001u); // addi r3,r3,1
WriteU32(code, 0x220, 0x38600001u); // li r3,1
WriteU32(code, 0x224, 0x4E800020u); // blr
WriteU32(code, 0x2FC, 0x38630001u); // addi r3,r3,1
WriteU32(code, 0x300, 0x38600002u); // li r3,2
WriteU32(code, 0x304, 0x4E800020u); // blr
var chunk = CodeChunk(
code,
PointerRelocation(0x100, 0x300),
RelativeCommand(KamekCommandId.Write32, 0x100, 0x81840220u));
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code);
Assert.DoesNotContain(starts, start => start.Address == moduleBase + 0x220);
Assert.DoesNotContain(starts, start => start.Address == moduleBase + 0x300);
}
[Theory]
[InlineData(KamekCommandId.Addr16Lo, 2u)]
[InlineData(KamekCommandId.Addr16Ha, 2u)]
[InlineData(KamekCommandId.Addr16Hi, 2u)]
[InlineData(KamekCommandId.Addr16Lo, 0u)]
[InlineData(KamekCommandId.Addr16Ha, 0u)]
[InlineData(KamekCommandId.Addr16Hi, 0u)]
[InlineData(KamekCommandId.Write32, 0u)]
[InlineData(KamekCommandId.CondWrite32, 0u)]
public void IgnoresWordWhoseOnlyRelocationIsNotAPointerWrite(KamekCommandId id, uint addressDelta)
{
const uint moduleBase = 0x81840000u;
var code = new byte[0x400];
WriteU32(code, 0x100, 0x81840220u); // lwz r12,0x220(r4)
WriteU32(code, 0x21C, 0x38630001u); // addi r3,r3,1
WriteU32(code, 0x220, 0x38600001u); // li r3,1
WriteU32(code, 0x224, 0x4E800020u); // blr
var chunk = CodeChunk(code, RelativeCommand(id, 0x100 + addressDelta, 0x220));
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code);
Assert.DoesNotContain(starts, start => start.Address == moduleBase + 0x220 && start.Reason == "module data pointer");
}
[Fact]
public void IgnoresAbsolutePointerWriteWhenGatingModuleWords()
{
const uint moduleBase = 0x81840000u;
var code = new byte[0x400];
WriteU32(code, 0x100, 0x81840220u); // lwz r12,0x220(r4)
WriteU32(code, 0x21C, 0x38630001u); // addi r3,r3,1
WriteU32(code, 0x220, 0x38600001u); // li r3,1
WriteU32(code, 0x224, 0x4E800020u); // blr
// A base-game pointer patch whose address field happens to match the module offset.
var chunk = CodeChunk(code, AbsoluteCommand(KamekCommandId.WritePointer, 0x100, 0x300));
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code);
Assert.DoesNotContain(starts, start => start.Address == moduleBase + 0x220);
}
[Fact]
public void HonorsPointerRelocationInLastWordOfCode()
{
const uint moduleBase = 0x81200000u;
var code = new byte[0x100];
WriteU32(code, 0xFC, moduleBase + 0x50);
WriteU32(code, 0x50, 0x4E800020u); // blr
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(CodeChunk(code, PointerRelocation(0xFC, 0x50)), moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module data pointer", starts[moduleBase + 0x50]);
}
[Fact]
public void IgnoresUnrelocatedModuleAddressInLastWordOfCode()
{
const uint moduleBase = 0x81200000u;
var code = new byte[0x100];
WriteU32(code, 0xFC, moduleBase + 0x50);
WriteU32(code, 0x50, 0x4E800020u); // blr
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(CodeChunk(code), moduleBase, code);
Assert.DoesNotContain(starts, start => start.Address == moduleBase + 0x50);
}
[Fact]
public void ScansFirstAppendedWordWithoutRelocations()
{
const uint moduleBase = 0x81200000u;
var relocatedImage = new byte[0x200];
WriteU32(relocatedImage, 0x50, 0x4E800020u); // blr
WriteU32(relocatedImage, 0x100, moduleBase + 0x50); // first appended word
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: 0x100,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(0x100 + KamekChunk.HeaderSize),
codeBlob: relocatedImage[..0x100],
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, relocatedImage)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("module data pointer", starts[moduleBase + 0x50]);
}
[Fact]
public void IgnoresUnrelocatedCodeWordPointingIntoAppendedImage()
{
const uint moduleBase = 0x81200000u;
var relocatedImage = new byte[0x300];
WriteU32(relocatedImage, 0x20, moduleBase + 0x200);
WriteU32(relocatedImage, 0x1FC, 0x38630001u); // addi r3,r3,1
WriteU32(relocatedImage, 0x200, 0x38600001u); // li r3,1
WriteU32(relocatedImage, 0x204, 0x4E800020u); // blr
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: 0x100,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(0x100 + KamekChunk.HeaderSize),
codeBlob: relocatedImage[..0x100],
commands: []);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, relocatedImage);
Assert.DoesNotContain(starts, start => start.Address == moduleBase + 0x200);
}
[Fact]
public void StillRejectsRelocatedPointersToData()
{
const uint moduleBase = 0x81200000u;
var code = new byte[0x200];
"control\0"u8.ToArray().CopyTo(code, 0x80);
WriteU32(code, 0x20, moduleBase + 0x80);
WriteU32(code, 0x24, moduleBase + 0x180); // zero word
var chunk = CodeChunk(code, PointerRelocation(0x20, 0x80), PointerRelocation(0x24, 0x180));
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code);
Assert.DoesNotContain(starts, start => start.Address == moduleBase + 0x80);
Assert.DoesNotContain(starts, start => start.Address == moduleBase + 0x180);
}
[Fact]
public void DiscoversCtorEntriesWithAndWithoutPointerRelocations()
{
const uint moduleBase = 0x81200000u;
var code = new byte[0x100];
WriteU32(code, 0x40, moduleBase + 0x60);
WriteU32(code, 0x44, moduleBase + 0x70);
WriteU32(code, 0x5C, 0x38630001u); // addi r3,r3,1
WriteU32(code, 0x60, 0x38600000u); // li r3,0
WriteU32(code, 0x64, 0x4E800020u); // blr
WriteU32(code, 0x6C, 0x38630001u); // addi r3,r3,1
WriteU32(code, 0x70, 0x38600001u); // li r3,1
WriteU32(code, 0x74, 0x4E800020u); // blr
var chunk = new KamekChunk(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: (uint)code.Length,
ctorStart: 0x40,
ctorEnd: 0x48,
chunkSize: (uint)(code.Length + KamekChunk.HeaderSize),
codeBlob: code,
commands: [PointerRelocation(0x40, 0x60)]);
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("ctor", starts[moduleBase + 0x60]);
Assert.Equal("ctor", starts[moduleBase + 0x70]);
}
[Fact]
public void DiscoversHookAndRelocationBranchTargets()
{
const uint moduleBase = 0x81200000u;
var code = new byte[0x200];
WriteU32(code, 0x5C, 0x38630001u); // addi r3,r3,1
WriteU32(code, 0x60, 0x38600001u); // li r3,1
WriteU32(code, 0x64, 0x4E800020u); // blr
WriteU32(code, 0x9C, 0x38630001u); // addi r3,r3,1
WriteU32(code, 0xA0, 0x38600002u); // li r3,2
WriteU32(code, 0xA4, 0x4E800020u); // blr
WriteU32(code, 0xDC, 0x38630001u); // addi r3,r3,1
WriteU32(code, 0xE0, 0x38600003u); // li r3,3
WriteU32(code, 0xE4, 0x4E800020u); // blr
WriteU32(code, 0x100, EncodeBranch(moduleBase + 0x100, 0x80001000u) | 1u); // bl base code
var chunk = CodeChunk(
code,
AbsoluteCommand(KamekCommandId.Branch, 0x80001234u, 0x60),
AbsoluteCommand(KamekCommandId.BranchLink, 0x80001238u, 0xA0),
RelativeCommand(KamekCommandId.Rel24, 0x100, 0xE0));
var starts = ModFunctionDiscovery.DiscoverKamekFunctions(chunk, moduleBase, code)
.ToDictionary(start => start.Address, start => start.Reason);
Assert.Equal("Branch", starts[moduleBase + 0x60]);
Assert.Equal("BranchLink", starts[moduleBase + 0xA0]);
Assert.Equal("Rel24", starts[moduleBase + 0xE0]);
}
private static KamekChunk CodeChunk(byte[] code, params KamekCommand[] commands) =>
new(
index: 0,
fileOffset: 0,
bssSize: 0,
codeSize: (uint)code.Length,
ctorStart: 0,
ctorEnd: 0,
chunkSize: (uint)(code.Length + KamekChunk.HeaderSize),
codeBlob: code,
commands: commands);
private static KamekCommand RelativeCommand(KamekCommandId id, uint offset, params uint[] arguments) =>
new(0, ((uint)id << 24) | offset, id, false, offset, arguments);
private static KamekCommand AbsoluteCommand(KamekCommandId id, uint address, params uint[] arguments) =>
new(0, ((uint)id << 24) | 0x00FFFFFEu, id, true, address, arguments);
private static KamekCommand PointerRelocation(uint offset, uint target) =>
RelativeCommand(KamekCommandId.Addr32, offset, target);
private static void WriteU32(byte[] data, int offset, uint value) =>
BinaryPrimitives.WriteUInt32BigEndian(data.AsSpan(offset, 4), value);