mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-11 01:23:15 -04:00
152 lines
5.3 KiB
C#
152 lines
5.3 KiB
C#
using System.Buffers.Binary;
|
|
using Translator.Core.Parsing.Kamek;
|
|
using Xunit;
|
|
|
|
namespace Translator.Tests;
|
|
|
|
public class KamekPulFileTests
|
|
{
|
|
[Fact]
|
|
public void ParsesRawSingleChunk()
|
|
{
|
|
var chunk = BuildChunk(
|
|
bssSize: 0x20,
|
|
code: [0x60, 0x00, 0x00, 0x00],
|
|
commands:
|
|
[
|
|
BuildCommand(KamekCommandId.Write32, absolute: true, address: 0x805850BC, args: [0x60000000])
|
|
]);
|
|
|
|
var pul = KamekPulFile.Parse(chunk);
|
|
var selected = pul.SelectRegion("P");
|
|
|
|
Assert.False(pul.IsCombined);
|
|
Assert.Single(pul.Chunks);
|
|
Assert.Equal(0x20u, selected.BssSize);
|
|
Assert.Equal(4u, selected.CodeSize);
|
|
Assert.Single(selected.Commands);
|
|
Assert.Equal(KamekCommandId.Write32, selected.Commands[0].Id);
|
|
Assert.True(selected.Commands[0].AddressIsAbsolute);
|
|
Assert.Equal(0x805850BCu, selected.Commands[0].Address);
|
|
}
|
|
|
|
[Fact]
|
|
public void ParsesCombinedChunksAndSelectsRegion()
|
|
{
|
|
var p = BuildChunk(0x10, [0x4E, 0x80, 0x00, 0x20], [BuildCommand(KamekCommandId.Branch, true, 0x8053369C, [0x100])]);
|
|
var e = BuildChunk(0x20, [0x60, 0x00, 0x00, 0x00], [BuildCommand(KamekCommandId.Write8, false, 0x10, [0x7F])]);
|
|
var combined = new byte[0x10 + p.Length + e.Length];
|
|
WriteU32(combined, 0, (uint)p.Length);
|
|
WriteU32(combined, 4, (uint)e.Length);
|
|
p.CopyTo(combined, 0x10);
|
|
e.CopyTo(combined, 0x10 + p.Length);
|
|
|
|
var pul = KamekPulFile.Parse(combined);
|
|
|
|
Assert.True(pul.IsCombined);
|
|
Assert.Equal(2, pul.Chunks.Count);
|
|
Assert.Equal(0x10u, pul.SelectRegion("P").BssSize);
|
|
Assert.Equal(0x20u, pul.SelectRegion("E").BssSize);
|
|
}
|
|
|
|
[Fact]
|
|
public void EncodesPpcBranchLikeKamek()
|
|
{
|
|
Assert.Equal(0x48000144u, KamekPpcEncoding.EncodeBranch(0x8053369C, 0x805337E0, link: false));
|
|
Assert.Equal(0x4BFFFFFDu, KamekPpcEncoding.EncodeBranch(0x8053369C, 0x80533698, link: true));
|
|
Assert.Throws<ArgumentOutOfRangeException>(() => KamekPpcEncoding.EncodeBranch(0x8053369C, 0x8053369D, link: false));
|
|
}
|
|
|
|
[Fact]
|
|
public void ParsesBundledRetroRewindCodePulWhenPresent()
|
|
{
|
|
var repoRoot = ProjectPathsForTests.FindRepositoryRoot();
|
|
var codePul = Path.Combine(repoRoot, "PulsarPacks", "completed", "RetroRewind", "RetroRewind6", "Binaries", "Code.pul");
|
|
if (!File.Exists(codePul))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var pul = KamekPulFile.Load(codePul);
|
|
var chunk = pul.SelectRegion("P");
|
|
|
|
Assert.True(pul.IsCombined);
|
|
Assert.Equal(3, pul.Chunks.Count);
|
|
Assert.All(pul.CombinedChunkSizes.Take(3), size => Assert.True(size >= KamekChunk.HeaderSize));
|
|
Assert.Equal(0u, pul.CombinedChunkSizes[3]);
|
|
Assert.NotEmpty(chunk.CodeBlob);
|
|
Assert.True(chunk.BssSize > 0);
|
|
Assert.True(chunk.CtorStart <= chunk.CtorEnd);
|
|
Assert.True(chunk.CtorEnd <= chunk.CodeSize);
|
|
Assert.NotEmpty(chunk.Commands);
|
|
Assert.Equal(chunk.Commands.Count, chunk.RelativeCommandCount + chunk.AbsoluteCommandCount);
|
|
Assert.Equal(chunk.Commands.Count, chunk.CommandCounts.Values.Sum());
|
|
}
|
|
|
|
private static byte[] BuildChunk(uint bssSize, byte[] code, byte[][] commands)
|
|
{
|
|
var commandSize = commands.Sum(c => c.Length);
|
|
var chunkSize = KamekChunk.HeaderSize + code.Length + commandSize;
|
|
var data = new byte[chunkSize];
|
|
WriteU32(data, 0x00, KamekChunk.Magic0);
|
|
WriteU32(data, 0x04, KamekChunk.Magic1);
|
|
WriteU32(data, 0x08, bssSize);
|
|
WriteU32(data, 0x0C, (uint)code.Length);
|
|
WriteU32(data, 0x10, 0);
|
|
WriteU32(data, 0x14, 0);
|
|
WriteU32(data, 0x18, (uint)chunkSize);
|
|
code.CopyTo(data, KamekChunk.HeaderSize);
|
|
|
|
var offset = KamekChunk.HeaderSize + code.Length;
|
|
foreach (var command in commands)
|
|
{
|
|
command.CopyTo(data, offset);
|
|
offset += command.Length;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
private static byte[] BuildCommand(KamekCommandId id, bool absolute, uint address, uint[] args)
|
|
{
|
|
var size = 4 + (absolute ? 4 : 0) + args.Length * 4;
|
|
var data = new byte[size];
|
|
var commandWord = ((uint)(byte)id << 24) | (absolute ? 0x00FFFFFEu : address);
|
|
WriteU32(data, 0, commandWord);
|
|
var offset = 4;
|
|
if (absolute)
|
|
{
|
|
WriteU32(data, offset, address);
|
|
offset += 4;
|
|
}
|
|
foreach (var arg in args)
|
|
{
|
|
WriteU32(data, offset, arg);
|
|
offset += 4;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
private static void WriteU32(byte[] data, int offset, uint value) =>
|
|
BinaryPrimitives.WriteUInt32BigEndian(data.AsSpan(offset, 4), value);
|
|
}
|
|
|
|
internal static class ProjectPathsForTests
|
|
{
|
|
public static string FindRepositoryRoot()
|
|
{
|
|
var dir = new DirectoryInfo(AppContext.BaseDirectory);
|
|
while (dir is not null)
|
|
{
|
|
if (File.Exists(Path.Combine(dir.FullName, "translator", "Translator.sln")) &&
|
|
File.Exists(Path.Combine(dir.FullName, "projects", "mkwii", "recomp.yml")))
|
|
{
|
|
return dir.FullName;
|
|
}
|
|
dir = dir.Parent;
|
|
}
|
|
|
|
throw new DirectoryNotFoundException("Could not find repository root.");
|
|
}
|
|
}
|