mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-11 01:23:15 -04:00
Support Kamek v2 Code.pul files (#126)
This commit is contained in:
@@ -6,6 +6,12 @@ namespace Translator.Core.Parsing.Kamek;
|
||||
public sealed class KamekChunk
|
||||
{
|
||||
public const uint Magic0 = 0x4B616D65;
|
||||
/// <summary>
|
||||
/// Kamek v2 is still emitted by current Retro Rewind releases. Its command
|
||||
/// stream matches v3, but combined v2 files carry each chunk length only in
|
||||
/// the outer size table.
|
||||
/// </summary>
|
||||
public const uint Magic1V2 = 0x6B000002;
|
||||
public const uint Magic1 = 0x6B000003;
|
||||
public const int HeaderSize = 0x20;
|
||||
|
||||
@@ -58,7 +64,7 @@ public sealed class KamekChunk
|
||||
}
|
||||
|
||||
return BinaryPrimitives.ReadUInt32BigEndian(data.Slice(offset, 4)) == Magic0 &&
|
||||
BinaryPrimitives.ReadUInt32BigEndian(data.Slice(offset + 4, 4)) == Magic1;
|
||||
IsSupportedFormatVersion(BinaryPrimitives.ReadUInt32BigEndian(data.Slice(offset + 4, 4)));
|
||||
}
|
||||
|
||||
public static KamekChunk Parse(byte[] data, int offset, int expectedSize, int index)
|
||||
@@ -74,7 +80,7 @@ public sealed class KamekChunk
|
||||
|
||||
var magic0 = reader.ReadUInt32();
|
||||
var magic1 = reader.ReadUInt32();
|
||||
if (magic0 != Magic0 || magic1 != Magic1)
|
||||
if (magic0 != Magic0 || !IsSupportedFormatVersion(magic1))
|
||||
{
|
||||
throw new InvalidDataException($"Kamek chunk {index} at 0x{offset:X} has invalid magic 0x{magic0:X8}/0x{magic1:X8}.");
|
||||
}
|
||||
@@ -83,9 +89,15 @@ public sealed class KamekChunk
|
||||
var codeSize = reader.ReadUInt32();
|
||||
var ctorStart = reader.ReadUInt32();
|
||||
var ctorEnd = reader.ReadUInt32();
|
||||
var chunkSize = reader.ReadUInt32();
|
||||
var declaredChunkSize = reader.ReadUInt32();
|
||||
_ = reader.ReadUInt32(); // reserved
|
||||
|
||||
// v3 writes each chunk's length in its header. v2 leaves that field at
|
||||
// zero and relies on the combined-file table (or the raw file length).
|
||||
var chunkSize = magic1 == Magic1V2
|
||||
? checked((uint)(expectedSize != 0 ? expectedSize : data.Length - offset))
|
||||
: declaredChunkSize;
|
||||
|
||||
if (chunkSize < HeaderSize + codeSize)
|
||||
{
|
||||
throw new InvalidDataException($"Kamek chunk {index} chunkSize 0x{chunkSize:X} is smaller than header+code.");
|
||||
@@ -171,4 +183,7 @@ public sealed class KamekChunk
|
||||
KamekCommandId.BranchLink => 1,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(id), id, "Unsupported Kamek command id")
|
||||
};
|
||||
|
||||
private static bool IsSupportedFormatVersion(uint magic1) =>
|
||||
magic1 is Magic1V2 or Magic1;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public sealed class KamekPulFile
|
||||
|
||||
if (!KamekChunk.HasMagic(data, 0))
|
||||
{
|
||||
throw new InvalidDataException("File is neither a combined Code.pul nor a raw Kamek v3 chunk.");
|
||||
throw new InvalidDataException("File is neither a combined Code.pul nor a raw supported Kamek chunk.");
|
||||
}
|
||||
|
||||
var rawChunk = KamekChunk.Parse(data, 0, expectedSize: 0, index: 0);
|
||||
|
||||
@@ -49,6 +49,27 @@ public class KamekPulFileTests
|
||||
Assert.Equal(0x20u, pul.SelectRegion("E").BssSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParsesKamekV2CombinedChunks()
|
||||
{
|
||||
var p = BuildChunk(0x10, [0x4E, 0x80, 0x00, 0x20],
|
||||
[BuildCommand(KamekCommandId.Branch, true, 0x8053369C, [0x100])], KamekChunk.Magic1V2);
|
||||
var combined = new byte[0x10 + p.Length];
|
||||
WriteU32(combined, 0, (uint)p.Length);
|
||||
p.CopyTo(combined, 0x10);
|
||||
|
||||
var pul = KamekPulFile.Parse(combined);
|
||||
|
||||
Assert.True(pul.IsCombined);
|
||||
Assert.Equal(0x10u, pul.SelectRegion("P").BssSize);
|
||||
Assert.Equal(KamekCommandId.Branch, pul.SelectRegion("P").Commands.Single().Id);
|
||||
|
||||
var raw = KamekPulFile.Parse(p);
|
||||
|
||||
Assert.False(raw.IsCombined);
|
||||
Assert.Equal((uint)p.Length, raw.Chunks.Single().ChunkSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EncodesPpcBranchLikeKamek()
|
||||
{
|
||||
@@ -83,18 +104,19 @@ public class KamekPulFileTests
|
||||
Assert.Equal(chunk.Commands.Count, chunk.CommandCounts.Values.Sum());
|
||||
}
|
||||
|
||||
private static byte[] BuildChunk(uint bssSize, byte[] code, byte[][] commands)
|
||||
private static byte[] BuildChunk(uint bssSize, byte[] code, byte[][] commands,
|
||||
uint magic1 = KamekChunk.Magic1)
|
||||
{
|
||||
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, 0x04, magic1);
|
||||
WriteU32(data, 0x08, bssSize);
|
||||
WriteU32(data, 0x0C, (uint)code.Length);
|
||||
WriteU32(data, 0x10, 0);
|
||||
WriteU32(data, 0x14, 0);
|
||||
WriteU32(data, 0x18, (uint)chunkSize);
|
||||
WriteU32(data, 0x18, magic1 == KamekChunk.Magic1V2 ? 0u : (uint)chunkSize);
|
||||
code.CopyTo(data, KamekChunk.HeaderSize);
|
||||
|
||||
var offset = KamekChunk.HeaderSize + code.Length;
|
||||
|
||||
Reference in New Issue
Block a user