Merge upstream main into openxr-work

This commit is contained in:
iChris4 committed 2026-09-03 15:08:50 +02:00
commit 60316f0115
69 files changed
+5581 -315

No files matched your search

+34 -2
View File
@@ -172,6 +172,7 @@ return command switch
"emit-build-shards" => RunEmitBuildShards(tail),
"emit-base-manifest" => RunEmitBaseManifest(tail),
"check-base-mod-awareness" => RunCheckBaseModAwareness(tail),
"validate-retro-wfc-payload" => RunValidateRetroWfcPayload(tail),
_ => ShowHelp(command)
};
@@ -360,6 +361,33 @@ int RunInfo()
return 0;
}
int RunValidateRetroWfcPayload(string[] argsTail)
{
var directory = OptionValue(argsTail, "--directory");
if (string.IsNullOrWhiteSpace(directory))
{
Console.Error.WriteLine("--directory is required.");
return 1;
}
try
{
WiiCompiled.Setup.Common.RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(directory);
Console.WriteLine("[translator] Retro WFC payload signature validated.");
return 0;
}
catch (InvalidDataException ex)
{
Console.Error.WriteLine($"[translator] Retro WFC payload validation failed: {ex.Message}");
return 2;
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
{
Console.Error.WriteLine($"[translator] Could not read Retro WFC payload: {ex.Message}");
return 1;
}
}
int RunTranslateRecursive(string[] argsTail)
{
if (argsTail.Length == 0 || argsTail[0].StartsWith("--", StringComparison.Ordinal))
@@ -3671,7 +3699,8 @@ static string[] KnownCommands() => new[]
"translate-mod",
"emit-base-manifest",
"emit-build-shards",
"check-base-mod-awareness"
"check-base-mod-awareness",
"validate-retro-wfc-payload"
};
/// <summary>
@@ -3714,6 +3743,10 @@ static (string? Positional, CommandOption[] Options)? CommandSpec(string command
new("--translation-output-metadata", "path"),
new("--code-pul", "path")
}),
"validate-retro-wfc-payload" => (null, new CommandOption[]
{
new("--directory", "directory", Required: true)
}),
"emit-base-manifest" => (null, new CommandOption[]
{
new("--out", "path"),
@@ -4110,4 +4143,3 @@ sealed record ResolvedDispatchEntry(
bool MustRemainDynamicallyDispatchable,
string SourceFile);
@@ -15,6 +15,7 @@
<ItemGroup>
<ProjectReference Include="..\Translator.Core\Translator.Core.csproj" />
<ProjectReference Include="..\..\..\Launcher\WiiCompiled.Setup.Common\WiiCompiled.Setup.Common.csproj" />
</ItemGroup>
<ItemGroup>
@@ -22,14 +22,15 @@ internal static class AssemblyBlobWriter
var expectedFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var assembly = new StringBuilder();
foreach (var header in headerLines) assembly.AppendLine(header);
// PE/COFF (Windows) and ELF (Linux) spell a read-only data section differently in GNU-as
// syntax - COFF section flags ("dr" = data, read-only) versus an ELF section needing an
// allocatable-only flag plus an explicit @progbits type. The build always targets
// PE/COFF (Windows), Mach-O (macOS), and ELF (Linux) spell a read-only data section
// differently in GNU-as syntax. The build always targets
// whichever platform the translator itself runs on (there is no cross-compilation
// support), so that's what this picks the section syntax from.
assembly.AppendLine(OperatingSystem.IsWindows()
? ".section .rdata,\"dr\""
: ".section .rodata,\"a\",@progbits");
: OperatingSystem.IsMacOS()
? ".section __TEXT,__const"
: ".section .rodata,\"a\",@progbits");
assembly.AppendLine();
foreach (var blob in blobs)
@@ -40,8 +41,12 @@ internal static class AssemblyBlobWriter
var hash = ChecksumUtilities.Sha256Hex(blob.Data.Span);
assembly.AppendLine($"// {blob.Comment}; sha256={hash}");
assembly.AppendLine(".p2align 4");
assembly.AppendLine($".globl {blob.Symbol}");
assembly.AppendLine($"{blob.Symbol}:");
// C/C++ external symbols carry a leading underscore in Mach-O,
// unlike ELF and COFF. The generated C++ still names the symbol
// without that ABI decoration, so emit the platform spelling here.
var assemblySymbol = OperatingSystem.IsMacOS() ? $"_{blob.Symbol}" : blob.Symbol;
assembly.AppendLine($".globl {assemblySymbol}");
assembly.AppendLine($"{assemblySymbol}:");
var referencePath = Path.Combine(blobReferenceDirectory, blob.FileName);
assembly.AppendLine($".incbin \"{SanitizeAssemblyPath(referencePath)}\"");
assembly.AppendLine();
@@ -51,7 +56,7 @@ internal static class AssemblyBlobWriter
{
if (!expectedFiles.Contains(Path.GetFullPath(stalePath))) File.Delete(stalePath);
}
if (!OperatingSystem.IsWindows())
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
{
// Absence of a .note.GNU-stack section makes the linker assume the oldest, most
// conservative default for this object (an executable stack) and warn about it; this
@@ -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;
@@ -28,8 +28,10 @@ public sealed class ModDataPatchWriterTests
var assemblyPath = Path.Combine(root, "cpp", "mod_data_patches_blobs.S");
var assembly = File.ReadAllText(assemblyPath);
Assert.Contains(".globl kModuleImage", assembly, StringComparison.Ordinal);
Assert.Contains(".globl kKamekCodeSha1Digest", assembly, StringComparison.Ordinal);
var symbolPrefix = OperatingSystem.IsMacOS() ? "_" : "";
Assert.Contains($".globl {symbolPrefix}kModuleImage", assembly, StringComparison.Ordinal);
Assert.Contains($".globl {symbolPrefix}kKamekCodeSha1Digest", assembly, StringComparison.Ordinal);
Assert.DoesNotContain("__APPLE__", assembly, StringComparison.Ordinal);
Assert.Contains(".incbin", assembly, StringComparison.Ordinal);
var timestamps = new[] { cppPath, assemblyPath }