From 0dcb0529664974bd2263c1854a656e8039123380 Mon Sep 17 00:00:00 2001 From: Gericom Date: Mon, 1 Jan 2024 16:09:36 +0100 Subject: [PATCH] Initial commit --- .gitattributes | 63 ++++ .gitignore | 261 ++++++++++++++ GCDTool.sln | 25 ++ GCDTool/AesKeyScrambler.cs | 40 +++ GCDTool/Blowfish.cs | 190 +++++++++++ GCDTool/CommandLineOptions.cs | 30 ++ GCDTool/Elf/ElfFile.cs | 51 +++ GCDTool/Elf/ElfHeader.cs | 58 ++++ GCDTool/Elf/ElfSectionType.cs | 29 ++ GCDTool/Elf/ProgramHeaderTableEntry.cs | 54 +++ GCDTool/Elf/SectionHeaderTableEntry.cs | 41 +++ GCDTool/Elf/Sections/ElfSection.cs | 18 + GCDTool/Elf/Sections/ElfSectionFactory.cs | 14 + GCDTool/Elf/Sections/ElfStrtab.cs | 22 ++ GCDTool/Elf/Sections/ElfSymbol.cs | 23 ++ GCDTool/Elf/Sections/ElfSymtab.cs | 22 ++ GCDTool/GCDTool.csproj | 17 + GCDTool/GcdBlowfish.cs | 392 ++++++++++++++++++++++ GCDTool/GcdHeader.cs | 133 ++++++++ GCDTool/GcdHeaderFlags.cs | 9 + GCDTool/GcdRom.cs | 88 +++++ GCDTool/GcdSignature.cs | 76 +++++ GCDTool/IO/EndianBinaryReader.cs | 152 +++++++++ GCDTool/IO/Endianness.cs | 7 + GCDTool/IO/IOUtil.cs | 221 ++++++++++++ GCDTool/Program.cs | 212 ++++++++++++ GCDTool/Wram/NtrWramMaster.cs | 10 + GCDTool/Wram/WramABlockMapping.cs | 14 + GCDTool/Wram/WramAMappedSlots.cs | 22 ++ GCDTool/Wram/WramAMapping.cs | 63 ++++ GCDTool/Wram/WramAMaster.cs | 10 + GCDTool/Wram/WramASlot.cs | 12 + GCDTool/Wram/WramBBlockMapping.cs | 14 + GCDTool/Wram/WramBCMappedSlots.cs | 27 ++ GCDTool/Wram/WramBCMapping.cs | 63 ++++ GCDTool/Wram/WramBCSlot.cs | 16 + GCDTool/Wram/WramBMaster.cs | 11 + GCDTool/Wram/WramCBlockMapping.cs | 14 + GCDTool/Wram/WramCMaster.cs | 11 + GCDTool/Wram/WramConfig.cs | 126 +++++++ GCDTool/Wram/WramConfigJsonReader.cs | 254 ++++++++++++++ readme.md | 88 +++++ 42 files changed, 3003 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 GCDTool.sln create mode 100644 GCDTool/AesKeyScrambler.cs create mode 100644 GCDTool/Blowfish.cs create mode 100644 GCDTool/CommandLineOptions.cs create mode 100644 GCDTool/Elf/ElfFile.cs create mode 100644 GCDTool/Elf/ElfHeader.cs create mode 100644 GCDTool/Elf/ElfSectionType.cs create mode 100644 GCDTool/Elf/ProgramHeaderTableEntry.cs create mode 100644 GCDTool/Elf/SectionHeaderTableEntry.cs create mode 100644 GCDTool/Elf/Sections/ElfSection.cs create mode 100644 GCDTool/Elf/Sections/ElfSectionFactory.cs create mode 100644 GCDTool/Elf/Sections/ElfStrtab.cs create mode 100644 GCDTool/Elf/Sections/ElfSymbol.cs create mode 100644 GCDTool/Elf/Sections/ElfSymtab.cs create mode 100644 GCDTool/GCDTool.csproj create mode 100644 GCDTool/GcdBlowfish.cs create mode 100644 GCDTool/GcdHeader.cs create mode 100644 GCDTool/GcdHeaderFlags.cs create mode 100644 GCDTool/GcdRom.cs create mode 100644 GCDTool/GcdSignature.cs create mode 100644 GCDTool/IO/EndianBinaryReader.cs create mode 100644 GCDTool/IO/Endianness.cs create mode 100644 GCDTool/IO/IOUtil.cs create mode 100644 GCDTool/Program.cs create mode 100644 GCDTool/Wram/NtrWramMaster.cs create mode 100644 GCDTool/Wram/WramABlockMapping.cs create mode 100644 GCDTool/Wram/WramAMappedSlots.cs create mode 100644 GCDTool/Wram/WramAMapping.cs create mode 100644 GCDTool/Wram/WramAMaster.cs create mode 100644 GCDTool/Wram/WramASlot.cs create mode 100644 GCDTool/Wram/WramBBlockMapping.cs create mode 100644 GCDTool/Wram/WramBCMappedSlots.cs create mode 100644 GCDTool/Wram/WramBCMapping.cs create mode 100644 GCDTool/Wram/WramBCSlot.cs create mode 100644 GCDTool/Wram/WramBMaster.cs create mode 100644 GCDTool/Wram/WramCBlockMapping.cs create mode 100644 GCDTool/Wram/WramCMaster.cs create mode 100644 GCDTool/Wram/WramConfig.cs create mode 100644 GCDTool/Wram/WramConfigJsonReader.cs create mode 100644 readme.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c4efe2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,261 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file diff --git a/GCDTool.sln b/GCDTool.sln new file mode 100644 index 0000000..cca48f4 --- /dev/null +++ b/GCDTool.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34309.116 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GCDTool", "GCDTool\GCDTool.csproj", "{C9C84AF7-5F36-4623-AE76-19AC55B64450}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C9C84AF7-5F36-4623-AE76-19AC55B64450}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C9C84AF7-5F36-4623-AE76-19AC55B64450}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C9C84AF7-5F36-4623-AE76-19AC55B64450}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C9C84AF7-5F36-4623-AE76-19AC55B64450}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {76A73BFB-4C65-473A-821E-CDCA3B40E343} + EndGlobalSection +EndGlobal diff --git a/GCDTool/AesKeyScrambler.cs b/GCDTool/AesKeyScrambler.cs new file mode 100644 index 0000000..de3d3e4 --- /dev/null +++ b/GCDTool/AesKeyScrambler.cs @@ -0,0 +1,40 @@ +using System.Buffers.Binary; + +namespace GCDTool; + +/// +/// Static class implementing the DSi AES key scrambler. +/// +public static class AesKeyScrambler +{ + private static readonly UInt128 scramblerMagic = new(0xFFFEFB4E29590258ul, 0x2A680F5F1A4F3E79ul); + + private const int SCRAMBLER_ROL = 42; + + /// + /// Scrambles the given and . + /// + /// Input key X. + /// Input key Y. + /// The scrambled key. + public static byte[] Scramble(ReadOnlySpan keyX, ReadOnlySpan keyY) + { + UInt128 scrambledKey = Scramble( + BinaryPrimitives.ReadUInt128LittleEndian(keyX), + BinaryPrimitives.ReadUInt128LittleEndian(keyY)); + var result = new byte[16]; + BinaryPrimitives.WriteUInt128LittleEndian(result, scrambledKey); + return result; + } + + /// + /// Scrambles the given and . + /// + /// Input key X. + /// Input key Y. + /// The scrambled key. + public static UInt128 Scramble(UInt128 keyX, UInt128 keyY) + { + return UInt128.RotateLeft((keyX ^ keyY) + scramblerMagic, SCRAMBLER_ROL); + } +} \ No newline at end of file diff --git a/GCDTool/Blowfish.cs b/GCDTool/Blowfish.cs new file mode 100644 index 0000000..5dc9bd8 --- /dev/null +++ b/GCDTool/Blowfish.cs @@ -0,0 +1,190 @@ +using GCDTool.IO; + +namespace GCDTool; + +/// +/// Class for performing Blowfish encryption and decryption. +/// +public sealed class Blowfish +{ + public const int BLOCK_LENGTH = 8; + public const int KEY_TABLE_LENGTH = 0x1048; + public const int P_TABLE_ENTRY_COUNT = 18; + public const int S_BOX_COUNT = 4; + public const int S_BOX_ENTRY_COUNT = 256; + + private const string DATA_LENGTH_NOT_MULTIPLE_OF_8_EXCEPTION_MESSAGE = "Data length must be a multiple of 8."; + private const string DESTINATION_BUFFER_TOO_SMALL_EXCEPTION_MESSAGE = "Not enough space in destination buffer."; + + private readonly uint[] _pTable; + private readonly uint[][] _sBoxes; + + public Blowfish(uint[] pTable, uint[][] sBoxes) + { + if (pTable.Length != P_TABLE_ENTRY_COUNT) + { + throw new ArgumentException($"Size of p table should be {P_TABLE_ENTRY_COUNT}", nameof(pTable)); + } + + if (sBoxes.Length != S_BOX_COUNT) + { + throw new ArgumentException($"Number of s boxes should be {S_BOX_COUNT}", nameof(sBoxes)); + } + + for (int i = 0; i < S_BOX_COUNT; i++) + { + if (sBoxes[i].Length != S_BOX_ENTRY_COUNT) + { + throw new ArgumentException($"Size of s box {i} should be {S_BOX_ENTRY_COUNT}", nameof(sBoxes)); + } + } + + _pTable = pTable; + _sBoxes = sBoxes; + } + + public Blowfish(ReadOnlySpan keyTable) + { + if (keyTable.Length < KEY_TABLE_LENGTH) + { + throw new ArgumentException(nameof(keyTable)); + } + + _pTable = IOUtil.ReadU32Le(keyTable, P_TABLE_ENTRY_COUNT); + _sBoxes = new uint[S_BOX_COUNT][]; + _sBoxes[0] = IOUtil.ReadU32Le(keyTable[0x48..], S_BOX_ENTRY_COUNT); + _sBoxes[1] = IOUtil.ReadU32Le(keyTable[0x448..], S_BOX_ENTRY_COUNT); + _sBoxes[2] = IOUtil.ReadU32Le(keyTable[0x848..], S_BOX_ENTRY_COUNT); + _sBoxes[3] = IOUtil.ReadU32Le(keyTable[0xC48..], S_BOX_ENTRY_COUNT); + } + + /// + /// Encrypts bytes of data in the given buffer + /// starting at . must be a multiple of 8. + /// + /// The data buffer. + /// The offset in the buffer to start encrypting. + /// The number of bytes to encrypt. Should be a multiple of 8. + public void Encrypt(byte[] data, int offset, int length) + { + Encrypt(data.AsSpan(offset, length)); + } + + /// + /// Encrypts the data in the given span in place. + /// The length of the span must be a multiple of 8. + /// + /// The data to encrypt. + public void Encrypt(Span data) + { + ThrowIfDataLengthNotMultipleOf8(data.Length, nameof(data)); + + for (int i = 0; i < data.Length; i += BLOCK_LENGTH) + { + ulong val = Encrypt(IOUtil.ReadU64Le(data[i..])); + IOUtil.WriteU64Le(data[i..], val); + } + } + + /// + /// Encrypts a single 64 bit value. + /// + /// The value to encrypt. + /// The encrypted value. + public ulong Encrypt(ulong val) + { + uint y = (uint)(val & 0xFFFFFFFF); + uint x = (uint)(val >> 32); + for (int i = 0; i < 16; i++) + { + uint z = _pTable[i] ^ x; + uint a = _sBoxes[0][z >> 24 & 0xFF]; + uint b = _sBoxes[1][z >> 16 & 0xFF]; + uint c = _sBoxes[2][z >> 8 & 0xFF]; + uint d = _sBoxes[3][z & 0xFF]; + x = d + (c ^ b + a) ^ y; + y = z; + } + + return x ^ _pTable[16] | (ulong)(y ^ _pTable[17]) << 32; + } + + /// + /// Decrypts bytes of data starting as in + /// and writes it to starting from . + /// must be a multiple of 8. + /// + /// The buffer containing the data to decrypt. + /// The offset in where the encrypted data starts. + /// The amount of data to decrypt. Should be a multiple of 8 + /// The buffer to write the decrypted data to. + /// The offset in where the decrypted data should be written. + public void Decrypt(byte[] src, int srcOffset, int length, byte[] dst, int dstOffset) + { + Decrypt(src.AsSpan(srcOffset, length), dst.AsSpan(dstOffset, length)); + } + + /// + /// Decrypts the data in the given span in place. + /// The length of the span must be a multiple of 8. + /// + /// The data to decrypt. + public void Decrypt(Span data) + { + Decrypt(data, data); + } + + /// + /// Decrypts the data in the given span and writes it to the span. + /// The length of the span must be a multiple of 8. + /// must have a length equal to or larger than . + /// + /// The data to decrypt. + /// The span to write the decrypted data to. + public void Decrypt(ReadOnlySpan src, Span dst) + { + ThrowIfDataLengthNotMultipleOf8(src.Length, nameof(src)); + + if (dst.Length < src.Length) + { + throw new ArgumentException(DESTINATION_BUFFER_TOO_SMALL_EXCEPTION_MESSAGE, nameof(dst)); + } + + for (int i = 0; i < src.Length; i += BLOCK_LENGTH) + { + ulong val = Decrypt(IOUtil.ReadU64Le(src[i..])); + IOUtil.WriteU64Le(dst[i..], val); + } + } + + /// + /// Decrypts a single 64 bit value. + /// + /// The value to decrypt. + /// The decrypted value. + public ulong Decrypt(ulong val) + { + uint y = (uint)(val & 0xFFFFFFFF); + uint x = (uint)(val >> 32); + for (int i = 17; i >= 2; i--) + { + uint z = _pTable[i] ^ x; + uint a = _sBoxes[0][z >> 24 & 0xFF]; + uint b = _sBoxes[1][z >> 16 & 0xFF]; + uint c = _sBoxes[2][z >> 8 & 0xFF]; + uint d = _sBoxes[3][z & 0xFF]; + x = d + (c ^ b + a) ^ y; + y = z; + } + + return x ^ _pTable[1] | (ulong)(y ^ _pTable[0]) << 32; + } + + private void ThrowIfDataLengthNotMultipleOf8(int dataLength, string paramName) + { + if ((dataLength & 7) != 0) + { + throw new ArgumentException(DATA_LENGTH_NOT_MULTIPLE_OF_8_EXCEPTION_MESSAGE, paramName); + } + } +} \ No newline at end of file diff --git a/GCDTool/CommandLineOptions.cs b/GCDTool/CommandLineOptions.cs new file mode 100644 index 0000000..876f2c5 --- /dev/null +++ b/GCDTool/CommandLineOptions.cs @@ -0,0 +1,30 @@ +using CommandLine; + +namespace GCDTool; + +/// +/// Class containing the command line options of GCDTool. +/// +sealed class CommandLineOptions +{ + [Option("arm9", Required = true, HelpText = "ARM9 elf file path.")] + public required string Arm9Path { get; init; } + + [Option("arm7", Required = true, HelpText = "ARM7 elf file path.")] + public required string Arm7Path { get; init; } + + [Option("wram", Required = true, HelpText = "Initial WRAM configuration json file path.")] + public required string WramConfigJsonPath { get; init; } + + [Option("rsakey", Required = true, HelpText = "GCD RSA private key der file path.")] + public required string GcdKeyPath { get; init; } + + [Option("gamecode", Default = "####", Required = false, HelpText = "The gamecode to use.")] + public required string GameCode { get; init; } + + [Option("arm9-67-mhz", Default = false, Required = false, HelpText = "When true, the ARM9 will start at 67 MHz instead of 134 MHz.")] + public bool Arm9Speed67MHz { get; init; } + + [Value(0, MetaName = "output path", Required = true, HelpText = "The path of the gcd file to create.")] + public required string OutputPath { get; init; } +} diff --git a/GCDTool/Elf/ElfFile.cs b/GCDTool/Elf/ElfFile.cs new file mode 100644 index 0000000..cb6f008 --- /dev/null +++ b/GCDTool/Elf/ElfFile.cs @@ -0,0 +1,51 @@ +using GCDTool.Elf.Sections; +using GCDTool.IO; + +namespace GCDTool.Elf; + +sealed class ElfFile +{ + public ElfFile(byte[] data) + { + var er = new EndianBinaryReader(new MemoryStream(data), Endianness.LittleEndian); + Header = new ElfHeader(er); + + er.BaseStream.Position = Header.ProgramHeaderTableOffset; + ProgramHeaderTable = new ProgramHeaderTableEntry[Header.ProgramHeaderTableEntryCount]; + for (int i = 0; i < Header.ProgramHeaderTableEntryCount; i++) + { + ProgramHeaderTable[i] = new ProgramHeaderTableEntry(er); + } + + er.BaseStream.Position = Header.SectionHeaderTableOffset; + SectionHeaderTable = new SectionHeaderTableEntry[Header.SectionHeaderTableEntryCount]; + for (int i = 0; i < Header.SectionHeaderTableEntryCount; i++) + { + SectionHeaderTable[i] = new SectionHeaderTableEntry(er); + } + + er.Close(); + + var namestab = new ElfStrtab(SectionHeaderTable[Header.SectionNamesIndex], string.Empty); + Sections = new ElfSection[SectionHeaderTable.Length]; + var sectionFactory = new ElfSectionFactory(); + for (int i = 0; i < SectionHeaderTable.Length; i++) + { + Sections[i] = sectionFactory.CreateElfSection(SectionHeaderTable[i], + namestab.GetString(SectionHeaderTable[i].NameOffset)); + } + } + + public ElfHeader Header; + + public ProgramHeaderTableEntry[] ProgramHeaderTable; + + public SectionHeaderTableEntry[] SectionHeaderTable; + + public ElfSection[] Sections { get; } + + public ElfSection? GetSectionByName(string name) + { + return Sections.FirstOrDefault(section => section.Name == name); + } +} diff --git a/GCDTool/Elf/ElfHeader.cs b/GCDTool/Elf/ElfHeader.cs new file mode 100644 index 0000000..b5b153b --- /dev/null +++ b/GCDTool/Elf/ElfHeader.cs @@ -0,0 +1,58 @@ +using GCDTool.IO; + +namespace GCDTool.Elf; + +sealed class ElfHeader +{ + private const uint ElfHeaderMagic = 0x464C457F; + + public ElfHeader(EndianBinaryReader er) + { + Magic = er.Read(); + if (Magic != ElfHeaderMagic) + { + throw new InvalidDataException("Elf magic invalid!"); + } + + BitFormat = er.Read(); + Endianness = er.Read(); + Version = er.Read(); + Abi = er.Read(); + AbiVersion = er.Read(); + Padding = er.Read(7); + ObjectType = er.Read(); + Architecture = er.Read(); + Version2 = er.Read(); + EntryPoint = er.Read(); + ProgramHeaderTableOffset = er.Read(); + SectionHeaderTableOffset = er.Read(); + Flags = er.Read(); + HeaderSize = er.Read(); + ProgramHeaderTableEntrySize = er.Read(); + ProgramHeaderTableEntryCount = er.Read(); + SectionHeaderTableEntrySize = er.Read(); + SectionHeaderTableEntryCount = er.Read(); + SectionNamesIndex = er.Read(); + } + + public uint Magic; + public byte BitFormat; + public byte Endianness; + public byte Version; + public byte Abi; + public byte AbiVersion; + public byte[] Padding;//7 + public ushort ObjectType; + public ushort Architecture; + public uint Version2; + public uint EntryPoint; + public uint ProgramHeaderTableOffset; + public uint SectionHeaderTableOffset; + public uint Flags; + public ushort HeaderSize; + public ushort ProgramHeaderTableEntrySize; + public ushort ProgramHeaderTableEntryCount; + public ushort SectionHeaderTableEntrySize; + public ushort SectionHeaderTableEntryCount; + public ushort SectionNamesIndex; +} \ No newline at end of file diff --git a/GCDTool/Elf/ElfSectionType.cs b/GCDTool/Elf/ElfSectionType.cs new file mode 100644 index 0000000..fd81836 --- /dev/null +++ b/GCDTool/Elf/ElfSectionType.cs @@ -0,0 +1,29 @@ +namespace GCDTool.Elf; + +enum ElfSectionType : uint +{ + Null = 0, + Progbits = 1, + Symtab = 2, + Strtab = 3, + Rela = 4, + Hash = 5, + Dynamic = 6, + Note = 7, + Nobits = 8, + Rel = 9, + Shlib = 10, + Dynsym = 11, + InitArray = 14, + FiniArray = 15, + PreinitArray = 16, + Group = 17, + SymtabShndx = 18, + Num = 19, + Loos = 0x60000000, + Hios = 0x6fffffff, + Loproc = 0x70000000, + Hiproc = 0x7fffffff, + Louser = 0x80000000, + Hiuser = 0xffffffff +} \ No newline at end of file diff --git a/GCDTool/Elf/ProgramHeaderTableEntry.cs b/GCDTool/Elf/ProgramHeaderTableEntry.cs new file mode 100644 index 0000000..de04db1 --- /dev/null +++ b/GCDTool/Elf/ProgramHeaderTableEntry.cs @@ -0,0 +1,54 @@ +using GCDTool.IO; + +namespace GCDTool.Elf; + +sealed class ProgramHeaderTableEntry +{ + public enum ElfSegmentType : uint + { + Null = 0, + Load = 1, + Dynamic = 2, + Interp = 3, + Note = 4, + Shlib = 5, + Phdr = 6, + Tls = 7, + Num = 8, + Loos = 0x60000000, + Hios = 0x6fffffff, + Loproc = 0x70000000, + Hiproc = 0x7fffffff + } + + public ProgramHeaderTableEntry(EndianBinaryReader er) + { + SegmentType = er.Read(); + FileImageOffset = er.Read(); + VirtualAddress = er.Read(); + PhysicalAddress = er.Read(); + FileImageSize = er.Read(); + MemorySize = er.Read(); + Flags = er.Read(); + Alignment = er.Read(); + + if (FileImageSize != 0) + { + long curpos = er.BaseStream.Position; + er.BaseStream.Position = FileImageOffset; + SegmentData = er.Read((int)FileImageSize); + er.BaseStream.Position = curpos; + } + } + + public ElfSegmentType SegmentType; + public uint FileImageOffset; + public uint VirtualAddress; + public uint PhysicalAddress; + public uint FileImageSize; + public uint MemorySize; + public uint Flags; + public uint Alignment; + + public byte[] SegmentData = []; +} diff --git a/GCDTool/Elf/SectionHeaderTableEntry.cs b/GCDTool/Elf/SectionHeaderTableEntry.cs new file mode 100644 index 0000000..f7bc918 --- /dev/null +++ b/GCDTool/Elf/SectionHeaderTableEntry.cs @@ -0,0 +1,41 @@ +using GCDTool.IO; + +namespace GCDTool.Elf; + +sealed class SectionHeaderTableEntry +{ + public SectionHeaderTableEntry(EndianBinaryReader er) + { + NameOffset = er.Read(); + SectionType = er.Read(); + Flags = er.Read(); + VirtualAddress = er.Read(); + FileImageOffset = er.Read(); + FileImageSize = er.Read(); + Link = er.Read(); + Info = er.Read(); + Alignment = er.Read(); + EntrySize = er.Read(); + + if (FileImageSize != 0) + { + long curpos = er.BaseStream.Position; + er.BaseStream.Position = FileImageOffset; + SectionData = er.Read((int)FileImageSize); + er.BaseStream.Position = curpos; + } + } + + public uint NameOffset; + public ElfSectionType SectionType; + public uint Flags; + public uint VirtualAddress; + public uint FileImageOffset; + public uint FileImageSize; + public uint Link; + public uint Info; + public uint Alignment; + public uint EntrySize; + + public byte[] SectionData = []; +} diff --git a/GCDTool/Elf/Sections/ElfSection.cs b/GCDTool/Elf/Sections/ElfSection.cs new file mode 100644 index 0000000..0f115f1 --- /dev/null +++ b/GCDTool/Elf/Sections/ElfSection.cs @@ -0,0 +1,18 @@ +namespace GCDTool.Elf.Sections; + +class ElfSection +{ + public SectionHeaderTableEntry SectionHeader { get; } + public string Name { get; } + + public ElfSection(SectionHeaderTableEntry sectionHeader, string name) + { + SectionHeader = sectionHeader; + Name = name; + } + + public override string ToString() + { + return Name; + } +} diff --git a/GCDTool/Elf/Sections/ElfSectionFactory.cs b/GCDTool/Elf/Sections/ElfSectionFactory.cs new file mode 100644 index 0000000..a875973 --- /dev/null +++ b/GCDTool/Elf/Sections/ElfSectionFactory.cs @@ -0,0 +1,14 @@ +namespace GCDTool.Elf.Sections; + +sealed class ElfSectionFactory +{ + public ElfSection CreateElfSection(SectionHeaderTableEntry section, string name) + { + return section.SectionType switch + { + ElfSectionType.Strtab => new ElfStrtab(section, name), + ElfSectionType.Symtab => new ElfSymtab(section, name), + _ => new ElfSection(section, name), + }; + } +} \ No newline at end of file diff --git a/GCDTool/Elf/Sections/ElfStrtab.cs b/GCDTool/Elf/Sections/ElfStrtab.cs new file mode 100644 index 0000000..66ac448 --- /dev/null +++ b/GCDTool/Elf/Sections/ElfStrtab.cs @@ -0,0 +1,22 @@ +namespace GCDTool.Elf.Sections; + +sealed class ElfStrtab : ElfSection +{ + public ElfStrtab(SectionHeaderTableEntry section, string name) + : base(section, name) { } + + public string GetString(uint offset) + { + string result = string.Empty; + while (offset < SectionHeader.SectionData.Length) + { + char c = (char)SectionHeader.SectionData[offset++]; + if (c == '\0') + { + return result; + } + result += c; + } + return result; + } +} diff --git a/GCDTool/Elf/Sections/ElfSymbol.cs b/GCDTool/Elf/Sections/ElfSymbol.cs new file mode 100644 index 0000000..da09857 --- /dev/null +++ b/GCDTool/Elf/Sections/ElfSymbol.cs @@ -0,0 +1,23 @@ +using GCDTool.IO; + +namespace GCDTool.Elf.Sections; + +sealed class ElfSymbol +{ + public ElfSymbol(EndianBinaryReader er) + { + NameOffset = er.Read(); + Value = er.Read(); + Size = er.Read(); + Info = er.Read(); + Other = er.Read(); + SectionIndex = er.Read(); + } + + public uint NameOffset; + public uint Value; + public uint Size; + public byte Info; + public byte Other; + public ushort SectionIndex; +} \ No newline at end of file diff --git a/GCDTool/Elf/Sections/ElfSymtab.cs b/GCDTool/Elf/Sections/ElfSymtab.cs new file mode 100644 index 0000000..2a75c8e --- /dev/null +++ b/GCDTool/Elf/Sections/ElfSymtab.cs @@ -0,0 +1,22 @@ +using GCDTool.IO; + +namespace GCDTool.Elf.Sections; + +sealed class ElfSymtab : ElfSection +{ + public IReadOnlyList Symbols { get; } + + public ElfSymtab(SectionHeaderTableEntry section, string name) + : base(section, name) + { + uint nrEntries = section.FileImageSize / section.EntrySize; + var symbols = new ElfSymbol[nrEntries]; + var er = new EndianBinaryReader(new MemoryStream(section.SectionData), Endianness.LittleEndian); + for (int i = 0; i < nrEntries; i++) + { + symbols[i] = new ElfSymbol(er); + } + Symbols = symbols; + er.Close(); + } +} diff --git a/GCDTool/GCDTool.csproj b/GCDTool/GCDTool.csproj new file mode 100644 index 0000000..f301082 --- /dev/null +++ b/GCDTool/GCDTool.csproj @@ -0,0 +1,17 @@ + + + + Exe + net8.0 + enable + enable + Gericom + True + + + + + + + + diff --git a/GCDTool/GcdBlowfish.cs b/GCDTool/GcdBlowfish.cs new file mode 100644 index 0000000..62ca07a --- /dev/null +++ b/GCDTool/GcdBlowfish.cs @@ -0,0 +1,392 @@ +using GCDTool.IO; +using System.Buffers.Binary; + +namespace GCDTool; + +/// +/// Static helper class for the GCD blowfish table. +/// +static class GcdBlowfish +{ + /// + /// Creates the GCD blowfish table corresponding to the given . + /// + /// The game code. + /// The transformed blowfish table. + public static byte[] GetTransformedKeyTable(uint gameCode) + { + var keyTable = KeyTable.ToArray(); + uint keyCode = BinaryPrimitives.ReverseEndianness(gameCode); + for (int i = 0; i < Blowfish.P_TABLE_ENTRY_COUNT; i++) + { + IOUtil.WriteU32Le(keyTable, i * 4, + IOUtil.ReadU32Le(keyTable, i * 4) ^ keyCode); + } + + var scratch = new byte[8]; + for (int i = 0; i < Blowfish.KEY_TABLE_LENGTH; i += Blowfish.BLOCK_LENGTH) + { + var blowfish = new Blowfish(keyTable); + blowfish.Encrypt(scratch); + Array.Copy(scratch, 4, keyTable, i, 4); + Array.Copy(scratch, 0, keyTable, i + 4, 4); + } + + return keyTable; + } + + /// + /// The untransformed GCD blowfish table. + /// + public static ReadOnlySpan KeyTable => + [ + 0xEE, 0xA8, 0x95, 0x75, 0x2D, 0xF3, 0xFF, 0x84, 0xC6, 0xAE, 0xF8, 0x58, + 0xC2, 0x44, 0x64, 0x6F, 0xBC, 0xCF, 0xA6, 0x10, 0x13, 0xB8, 0xE1, 0xBE, + 0xC3, 0xAB, 0xEF, 0x88, 0xCD, 0x26, 0x20, 0xC7, 0x3A, 0x91, 0x0B, 0xC0, + 0xC0, 0x74, 0xB0, 0x9F, 0x0F, 0x83, 0xD4, 0x56, 0xE5, 0xDE, 0xAB, 0x69, + 0xF2, 0x5F, 0x6E, 0xCF, 0x2F, 0xBE, 0xFE, 0xD7, 0xE2, 0xD5, 0xF5, 0x84, + 0xDA, 0xCC, 0xA1, 0x73, 0x99, 0x59, 0x20, 0x44, 0x63, 0x8F, 0x27, 0x74, + 0x53, 0x72, 0x90, 0xF0, 0x8F, 0xD4, 0x95, 0x1C, 0x99, 0xCE, 0xDB, 0x7C, + 0x8A, 0x50, 0xB9, 0xA8, 0x9E, 0x9F, 0x37, 0x79, 0xFE, 0x44, 0x91, 0x12, + 0x4D, 0x55, 0xB3, 0xD2, 0xC6, 0x02, 0xD7, 0x72, 0x43, 0x81, 0x05, 0xCE, + 0xB8, 0x11, 0xB4, 0x72, 0xE7, 0x2A, 0xCF, 0x9A, 0x95, 0xD1, 0xC0, 0x62, + 0xE5, 0x61, 0x08, 0x7D, 0xF6, 0xC8, 0x3A, 0x33, 0x59, 0x7B, 0xC1, 0xAF, + 0x12, 0xAB, 0xAB, 0x7F, 0xDF, 0xB8, 0x20, 0x5A, 0xE1, 0x08, 0xC9, 0x43, + 0x54, 0x7B, 0x05, 0xDF, 0x17, 0x7D, 0x23, 0x65, 0x1C, 0x58, 0x88, 0x89, + 0xD9, 0xCA, 0x02, 0x1D, 0x8A, 0x3C, 0xE6, 0xDD, 0x12, 0xEE, 0x2F, 0x30, + 0x9D, 0xDD, 0xBE, 0x0A, 0x74, 0xB5, 0x6F, 0x58, 0xAD, 0x0A, 0x13, 0x10, + 0x35, 0xAD, 0x1E, 0x0A, 0x70, 0x08, 0x6F, 0xFE, 0x02, 0x4F, 0xAC, 0x8C, + 0x2C, 0xEF, 0x56, 0x9C, 0xCD, 0x9B, 0xAB, 0xC0, 0x52, 0x9A, 0xAF, 0x7F, + 0xCD, 0x2D, 0x82, 0xEF, 0xC2, 0xFD, 0xC0, 0x2B, 0x57, 0xA5, 0x22, 0xDE, + 0x67, 0x28, 0xF8, 0xCC, 0x19, 0x9D, 0x2A, 0x58, 0x15, 0x07, 0x79, 0xD2, + 0x89, 0xF6, 0x8D, 0xDC, 0xE2, 0xA6, 0x0C, 0x25, 0x6D, 0x23, 0x35, 0xFE, + 0xD6, 0xE2, 0x48, 0x86, 0xE7, 0xF6, 0x0B, 0xAA, 0x20, 0x06, 0x24, 0xD1, + 0xA7, 0x5E, 0x3A, 0x41, 0x43, 0x91, 0xF5, 0x4C, 0x72, 0x54, 0x4F, 0x4F, + 0x08, 0x9A, 0xF2, 0xA5, 0x8F, 0x4E, 0xFF, 0x1F, 0x2D, 0xEC, 0xF0, 0x14, + 0x2A, 0xF4, 0xD6, 0x47, 0xF0, 0x21, 0xB0, 0x85, 0x0A, 0xF2, 0x36, 0x78, + 0xD7, 0xD0, 0x08, 0xD9, 0xD5, 0x9D, 0xCC, 0xBD, 0xFB, 0x0B, 0xAC, 0xA6, + 0xAF, 0x7D, 0xFB, 0x96, 0xFF, 0x76, 0x54, 0xB6, 0x51, 0x9B, 0xE9, 0xBD, + 0x8E, 0x4B, 0xC8, 0xE8, 0x30, 0x86, 0xC8, 0x72, 0x79, 0x09, 0x8D, 0x3F, + 0xDC, 0x45, 0x1E, 0x5C, 0xDB, 0xB1, 0x55, 0x75, 0x90, 0x5A, 0xDB, 0x3E, + 0x66, 0xAC, 0x7F, 0xB0, 0xBA, 0x6A, 0x31, 0xF7, 0xBD, 0x88, 0xA0, 0x84, + 0x9D, 0xFB, 0xB2, 0xF0, 0xE1, 0x48, 0x4B, 0x01, 0xE3, 0x67, 0x09, 0x6D, + 0xE4, 0x60, 0x8D, 0xA3, 0xF2, 0xED, 0x8E, 0x14, 0x64, 0x88, 0x89, 0x81, + 0xAA, 0x73, 0x0D, 0xFE, 0xD5, 0x7B, 0xC3, 0x58, 0x45, 0xC3, 0xE2, 0x8C, + 0xE7, 0x1D, 0x95, 0x82, 0xA6, 0x1A, 0xBE, 0x17, 0x1B, 0xAD, 0xE8, 0xBF, + 0x76, 0x41, 0x6B, 0x4D, 0x3D, 0xA4, 0x0D, 0x3A, 0xAC, 0xDC, 0xA4, 0x50, + 0x2E, 0x28, 0xF1, 0x69, 0x76, 0x95, 0xB4, 0x16, 0xFE, 0x1F, 0x52, 0x6B, + 0x74, 0x81, 0x58, 0x67, 0x28, 0x1D, 0xDE, 0xB3, 0xBD, 0x8C, 0x19, 0x06, + 0xEA, 0x40, 0xA2, 0xEE, 0x8E, 0x35, 0x3E, 0xD1, 0x6C, 0xB0, 0x64, 0x36, + 0x27, 0xE2, 0xF4, 0x59, 0xD3, 0x22, 0x41, 0xB5, 0xE3, 0x71, 0xBB, 0x94, + 0x94, 0xFB, 0x15, 0x03, 0xD6, 0x01, 0x73, 0x64, 0x0D, 0x1F, 0x3F, 0x94, + 0xC4, 0xAE, 0x2A, 0x7A, 0xF7, 0x88, 0xBF, 0x51, 0x1A, 0x09, 0x2C, 0x71, + 0x3E, 0x6E, 0x3B, 0x6D, 0x52, 0x4D, 0xB2, 0x6E, 0xA4, 0x2C, 0xC8, 0x9F, + 0x8E, 0x18, 0xFC, 0x8F, 0x0A, 0x14, 0x31, 0xBE, 0x56, 0x57, 0x3E, 0x1D, + 0x6E, 0xE1, 0x74, 0xC5, 0x93, 0xA7, 0x05, 0xB1, 0xEC, 0x58, 0xF6, 0x94, + 0x4C, 0x86, 0xD4, 0x6E, 0xCF, 0xE3, 0xBA, 0xB2, 0x15, 0xCF, 0x42, 0xE3, + 0x27, 0x4D, 0xBC, 0xF2, 0xB1, 0x34, 0x03, 0x09, 0x42, 0x2A, 0x63, 0xC2, + 0x92, 0x00, 0x4F, 0x88, 0x51, 0xBD, 0x9A, 0xA5, 0xF9, 0x03, 0xD2, 0xE9, + 0x57, 0x22, 0x12, 0x7D, 0xAA, 0x09, 0xA7, 0x02, 0x9B, 0xC3, 0x9F, 0xAC, + 0xED, 0xBE, 0x40, 0x48, 0xA4, 0x37, 0xBE, 0x74, 0xD3, 0xA4, 0xB9, 0x24, + 0xA7, 0xBC, 0x78, 0x81, 0xE5, 0x85, 0x6B, 0x60, 0xB2, 0x46, 0xCB, 0x1D, + 0x60, 0x20, 0x11, 0xE6, 0x8A, 0x00, 0x44, 0xBD, 0x64, 0x29, 0xBE, 0xD1, + 0x32, 0x50, 0xCC, 0xCF, 0x43, 0x0E, 0xE1, 0x74, 0xAB, 0xC5, 0xFA, 0x53, + 0xA4, 0xE0, 0xA6, 0x84, 0x7A, 0x99, 0x80, 0x90, 0x00, 0x01, 0x1A, 0xA3, + 0x1C, 0x1C, 0xE2, 0xCE, 0x73, 0xE9, 0xCE, 0xEB, 0xC7, 0xA4, 0xE9, 0x77, + 0x9B, 0x1B, 0x26, 0x3A, 0x16, 0xCA, 0x49, 0xCE, 0x2D, 0x43, 0x3A, 0xEC, + 0xBF, 0xC1, 0x10, 0x14, 0x24, 0xC8, 0x22, 0x8D, 0x66, 0x82, 0x01, 0xB0, + 0xE2, 0xE2, 0x64, 0x79, 0x14, 0xEB, 0xC6, 0xFC, 0x35, 0xE0, 0xE6, 0x12, + 0x0A, 0x9D, 0x68, 0xCA, 0xFC, 0x39, 0x9F, 0x5F, 0xE9, 0xF7, 0x14, 0xBF, + 0xF2, 0x13, 0x78, 0x5D, 0x01, 0x74, 0x34, 0x87, 0x60, 0x76, 0x10, 0x1A, + 0xB7, 0x1B, 0xAA, 0xFE, 0xA2, 0xB1, 0xEB, 0x53, 0x57, 0xA4, 0x47, 0xFC, + 0x17, 0xDE, 0x1C, 0xF7, 0x36, 0x9A, 0x37, 0x25, 0x5D, 0x20, 0xED, 0xB2, + 0x36, 0xBF, 0x25, 0x12, 0xD4, 0x6B, 0x53, 0x14, 0xDF, 0x8C, 0x13, 0x31, + 0xF4, 0xB7, 0xEC, 0xEE, 0x19, 0x5B, 0x6E, 0xBB, 0x0F, 0x7D, 0x87, 0x39, + 0x24, 0xAC, 0x66, 0x43, 0x3C, 0x89, 0x13, 0xFB, 0xB2, 0x39, 0x70, 0x3A, + 0x37, 0x80, 0x1B, 0x3A, 0x34, 0x86, 0x66, 0xFC, 0x83, 0xB7, 0x3F, 0x0A, + 0x26, 0x05, 0x38, 0x50, 0x1A, 0x4B, 0x0E, 0x9C, 0x98, 0x93, 0x2B, 0x46, + 0x2D, 0x7D, 0x6C, 0x0E, 0x9E, 0x92, 0x9A, 0x68, 0xE6, 0x01, 0x57, 0x71, + 0x7D, 0xD1, 0x61, 0x2E, 0x45, 0x91, 0xDA, 0x09, 0xA1, 0x3B, 0x14, 0xFB, + 0x59, 0x92, 0x19, 0x5B, 0xEE, 0xA5, 0xE3, 0xC4, 0x9A, 0xDA, 0x4B, 0x0B, + 0x6C, 0x52, 0x8C, 0xE1, 0x1A, 0x1C, 0x17, 0x73, 0x4B, 0xA9, 0x72, 0x15, + 0x29, 0xB7, 0x15, 0x6D, 0x91, 0x9E, 0x50, 0x71, 0x6E, 0xC7, 0x1B, 0x57, + 0x18, 0x10, 0x72, 0x8C, 0x42, 0x17, 0xCC, 0x1F, 0x75, 0x2C, 0x60, 0xA6, + 0xA9, 0x88, 0x7F, 0x78, 0xC4, 0xF9, 0x98, 0x05, 0x1B, 0xC6, 0xDD, 0x5A, + 0x41, 0x7B, 0x9B, 0x8E, 0x72, 0xAF, 0x4D, 0x2F, 0x2A, 0x9E, 0xF0, 0xD9, + 0xCA, 0x54, 0x60, 0xE7, 0x4E, 0x0B, 0xBC, 0x28, 0x32, 0x0C, 0x32, 0x4A, + 0x7E, 0x68, 0x2B, 0xDF, 0xCC, 0xFB, 0xDF, 0x2C, 0x69, 0x6B, 0xF1, 0x77, + 0x65, 0x56, 0x42, 0x5D, 0xD9, 0x7F, 0x20, 0xCF, 0x6D, 0x16, 0x4D, 0x2E, + 0x57, 0x79, 0xE5, 0x46, 0x39, 0x0F, 0xF5, 0x37, 0x4A, 0xB7, 0x42, 0x67, + 0x47, 0xDF, 0x21, 0xBB, 0x64, 0x09, 0xDF, 0xE2, 0x89, 0xD9, 0xD6, 0x0D, + 0x6E, 0x5A, 0x63, 0x6F, 0x57, 0xA4, 0x05, 0x8F, 0x10, 0xC0, 0x5D, 0x6E, + 0x43, 0x6F, 0xE7, 0xEA, 0x6A, 0x20, 0x5C, 0x13, 0x38, 0xD9, 0x6B, 0x87, + 0x07, 0xC6, 0xCB, 0x1C, 0x28, 0x0E, 0x60, 0xAF, 0xE0, 0x4F, 0xAA, 0x0D, + 0x33, 0xFE, 0xE0, 0x95, 0xB1, 0x44, 0x3E, 0x09, 0xAD, 0xB2, 0x16, 0x1D, + 0xA2, 0x0B, 0x71, 0x8D, 0xD9, 0xC1, 0xA3, 0x3C, 0xE1, 0xA8, 0x54, 0x8F, + 0xC8, 0x1E, 0xB1, 0x7E, 0x0E, 0x2A, 0xB8, 0xCD, 0x6D, 0xFF, 0x1A, 0xDE, + 0xE2, 0x2C, 0xAE, 0x68, 0x3E, 0xCA, 0xE1, 0x80, 0x2C, 0x0C, 0xD6, 0x67, + 0xDA, 0xD1, 0x6A, 0xAC, 0x02, 0xC8, 0x30, 0x53, 0x4D, 0xA0, 0x67, 0x5F, + 0x9D, 0x3C, 0x6F, 0x5D, 0xB0, 0x25, 0x47, 0x1F, 0x69, 0x4C, 0x4A, 0x09, + 0x21, 0xEA, 0x1D, 0x4C, 0xD5, 0xCE, 0x09, 0xFD, 0x41, 0xC3, 0x0F, 0x27, + 0xF5, 0x81, 0x60, 0xF1, 0xB4, 0xBD, 0xDC, 0x54, 0xF5, 0x6B, 0xA3, 0x72, + 0x6A, 0xC1, 0xAB, 0xBE, 0x02, 0xBB, 0x82, 0xDD, 0x20, 0x5D, 0xE7, 0xF0, + 0xB0, 0x64, 0x6A, 0xEA, 0x2C, 0x5C, 0xCB, 0x85, 0xAA, 0x5A, 0x3A, 0xD5, + 0xD1, 0x91, 0x0F, 0x40, 0x5E, 0x69, 0x20, 0xC0, 0x76, 0x5C, 0xD0, 0xA3, + 0x46, 0x04, 0xC0, 0x8A, 0xCE, 0xE1, 0xEB, 0xE0, 0x13, 0xCA, 0xD2, 0x7E, + 0xCA, 0xBB, 0x41, 0x7F, 0xC0, 0xFF, 0xDA, 0x09, 0x09, 0x9C, 0xD3, 0x16, + 0x9E, 0xFE, 0x17, 0xD4, 0x36, 0x4B, 0xFC, 0x11, 0x11, 0x21, 0xB9, 0x87, + 0x89, 0xEA, 0x1C, 0x58, 0x6D, 0x51, 0x51, 0x6A, 0x94, 0xF9, 0x91, 0xA6, + 0xAB, 0xC6, 0xE5, 0x06, 0x80, 0x07, 0x20, 0x81, 0xB7, 0x60, 0xEA, 0xE6, + 0xAF, 0x67, 0x09, 0x8D, 0x79, 0x1D, 0xF8, 0x32, 0x78, 0x3B, 0xF6, 0x1B, + 0xBB, 0x70, 0xFE, 0xEE, 0x42, 0x3B, 0x12, 0x74, 0x16, 0x5B, 0x34, 0xF7, + 0x88, 0x13, 0xAA, 0x7A, 0x94, 0xA3, 0x52, 0x01, 0xA1, 0x3E, 0x46, 0xB5, + 0x33, 0x86, 0x96, 0xE7, 0x17, 0x0F, 0xB0, 0xF6, 0x26, 0x59, 0xE6, 0xFE, + 0x88, 0x5F, 0xC9, 0xFA, 0x38, 0x88, 0x78, 0xF7, 0xC8, 0x27, 0x4D, 0x40, + 0x5A, 0x72, 0x40, 0x0A, 0x18, 0x86, 0x94, 0xAC, 0x6F, 0x96, 0xFB, 0xB2, + 0xC3, 0x40, 0x0B, 0x73, 0xA0, 0xB3, 0xA5, 0x3F, 0x57, 0x7B, 0x4A, 0x2A, + 0x94, 0x09, 0xEE, 0xE2, 0x03, 0x2B, 0x66, 0xE2, 0x53, 0xC2, 0x17, 0xA5, + 0xC2, 0x9A, 0xCD, 0x5F, 0x1A, 0x86, 0xF1, 0x7C, 0x92, 0x6D, 0x9C, 0xFB, + 0xF6, 0xFC, 0x6C, 0x72, 0x55, 0x08, 0xC2, 0x6B, 0x57, 0x77, 0x3C, 0xE0, + 0xFC, 0xAE, 0xF7, 0x15, 0xB2, 0xF4, 0x90, 0x04, 0x9E, 0x9D, 0xC4, 0xC5, + 0x03, 0xC4, 0x1B, 0xD9, 0x16, 0x90, 0x79, 0x84, 0x2F, 0x5D, 0x8B, 0x5E, + 0x68, 0xEF, 0x8A, 0xDD, 0x7A, 0xB8, 0x25, 0x42, 0x70, 0x27, 0x91, 0xD3, + 0xF2, 0x4D, 0xF8, 0x56, 0x69, 0xD4, 0x91, 0xE1, 0x14, 0x65, 0x30, 0xB6, + 0x61, 0x74, 0x6B, 0xF6, 0x42, 0x8B, 0x52, 0xAA, 0x11, 0xD2, 0xD8, 0xD7, + 0x09, 0xE8, 0x09, 0x04, 0x89, 0x54, 0x99, 0x3E, 0xD0, 0x2C, 0x4E, 0x98, + 0x26, 0x87, 0xDA, 0x21, 0xCE, 0xDC, 0x01, 0xA9, 0x27, 0xD6, 0x9E, 0xC5, + 0x95, 0xA3, 0x08, 0xA5, 0x10, 0x1C, 0x7E, 0xE6, 0x94, 0xC5, 0x86, 0x77, + 0x12, 0x9C, 0xB4, 0x7D, 0xFA, 0x48, 0xA0, 0xF3, 0x39, 0xC9, 0x03, 0x6C, + 0xB2, 0x38, 0xE5, 0x5F, 0xF1, 0x94, 0x8D, 0xE6, 0x18, 0x68, 0x11, 0x74, + 0x38, 0x1B, 0x04, 0xBC, 0x9F, 0xF5, 0xE9, 0x37, 0xB4, 0xD6, 0xF0, 0xD5, + 0x09, 0xDC, 0xBE, 0x3A, 0xCF, 0x7F, 0x27, 0xF2, 0xCA, 0x90, 0xC0, 0xB1, + 0xAF, 0xBE, 0x9F, 0x17, 0x69, 0xF2, 0x99, 0x67, 0xBA, 0xE1, 0xF2, 0x4E, + 0xFE, 0x30, 0x85, 0x9A, 0xD3, 0xBA, 0x29, 0xE6, 0x06, 0x18, 0x2D, 0xEF, + 0x1A, 0xD4, 0xE0, 0xEF, 0x38, 0x80, 0xBD, 0x8F, 0xB8, 0xB3, 0x93, 0xAE, + 0x9A, 0xF6, 0x6A, 0xB9, 0x0A, 0xF3, 0x76, 0xE9, 0xF2, 0xED, 0x35, 0x37, + 0x80, 0xAB, 0x0A, 0xA1, 0x46, 0xC3, 0x2B, 0x57, 0xE5, 0x52, 0xD3, 0xD9, + 0xFB, 0x82, 0x83, 0xA8, 0x6A, 0x9A, 0x6D, 0xC3, 0xF5, 0x69, 0x21, 0x62, + 0x47, 0x81, 0x66, 0x2B, 0x94, 0x8E, 0x8D, 0xC6, 0xDF, 0x25, 0x37, 0xD8, + 0xC0, 0x8D, 0xCA, 0x75, 0x7D, 0xF0, 0xFE, 0xE0, 0x8F, 0x34, 0x20, 0x30, + 0xC5, 0x41, 0xE9, 0xD7, 0x00, 0xDA, 0x18, 0x0F, 0xCA, 0xCA, 0x09, 0x99, + 0x03, 0x4C, 0x5F, 0x1B, 0x6E, 0xA6, 0x1B, 0x61, 0x8D, 0x5C, 0xC2, 0xE3, + 0x54, 0x16, 0xFE, 0x87, 0x9E, 0x09, 0xED, 0x20, 0x51, 0x62, 0xEF, 0x2E, + 0x47, 0x27, 0xB6, 0xF8, 0x16, 0x37, 0x86, 0xA8, 0x0A, 0x4E, 0x65, 0x2E, + 0x15, 0x55, 0x98, 0x17, 0xF0, 0xA7, 0x21, 0xD1, 0x5B, 0x5A, 0x54, 0xF0, + 0xA3, 0x9D, 0x9F, 0xED, 0x10, 0x7B, 0x60, 0xFE, 0x9B, 0x59, 0xFC, 0x3A, + 0x05, 0x33, 0xC1, 0xA3, 0x0A, 0xA2, 0x67, 0x96, 0x0C, 0x9F, 0x63, 0xBD, + 0xA0, 0xE3, 0x47, 0xE6, 0xB2, 0x04, 0x13, 0x0E, 0xEC, 0x1C, 0xA3, 0x26, + 0x73, 0x52, 0x43, 0xD1, 0x41, 0x53, 0x84, 0x33, 0xF5, 0x83, 0x33, 0xBE, + 0x7F, 0xE0, 0xE5, 0x22, 0xD2, 0xAE, 0x16, 0xB5, 0xC1, 0xD3, 0xDA, 0xE7, + 0xEC, 0x2E, 0x0B, 0x7E, 0x35, 0xD2, 0x09, 0x77, 0x97, 0x33, 0x92, 0x48, + 0xA6, 0x62, 0x6B, 0x08, 0x43, 0x08, 0x27, 0x05, 0x32, 0x3D, 0x07, 0x48, + 0x8E, 0xDB, 0x22, 0x3E, 0xEB, 0xD2, 0x0F, 0x7B, 0x1D, 0x53, 0x99, 0x91, + 0xFC, 0x9E, 0x7E, 0xA4, 0x31, 0xBF, 0xB1, 0x76, 0x31, 0x6F, 0x7B, 0xEE, + 0xC0, 0xE2, 0x6D, 0xDC, 0x2C, 0x4D, 0x0D, 0x12, 0x8B, 0x1A, 0x7D, 0x1C, + 0x42, 0x21, 0x19, 0xD9, 0x66, 0xCE, 0x51, 0x52, 0xCF, 0x0A, 0xDD, 0x26, + 0x04, 0x3F, 0x07, 0x18, 0xA3, 0xF0, 0x01, 0x59, 0xA0, 0xFE, 0x24, 0xFC, + 0x4B, 0x47, 0xD4, 0xE9, 0xDD, 0x7B, 0xDA, 0xBB, 0xF6, 0x45, 0xCB, 0x66, + 0x90, 0x7A, 0x13, 0x66, 0x36, 0xA0, 0x10, 0xA0, 0x6C, 0xF9, 0xFF, 0xA3, + 0x4F, 0x19, 0xC8, 0x61, 0x2B, 0x0B, 0x3E, 0x55, 0xE2, 0x4D, 0xB5, 0x72, + 0xE9, 0xEF, 0xAE, 0x59, 0xB4, 0xDA, 0x01, 0xDA, 0x91, 0x8B, 0xC4, 0xDA, + 0x7A, 0x6E, 0xA6, 0xBB, 0xE2, 0x9D, 0x81, 0xA3, 0xCE, 0x0F, 0x95, 0x1F, + 0x97, 0xD6, 0xC4, 0xA3, 0xCE, 0x8D, 0xE2, 0xC4, 0xEE, 0x79, 0x01, 0x53, + 0x68, 0xC0, 0x7C, 0xAA, 0x6A, 0x14, 0x44, 0x1B, 0x09, 0xD2, 0x67, 0xB2, + 0xBE, 0xF1, 0x4F, 0x98, 0x6B, 0x79, 0xFD, 0x90, 0x5F, 0x2A, 0xE4, 0xE0, + 0xFB, 0x33, 0xD0, 0x36, 0x82, 0xF4, 0xC2, 0x13, 0x6D, 0xA8, 0x19, 0x3B, + 0x56, 0xD2, 0x0A, 0xFF, 0x47, 0xCB, 0x6A, 0xF0, 0xA0, 0xBF, 0x4F, 0x0D, + 0xD3, 0x6F, 0x24, 0x53, 0xD5, 0x6D, 0x29, 0xF9, 0x4A, 0x5D, 0xBB, 0x42, + 0x75, 0x6E, 0xD8, 0xD4, 0x50, 0x26, 0x44, 0x96, 0x51, 0xBC, 0xDE, 0x0B, + 0x8D, 0xE9, 0xCE, 0xB3, 0xE2, 0x83, 0x8C, 0x18, 0x50, 0x28, 0x63, 0xBE, + 0x59, 0xE6, 0x0C, 0x9A, 0xCD, 0x50, 0x12, 0xC5, 0x77, 0x9F, 0x1B, 0x14, + 0xD4, 0x44, 0x28, 0x29, 0xD4, 0x50, 0xCE, 0xA8, 0xA3, 0x71, 0xF4, 0xE6, + 0xF9, 0x7E, 0x1B, 0xE5, 0x79, 0x3B, 0xE3, 0xEC, 0x6A, 0x7A, 0xC1, 0x1E, + 0x5E, 0x12, 0xA0, 0x01, 0x04, 0x1E, 0x07, 0x09, 0xF4, 0x2D, 0xE7, 0xBF, + 0xCF, 0xD1, 0x0E, 0x17, 0xAC, 0x59, 0xA2, 0x2F, 0x4F, 0x70, 0x21, 0x48, + 0x04, 0x27, 0xB8, 0x95, 0x6A, 0x0C, 0x1E, 0xCD, 0xCE, 0x33, 0x7A, 0xB5, + 0xFE, 0x04, 0x72, 0x05, 0x6B, 0xDE, 0x17, 0x76, 0xD5, 0xF1, 0x72, 0xAA, + 0x1A, 0xD6, 0x46, 0x4B, 0x78, 0x73, 0x79, 0xAC, 0xCF, 0xD9, 0x84, 0x49, + 0x22, 0x60, 0x72, 0x40, 0x5C, 0x5B, 0x42, 0x2B, 0xAB, 0x5D, 0xA2, 0x4C, + 0xA3, 0x3A, 0xE2, 0xA0, 0x7A, 0x52, 0xE1, 0x4F, 0xAC, 0xB7, 0xD3, 0x6F, + 0x60, 0xBA, 0x47, 0xEB, 0xB7, 0xC8, 0xE7, 0xCD, 0x0F, 0x5D, 0xEF, 0x9A, + 0xCD, 0x74, 0x16, 0x82, 0x7D, 0xEA, 0x58, 0xC2, 0xA5, 0x13, 0xA8, 0xA6, + 0x84, 0x8A, 0xE8, 0x93, 0xE3, 0xD2, 0x32, 0x8F, 0x0D, 0x44, 0xAE, 0x58, + 0x15, 0x97, 0x79, 0xC5, 0xD0, 0x84, 0x52, 0x42, 0x64, 0x6E, 0x69, 0x1B, + 0x3A, 0xE9, 0x3C, 0x7D, 0x5C, 0xF2, 0xD0, 0xDA, 0x14, 0xDD, 0xB0, 0xC4, + 0xE5, 0xE2, 0x79, 0x70, 0x1D, 0xE6, 0xE8, 0xA9, 0xB7, 0x86, 0xF6, 0xFA, + 0x7B, 0xB8, 0xF1, 0x1C, 0xC7, 0x43, 0xAB, 0x31, 0xBB, 0xD1, 0x45, 0xDC, + 0xEC, 0xB6, 0x6C, 0x38, 0xF2, 0x83, 0xD2, 0xCA, 0xAF, 0xBF, 0xCC, 0x4D, + 0x8B, 0xF2, 0x34, 0x49, 0xD4, 0x3D, 0xBC, 0x2F, 0xF7, 0x64, 0x9B, 0x78, + 0x8F, 0x91, 0xE8, 0xB2, 0xF7, 0xCB, 0x6A, 0x50, 0x60, 0x33, 0xA1, 0x50, + 0xD3, 0xD4, 0x28, 0xD1, 0x26, 0x75, 0x68, 0xBA, 0x40, 0x49, 0x47, 0xC5, + 0xC7, 0xDF, 0xB5, 0x6B, 0x1F, 0xDF, 0x9B, 0xEF, 0x3B, 0x64, 0xCC, 0x92, + 0xF0, 0xD4, 0x2A, 0xFB, 0x0B, 0x41, 0xF9, 0x4F, 0x1E, 0xC8, 0x13, 0xB0, + 0x74, 0x95, 0xE6, 0x9F, 0x88, 0x0D, 0xED, 0x5F, 0x20, 0x77, 0xE8, 0xCF, + 0x06, 0xDF, 0xCD, 0x09, 0x6F, 0x69, 0x57, 0x9E, 0xB9, 0xDF, 0x29, 0xC4, + 0xD2, 0x5D, 0x0C, 0x46, 0x5B, 0x50, 0x9E, 0x9B, 0xF6, 0xBC, 0x12, 0xC0, + 0xDA, 0xBD, 0x55, 0xD9, 0x31, 0xED, 0x10, 0x73, 0xE2, 0xEB, 0xAA, 0xCD, + 0xF6, 0x1F, 0x76, 0xC9, 0x45, 0x0F, 0xE0, 0x5D, 0x49, 0xE9, 0xA5, 0x66, + 0x1D, 0xDF, 0xC5, 0x45, 0xAB, 0x8F, 0xA3, 0x96, 0x5B, 0x9D, 0x93, 0x18, + 0x83, 0x50, 0x01, 0x94, 0x79, 0x13, 0x7B, 0xD2, 0xE7, 0x07, 0x01, 0xE1, + 0xF8, 0x6E, 0xEF, 0xDA, 0xB3, 0x83, 0x8F, 0xAF, 0x29, 0x70, 0xD3, 0xAF, + 0xE0, 0x03, 0x68, 0x7D, 0x55, 0x50, 0x54, 0x3A, 0x25, 0x99, 0x41, 0xFD, + 0x5F, 0xA2, 0x9F, 0xB4, 0xF4, 0xD3, 0x3A, 0x53, 0xE4, 0x3F, 0x9F, 0xC4, + 0x96, 0xB1, 0x9E, 0x9B, 0x8E, 0x40, 0x15, 0x20, 0x85, 0x64, 0xF4, 0xD6, + 0x79, 0x64, 0xE6, 0xA8, 0xE1, 0x66, 0x88, 0x5B, 0x8D, 0x47, 0x7D, 0xC9, + 0x0A, 0xB3, 0xFC, 0xB7, 0xF0, 0xDF, 0xEF, 0xDD, 0x4C, 0xB9, 0xAF, 0x34, + 0x2F, 0xD9, 0x55, 0xA1, 0xB1, 0x79, 0x39, 0xF5, 0x75, 0xD0, 0xCD, 0xF0, + 0xAE, 0xCC, 0x5A, 0xF2, 0xFD, 0x8A, 0x20, 0xE4, 0x77, 0xBB, 0x11, 0x5B, + 0xE4, 0x37, 0xB3, 0x37, 0xBE, 0xA8, 0x9D, 0xA6, 0x86, 0xEE, 0xA9, 0x28, + 0x0E, 0xB3, 0x84, 0xB2, 0x98, 0x75, 0x52, 0xC3, 0x6E, 0x93, 0x23, 0xF4, + 0x51, 0x1D, 0x2E, 0x7E, 0x3D, 0x61, 0xF2, 0xCA, 0x1E, 0x39, 0x05, 0x75, + 0xD5, 0x3D, 0x2D, 0x25, 0x03, 0x82, 0xD1, 0x00, 0x7E, 0x57, 0xF4, 0x7E, + 0xA9, 0x98, 0x4E, 0x24, 0x40, 0x58, 0x3B, 0xB7, 0xED, 0xC9, 0xF9, 0xB0, + 0x23, 0xBC, 0xD4, 0xA8, 0x43, 0x82, 0x22, 0xB7, 0xD3, 0x18, 0x14, 0x24, + 0x29, 0x44, 0xFD, 0x39, 0x37, 0x9B, 0xAB, 0xE8, 0xDF, 0xEC, 0x74, 0x3A, + 0x9C, 0x49, 0x99, 0xBF, 0x26, 0xEB, 0xC3, 0x70, 0x10, 0x5D, 0xAC, 0xC6, + 0x0A, 0x41, 0x85, 0x70, 0x24, 0xCA, 0xC5, 0x9B, 0x32, 0x4D, 0xCF, 0x37, + 0xF1, 0x7A, 0x40, 0x5A, 0xAF, 0xC2, 0x38, 0x7F, 0x26, 0x57, 0xF9, 0x6C, + 0xE9, 0xCC, 0x34, 0x29, 0x5E, 0x4A, 0x8E, 0x47, 0x71, 0xCD, 0xBC, 0x86, + 0x04, 0x38, 0x67, 0x46, 0xDA, 0xE9, 0xD7, 0x5C, 0x47, 0x0E, 0x1C, 0x60, + 0x5C, 0xC9, 0xF3, 0x32, 0x07, 0xB2, 0xF3, 0x98, 0xF2, 0x09, 0x9F, 0x2A, + 0x9B, 0x3B, 0x8E, 0xA6, 0x44, 0x7F, 0xA6, 0xE0, 0xEB, 0x01, 0x48, 0x8D, + 0x00, 0x09, 0x65, 0x0D, 0x8F, 0x9D, 0xBD, 0x2D, 0x47, 0xFE, 0x13, 0x91, + 0x0E, 0x7B, 0x31, 0x5A, 0x71, 0x8D, 0x5A, 0x3D, 0x45, 0x0E, 0xD6, 0xFB, + 0xDC, 0xEE, 0xF6, 0x86, 0xC6, 0xFE, 0x77, 0x3F, 0xB2, 0x0B, 0x6A, 0xE5, + 0xF7, 0x5E, 0xAB, 0x32, 0x29, 0xC4, 0x62, 0x22, 0xC6, 0x87, 0xD3, 0x7E, + 0x7E, 0xC8, 0x87, 0x00, 0xB7, 0xE4, 0x70, 0x65, 0xE1, 0x80, 0x8D, 0x64, + 0x1A, 0x76, 0xBD, 0xC3, 0x47, 0x0C, 0x3C, 0x5C, 0x7C, 0xB0, 0xC5, 0x9A, + 0x8F, 0x15, 0xCB, 0x6E, 0x23, 0xD3, 0xE2, 0x73, 0x5E, 0x43, 0x95, 0x50, + 0xE4, 0x7E, 0xA9, 0x21, 0x19, 0x95, 0x59, 0x0C, 0x41, 0x64, 0x6C, 0x1F, + 0xD7, 0xEF, 0x52, 0xEE, 0xE2, 0x6E, 0x88, 0xBD, 0x66, 0x3C, 0xEC, 0x66, + 0x0C, 0x82, 0x42, 0x0E, 0xCB, 0x44, 0xF2, 0xB4, 0xFC, 0x2E, 0x6F, 0xA5, + 0x16, 0x2F, 0x30, 0xC0, 0xAA, 0x95, 0xB7, 0xF9, 0x3F, 0x0F, 0xA0, 0x60, + 0xA9, 0xB0, 0x3F, 0xA8, 0x24, 0xF7, 0xB2, 0xE9, 0x4F, 0xB4, 0xC3, 0xA3, + 0x80, 0xCC, 0x51, 0x81, 0x9C, 0xE9, 0xE0, 0x2A, 0x00, 0xFF, 0xB0, 0x0C, + 0x6D, 0x64, 0x9A, 0x2E, 0xD6, 0x2C, 0x1D, 0x99, 0x6B, 0xF1, 0x2B, 0xEF, + 0xD2, 0x0A, 0x61, 0x0A, 0x07, 0xEA, 0x74, 0x16, 0xE7, 0xCF, 0x7F, 0x56, + 0xAF, 0xF5, 0x5E, 0xF0, 0xCB, 0x47, 0xDF, 0xDF, 0x59, 0xBB, 0x3E, 0x6C, + 0xAD, 0x2C, 0x05, 0x2F, 0x04, 0xCA, 0x01, 0x47, 0x4A, 0x16, 0x65, 0x0C, + 0xB3, 0xEC, 0x85, 0xA2, 0x0A, 0xD2, 0x8F, 0x34, 0xFF, 0xF8, 0x15, 0x79, + 0x33, 0x9D, 0x26, 0xDC, 0x72, 0x8F, 0x57, 0x74, 0x80, 0xED, 0x3C, 0x76, + 0x51, 0x59, 0x27, 0x53, 0xBD, 0xEE, 0x51, 0x8C, 0x52, 0x44, 0x8B, 0x6D, + 0x4E, 0xC3, 0x67, 0x52, 0x7B, 0x19, 0xB7, 0xE9, 0xAB, 0x00, 0x28, 0x91, + 0xA4, 0x07, 0xD2, 0x10, 0xF7, 0xEA, 0x38, 0x42, 0x12, 0xBD, 0x71, 0x21, + 0x0B, 0xFF, 0x7E, 0x9C, 0xFF, 0x79, 0x1B, 0x25, 0x53, 0x77, 0xBB, 0x41, + 0x83, 0x65, 0xEA, 0xEC, 0xE5, 0x93, 0x13, 0x03, 0xE9, 0x8F, 0xA5, 0x49, + 0x87, 0x75, 0xBB, 0x83, 0x56, 0x31, 0x2A, 0x4A, 0xA7, 0x8D, 0xAB, 0xA3, + 0xAA, 0x74, 0xF7, 0x38, 0x4E, 0x4D, 0x84, 0xD3, 0x18, 0x14, 0xC8, 0x7C, + 0x9D, 0xA0, 0x4C, 0x89, 0x56, 0xDE, 0x1A, 0x34, 0xA5, 0xD6, 0x74, 0x95, + 0x51, 0x97, 0x29, 0xD2, 0x93, 0x32, 0xD4, 0xDB, 0xE8, 0x53, 0x3D, 0x25, + 0xC4, 0x5C, 0x00, 0x64, 0x6F, 0xE5, 0x3D, 0x81, 0xDA, 0x3C, 0x1C, 0x63, + 0xBD, 0x81, 0x16, 0x93, 0xB8, 0x0A, 0xDD, 0x77, 0x6F, 0xB5, 0xD6, 0x15, + 0xB1, 0x06, 0x31, 0x2A, 0xBF, 0x02, 0x31, 0x60, 0x3D, 0x9A, 0xF2, 0xFE, + 0x38, 0xAA, 0x8B, 0x64, 0x91, 0xE7, 0x0A, 0x47, 0xAB, 0x25, 0xE6, 0x02, + 0x5D, 0x4D, 0x90, 0x04, 0xA4, 0xCE, 0x31, 0x3B, 0x6D, 0x12, 0xA2, 0xA9, + 0x75, 0x45, 0x81, 0x76, 0x11, 0x55, 0x6A, 0x4C, 0x19, 0xC6, 0xD7, 0xE2, + 0xA5, 0x8C, 0x0D, 0x84, 0xF8, 0xE8, 0x15, 0x4D, 0x95, 0x6A, 0x25, 0x59, + 0x56, 0x6A, 0xED, 0xF1, 0xAB, 0xEF, 0x96, 0xB9, 0x13, 0xC9, 0xA9, 0x5D, + 0xD8, 0xBE, 0x52, 0x76, 0xBE, 0xA0, 0xBD, 0xED, 0x08, 0x19, 0xF8, 0x59, + 0xD7, 0x5E, 0xD4, 0xD1, 0x76, 0xF8, 0xB5, 0x00, 0xE4, 0x97, 0x6B, 0x98, + 0x62, 0x98, 0xF9, 0x0B, 0xC6, 0x48, 0xA4, 0xF3, 0x73, 0x25, 0xF9, 0x05, + 0xC4, 0xC7, 0xB2, 0x9A, 0xF2, 0xBB, 0x92, 0xBF, 0x36, 0xE0, 0xE5, 0xA8, + 0x2B, 0xA0, 0x30, 0x30, 0x3B, 0x83, 0x1E, 0xAB, 0x39, 0xD0, 0x90, 0x51, + 0xC2, 0xEE, 0x31, 0x3D, 0x27, 0xF4, 0xAD, 0x4E, 0x17, 0xD2, 0x65, 0xEA, + 0xD3, 0x4D, 0xAC, 0xAF, 0xFE, 0xF6, 0x5E, 0xDF, 0x6C, 0xDD, 0x8C, 0xE8, + 0xDF, 0xD1, 0x55, 0xAB, 0x8B, 0x9E, 0x68, 0x77, 0x81, 0xB0, 0xA6, 0x3B, + 0x08, 0x79, 0xD3, 0x9E, 0x78, 0x36, 0x38, 0x42, 0xA6, 0x21, 0xF2, 0xF9, + 0x5D, 0x35, 0xCF, 0xB6, 0x09, 0xAA, 0x10, 0x1C, 0x86, 0x54, 0x64, 0x38, + 0x0E, 0x32, 0x46, 0xAB, 0xCE, 0xBD, 0x01, 0x6C, 0xBF, 0x77, 0x9B, 0xAA, + 0x85, 0x70, 0x8A, 0xDC, 0xC0, 0x40, 0x3C, 0x84, 0x12, 0x8B, 0x21, 0x35, + 0x35, 0x5A, 0xC1, 0x57, 0xD4, 0x79, 0xA8, 0x37, 0x71, 0x86, 0xFA, 0x41, + 0xF3, 0xE4, 0xBA, 0xE9, 0x23, 0xC9, 0x21, 0x95, 0xB3, 0xBA, 0x32, 0x2A, + 0x6B, 0xB4, 0x15, 0x54, 0xDD, 0x45, 0x72, 0xCB, 0xBD, 0x07, 0x64, 0x6E, + 0x82, 0x77, 0x47, 0x88, 0xA6, 0x4E, 0x42, 0x18, 0x27, 0x44, 0x21, 0x48, + 0xB8, 0xB8, 0x66, 0xD2, 0xD4, 0x96, 0xB7, 0x70, 0x23, 0x82, 0xE4, 0xE4, + 0x45, 0xBF, 0xA5, 0xF6, 0x00, 0xC4, 0x4C, 0xA3, 0x1C, 0xF2, 0x62, 0x0B, + 0xA7, 0x6C, 0xEB, 0xF0, 0xA3, 0x66, 0x85, 0x4F, 0x04, 0x59, 0x3E, 0xD2, + 0xFD, 0xC0, 0xEA, 0x86, 0x7D, 0x0A, 0x08, 0x95, 0x24, 0x6A, 0x92, 0xE2, + 0xA1, 0xC4, 0x83, 0x99, 0x84, 0x8B, 0x39, 0x7E, 0xF1, 0xB2, 0xCC, 0x9F, + 0x6B, 0x69, 0x70, 0xFF, 0xF7, 0x7A, 0xB4, 0xCC, 0x96, 0xBF, 0xD3, 0x61, + 0x85, 0x03, 0x17, 0xF4, 0xEF, 0x6F, 0xC8, 0x0B, 0x90, 0x5C, 0xFB, 0x06, + 0xFE, 0xD9, 0x92, 0xCE, 0xCF, 0xA3, 0x0C, 0x12, 0x59, 0x2F, 0xDE, 0xDC, + 0x38, 0x2B, 0xFD, 0xD6, 0x74, 0xD9, 0x58, 0x42, 0x3C, 0x0A, 0x2C, 0x4B, + 0xEC, 0x67, 0x09, 0x1F, 0x51, 0x7E, 0x98, 0x25, 0xC7, 0xCF, 0x7A, 0x4E, + 0x94, 0x96, 0x7F, 0x1F, 0x30, 0x32, 0xDE, 0x18, 0x5D, 0x09, 0xD9, 0x85, + 0x16, 0x76, 0x73, 0x54, 0x56, 0x69, 0xC2, 0x6E, 0x7B, 0x63, 0xB3, 0x9E, + 0x92, 0x51, 0x8A, 0x90, 0x3E, 0xB4, 0x9F, 0x83, 0x2A, 0x0F, 0xE7, 0xEF, + 0xA5, 0x68, 0xE1, 0x42, 0x22, 0x5C, 0x6E, 0x77, 0xA9, 0xE3, 0x6B, 0x73, + 0xED, 0x66, 0x2D, 0xB7, 0x94, 0x47, 0xF9, 0xA4, 0x55, 0xBD, 0x14, 0xB4, + 0xD3, 0x23, 0xE6, 0x5D, 0xA9, 0xE1, 0xD1, 0x9C, 0x7E, 0x7F, 0x95, 0x42, + 0x93, 0xF5, 0x1C, 0x38, 0x07, 0x7D, 0x8A, 0x67, 0x04, 0xB6, 0x8B, 0x15, + 0xBD, 0x26, 0x49, 0xE6, 0x38, 0x74, 0x04, 0x5A, 0xCB, 0x68, 0x0D, 0x36, + 0x8E, 0x7A, 0x48, 0xAB, 0x0B, 0x9A, 0x76, 0x0D, 0x39, 0xC9, 0x3F, 0xDE, + 0xC0, 0xA9, 0x3E, 0xF3, 0x55, 0x74, 0xB8, 0x28, 0x0C, 0xC0, 0xF6, 0xD5, + 0xFE, 0x8F, 0x3F, 0x85, 0xC3, 0x38, 0xD2, 0xC8, 0x3E, 0x47, 0xB5, 0x3B, + 0x97, 0x90, 0x77, 0xC9, 0x0C, 0x3A, 0xAF, 0xC4, 0xC0, 0x98, 0x90, 0x89, + 0xE4, 0xC0, 0x51, 0x3C, 0x4E, 0x21, 0x02, 0x7E, 0xA4, 0x85, 0x5F, 0xE6, + 0x57, 0xA8, 0xC4, 0xE7, 0x9E, 0x17, 0x96, 0x60, 0x59, 0xB5, 0x9E, 0x9C, + 0x7D, 0x5F, 0x9A, 0x70, 0xD5, 0x6F, 0x8F, 0x54, 0xCB, 0x58, 0xAD, 0xF1, + 0x83, 0xE7, 0xBC, 0xE4, 0x6C, 0x2B, 0xCA, 0x43, 0x8F, 0xDE, 0x43, 0x6F, + 0xF9, 0x02, 0x0A, 0xA6, 0x35, 0x0F, 0xDD, 0xBC, 0xB3, 0x82, 0xF5, 0x58, + 0x0D, 0x3B, 0x63, 0x30, 0x49, 0x7D, 0x7C, 0xF1, 0xF7, 0x09, 0xF8, 0x35, + 0x17, 0x04, 0xE9, 0x9E, 0x4F, 0x67, 0x60, 0x63, 0x7B, 0x5B, 0x37, 0x44, + 0x97, 0x9C, 0x94, 0x72, 0xD2, 0xDC, 0x78, 0xFF, 0x9A, 0xF5, 0xBF, 0x08, + 0x4E, 0x4D, 0xB1, 0x00, 0xC1, 0xF8, 0x7E, 0xF3, 0x10, 0xA0, 0x60, 0x67, + 0xEE, 0x4D, 0xDD, 0x87, 0x8A, 0x20, 0xF5, 0x31, 0xDD, 0x3A, 0x1C, 0x72, + 0x46, 0xAB, 0xB9, 0x6E, 0x41, 0x03, 0x25, 0x29, 0x21, 0xC7, 0xDC, 0x2F, + 0xBB, 0x2E, 0x81, 0x9D, 0xA0, 0x78, 0xA0, 0x60, 0x2D, 0xDC, 0xFC, 0x20, + 0xF9, 0x94, 0xC5, 0xE8, 0x88, 0xE4, 0x56, 0x49, 0x03, 0x38, 0x87, 0x81, + 0x3D, 0x95, 0x1A, 0xF5, 0xE1, 0xC7, 0x43, 0x9B, 0x40, 0x54, 0xD9, 0xDF, + 0x7E, 0xB0, 0xBD, 0x8D, 0x2F, 0xF2, 0xCA, 0x1E, 0xCA, 0xCE, 0x4D, 0xB1, + 0x3D, 0x00, 0xC8, 0xC4, 0x1B, 0x05, 0x26, 0xDC, 0x11, 0x10, 0xDC, 0x8B, + 0xDB, 0x04, 0xF2, 0x6D, 0xCF, 0x91, 0x24, 0xC7, 0x23, 0x46, 0xD7, 0x9F, + 0x7F, 0xC5, 0x63, 0xA6, 0x6D, 0xF9, 0xF6, 0xD0, 0x49, 0xD2, 0xF5, 0x60, + 0x81, 0x6B, 0xB6, 0x10, 0x37, 0x64, 0xB2, 0xEE, 0x2C, 0x33, 0x39, 0xE7, + 0xFD, 0xBD, 0x94, 0xEB, 0xE3, 0xE0, 0xC4, 0xBF, 0xA8, 0xE7, 0x8E, 0x5D, + 0xC3, 0xC5, 0x76, 0xF6, 0x61, 0x70, 0x35, 0x38, 0x68, 0x4D, 0x3E, 0x8D, + 0x0B, 0x28, 0x60, 0xD5, 0x38, 0xB4, 0x85, 0x7A, 0x19, 0xDA, 0xE7, 0x00, + 0x1B, 0x02, 0xE3, 0x44, 0x73, 0xEE, 0x2B, 0xB2, 0x14, 0x11, 0xF1, 0x7A, + 0xD3, 0xAC, 0x4A, 0x9A, 0x7A, 0xA9, 0x5E, 0xBB, 0x0B, 0xAC, 0x93, 0x79, + 0xD7, 0x2E, 0xC1, 0x3D, 0xA8, 0xED, 0xC7, 0x7A, 0x57, 0x8E, 0x52, 0x26, + 0xC9, 0x78, 0x5B, 0xB1, 0x77, 0xB1, 0xE2, 0xEA, 0x3C, 0x7A, 0xBD, 0xA9, + 0x80, 0x21, 0x81, 0x03, 0x1F, 0x45, 0xD1, 0x0E, 0x7D, 0xC5, 0xB1, 0x9A, + 0xC5, 0xC3, 0xAC, 0xA0, 0x3D, 0x53, 0x0B, 0xBF, 0x13, 0x84, 0x7F, 0x01, + 0x2A, 0x45, 0x28, 0x32, 0xE1, 0x48, 0xEB, 0x86, 0xFF, 0xBC, 0x9C, 0x37, + 0x75, 0x0F, 0x20, 0x3B, 0x2B, 0xB6, 0xBB, 0x0C, 0x7F, 0x62, 0x67, 0x74, + 0xE3, 0xB5, 0x82, 0xC7, 0x37, 0xC5, 0x02, 0xD4, 0x42, 0xE8, 0x6B, 0x02, + 0x7A, 0x2D, 0xA3, 0x41, 0xFF, 0xB6, 0x10, 0x59, 0xB4, 0x26, 0x99, 0x90, + 0x1E, 0x81, 0xBD, 0x05, 0xC8, 0x98, 0xFD, 0x92, 0x80, 0xE9, 0x66, 0xCF, + 0x9C, 0xB7, 0x5E, 0x1F, 0xE4, 0x31, 0x2C, 0x4E, 0x0C, 0x7F, 0x9B, 0x5F, + 0x91, 0x2E, 0x97, 0x91, 0x5A, 0x2E, 0x0F, 0xF7, 0x27, 0xB6, 0xB6, 0xD5, + 0x3A, 0x24, 0x1F, 0x3B, 0x7B, 0xCB, 0x04, 0xF4, 0xC3, 0x2E, 0x23, 0xE6, + 0x06, 0x89, 0xDA, 0x22, 0xFF, 0x48, 0xB3, 0x0C, 0x9D, 0x34, 0xA6, 0x0D, + 0x98, 0x8B, 0x85, 0x7C, 0xF1, 0x58, 0xB2, 0xD9, 0xD4, 0x9D, 0x3C, 0x62, + 0x26, 0xC6, 0x93, 0x84, 0x3B, 0x1F, 0x55, 0x91, 0x53, 0x7A, 0xD9, 0x6E, + 0x32, 0x0C, 0xA1, 0xB5, 0x3E, 0x9F, 0xD3, 0x23, 0x04, 0xDA, 0xE7, 0x57, + 0x89, 0x26, 0x0B, 0x8F, 0x29, 0x45, 0xB5, 0xEB, 0x72, 0x55, 0x01, 0x0F, + 0x33, 0xB8, 0x4D, 0x2F, 0x2B, 0x1D, 0x7E, 0x40, 0x1A, 0x5F, 0x81, 0xFD, + 0x64, 0xBB, 0xAC, 0xDE, 0x59, 0x88, 0xC3, 0xE8, 0x0B, 0x11, 0x04, 0xDE, + 0xAB, 0x01, 0xC9, 0xB6, 0x1A, 0xEE, 0x3A, 0x15, 0x20, 0xF7, 0x5A, 0x6B, + 0x96, 0xE7, 0xBF, 0xC3, 0xC7, 0x2B, 0x41, 0x8B, 0x67, 0xE1, 0xCB, 0xE8, + 0xF2, 0x3D, 0xD8, 0x60, 0x81, 0x69, 0x50, 0x35, 0xC7, 0x1F, 0x8F, 0x2B, + 0xCF, 0x29, 0x10, 0x31, 0x6E, 0xAD, 0x6F, 0xF4, 0x6B, 0x05, 0x40, 0x74, + 0x79, 0x97, 0xDF, 0x61, 0xDA, 0x02, 0x3D, 0x69, 0xCC, 0x3C, 0xD9, 0xE7, + 0xD3, 0xD3, 0x1B, 0x86, 0xA0, 0x85, 0x0B, 0xAD, 0x7A, 0x27, 0xCD, 0x8B, + 0xB7, 0xCE, 0x2F, 0xB7, 0x4F, 0xD8, 0xA5, 0x8C, 0x50, 0x71, 0x84, 0xC0, + 0x28, 0x84, 0xD8, 0x1B, 0x0D, 0xCA, 0x73, 0x46, 0xE5, 0xDB, 0x30, 0xB0, + 0xC1, 0x06, 0x1C, 0xB5, 0xFB, 0xAF, 0x2B, 0x64, 0x3B, 0x04, 0xB5, 0xCC, + 0xCB, 0xEB, 0x4A, 0xB8, 0x52, 0x03, 0xD8, 0xBC, 0xD6, 0x20, 0x55, 0x38, + 0xAA, 0x18, 0x45, 0x3B, 0xBE, 0x1B, 0xD9, 0xFE, 0x32, 0x89, 0xF6, 0x95, + 0x0E, 0xAE, 0x10, 0x3A, 0xAF, 0xAE, 0x33, 0xD7, 0xA7, 0x5B, 0xF8, 0xF9, + 0xD1, 0xC3, 0x8A, 0x97, 0x0C, 0x54, 0x64, 0x6A, 0x5C, 0x57, 0xA1, 0xC5, + 0x2B, 0xCD, 0xAD, 0xC2, 0x65, 0x54, 0xD4, 0xC3, 0x05, 0x1F, 0x67, 0x47, + 0xF3, 0xFF, 0x42, 0x85, 0x92, 0x8F, 0x52, 0x0F, 0xCD, 0xB9, 0xA8, 0x3D, + 0x28, 0x69, 0xE9, 0x13, 0x86, 0xCB, 0xE5, 0x0A, 0x23, 0x24, 0x60, 0x55, + 0x22, 0xB0, 0xCF, 0x1A, 0x6B, 0xF1, 0xC9, 0x68, 0xA5, 0x28, 0xBC, 0xBE, + 0x5E, 0xD1, 0xD1, 0xB1, 0x30, 0x41, 0xF5, 0xA9, 0x93, 0x0B, 0xBB, 0x0E, + 0xB9, 0xFD, 0x3F, 0xAF, 0xAD, 0x8A, 0x07, 0x52, 0x60, 0x21, 0xBF, 0xB3, + 0x05, 0x9D, 0x4D, 0xFB, 0xA3, 0x85, 0x27, 0xE3, 0xE5, 0x87, 0x5A, 0xD0, + 0xA5, 0x68, 0x8E, 0x68, 0x76, 0x03, 0x84, 0x38, 0x8D, 0xAC, 0x46, 0x9B, + 0xFD, 0x86, 0xF2, 0x23 + ]; +} \ No newline at end of file diff --git a/GCDTool/GcdHeader.cs b/GCDTool/GcdHeader.cs new file mode 100644 index 0000000..2f055a2 --- /dev/null +++ b/GCDTool/GcdHeader.cs @@ -0,0 +1,133 @@ +using GCDTool.IO; +using GCDTool.Wram; +using System.Security.Cryptography; + +namespace GCDTool; + +/// +/// Class representing the header of a GCD rom file. +/// +sealed class GcdHeader +{ + /// + /// The game code of the GCD rom. + /// + public uint GameCode { get; set; } + + /// + /// The offset of the ARM 9 binary in the rom. + /// + public uint Arm9RomOffset { get; set; } + + /// + /// The original size of the ARM 9 binary. + /// + public uint Arm9Size { get; set; } + + /// + /// The memory address where the ARM 9 binary will be loaded. + /// + public uint Arm9LoadAddress { get; set; } + + /// + /// The padded size of the ARM 9 binary. + /// + public uint Arm9PaddedSize { get; set; } + + /// + /// The offset of the ARM 7 binary in the rom. + /// + public uint Arm7RomOffset { get; set; } + + /// + /// The original size of the ARM 7 binary. + /// + public uint Arm7Size { get; set; } + + /// + /// The memory address where the ARM 7 binary will be loaded. + /// + public uint Arm7LoadAddress { get; set; } + + /// + /// The padded size of the ARM 7 binary. + /// + public uint Arm7PaddedSize { get; set; } + + /// + /// The regular rom control settings. + /// + public uint RomControl { get; set; } + + /// + /// The secure rom control settings. + /// + public uint RomControlSecure { get; set; } + + /// + /// The secure area delay. + /// + public ushort SecureAreaDelay { get; set; } + + /// + /// The end of the nitro region of the rom. + /// + public ushort NitroRomRegionEnd { get; set; } + + /// + /// The start of the twl region of the rom. + /// + public ushort TwlRomRegionStart { get; set; } + + /// + /// Flags controlling how the GCD firm is loaded. + /// + public GcdHeaderFlags Flags { get; set; } + + /// + /// The RSA signed signature. See for the format before signing. + /// + public byte[] Signature { get; } = new byte[GcdSignature.SIGNATURE_SIGNED_LENGTH]; + + /// + /// The initial WRAM configuration used by the GCD firm. + /// + public WramConfig WramConfiguration { get; set; } = new(); + + /// + /// Serializes the header data as a byte array. + /// + /// The header data as a byte array. + public byte[] ToByteArray() + { + var header = new byte[0x200]; + IOUtil.WriteU32Le(header, 0x0C, GameCode); + IOUtil.WriteU32Le(header, 0x20, Arm9RomOffset); + IOUtil.WriteU32Le(header, 0x24, Arm9Size); + IOUtil.WriteU32Le(header, 0x28, Arm9LoadAddress); + IOUtil.WriteU32Le(header, 0x2C, Arm9PaddedSize); + IOUtil.WriteU32Le(header, 0x30, Arm7RomOffset); + IOUtil.WriteU32Le(header, 0x34, Arm7Size); + IOUtil.WriteU32Le(header, 0x38, Arm7LoadAddress); + IOUtil.WriteU32Le(header, 0x3C, Arm7PaddedSize); + IOUtil.WriteU32Le(header, 0x60, RomControl); + IOUtil.WriteU32Le(header, 0x64, RomControlSecure); + IOUtil.WriteU16Le(header, 0x6E, SecureAreaDelay); + IOUtil.WriteU16Le(header, 0x90, NitroRomRegionEnd); + IOUtil.WriteU16Le(header, 0x92, TwlRomRegionStart); + header[0xFF] = (byte)Flags; + Signature.CopyTo(header, 0x100); + WramConfiguration.Write(header.AsSpan(0x180)); + return header; + } + + /// + /// Calculates the SHA-1 hash used for . + /// + /// The byte span to write the SHA-1 hash to. + public void GetSha1Hash(Span destination) + { + var headerData = ToByteArray(); + SHA1.HashData([.. headerData.AsSpan(0, 0x100), .. headerData.AsSpan(0x180, 0x80)], destination); + } +} diff --git a/GCDTool/GcdHeaderFlags.cs b/GCDTool/GcdHeaderFlags.cs new file mode 100644 index 0000000..956766f --- /dev/null +++ b/GCDTool/GcdHeaderFlags.cs @@ -0,0 +1,9 @@ +namespace GCDTool; + +[Flags] +enum GcdHeaderFlags : byte +{ + Arm9Compressed = (1 << 0), + Arm7Compressed = (1 << 1), + Arm9Speed134MHz = (1 << 2) +} \ No newline at end of file diff --git a/GCDTool/GcdRom.cs b/GCDTool/GcdRom.cs new file mode 100644 index 0000000..2381053 --- /dev/null +++ b/GCDTool/GcdRom.cs @@ -0,0 +1,88 @@ +namespace GCDTool; + +/// +/// Class representing a GCD rom file. +/// +sealed class GcdRom +{ + private const int BLOWFISH_P_TABLE_OFFSET = 0x1600; + private const int BLOWFISH_S_BOXES_OFFSET = 0x1C00; + private const int TEST_PATTERNS_OFFSET = 0x3000; + + /// + /// The header of the GCD rom. + /// + public GcdHeader Header { get; set; } = new(); + + /// + /// The encrypted ARM 9 binary. + /// + public byte[] EncryptedArm9Binary { get; set; } = []; + + /// + /// The encrypted ARM 7 binary. + /// + public byte[] EncryptedArm7Binary { get; set; } = []; + + /// + /// Writes the GCD rom file to the given . + /// + /// The stream to write to. + public void Write(Stream stream) + { + stream.Write(Header.ToByteArray()); + WriteBlowfishTable(stream); + WriteTestPatterns(stream); + stream.Position = Header.Arm9RomOffset; + stream.Write(EncryptedArm9Binary); + stream.Position = Header.Arm7RomOffset; + stream.Write(EncryptedArm7Binary); + } + + private void WriteBlowfishTable(Stream stream) + { + var blowfishTable = GcdBlowfish.GetTransformedKeyTable(Header.GameCode); + stream.Position = BLOWFISH_P_TABLE_OFFSET; + stream.Write(blowfishTable, 0, Blowfish.P_TABLE_ENTRY_COUNT * 4); + stream.Position = BLOWFISH_S_BOXES_OFFSET; + stream.Write(blowfishTable, Blowfish.P_TABLE_ENTRY_COUNT * 4, Blowfish.S_BOX_COUNT * Blowfish.S_BOX_ENTRY_COUNT * 4); + } + + private void WriteTestPatterns(Stream stream) + { + stream.Position = TEST_PATTERNS_OFFSET; + stream.Write([0xFF, 0x00, 0xFF, 0x00, 0xAA, 0x55, 0xAA, 0x55]); + for (int i = 8; i < 0x200; i++) + { + stream.WriteByte((byte)(i & 0xFF)); + } + for (int i = 0; i < 0x200; i++) + { + stream.WriteByte((byte)(0xFF - (i & 0xFF))); + } + for (int i = 0; i < 0x200; i++) + { + stream.WriteByte(0x00); + } + for (int i = 0; i < 0x200; i++) + { + stream.WriteByte(0xFF); + } + for (int i = 0; i < 0x200; i++) + { + stream.WriteByte(0x0F); + } + for (int i = 0; i < 0x200; i++) + { + stream.WriteByte(0xF0); + } + for (int i = 0; i < 0x200; i++) + { + stream.WriteByte(0x55); + } + for (int i = 0; i < 0x1FF; i++) + { + stream.WriteByte(0xAA); + } + } +} diff --git a/GCDTool/GcdSignature.cs b/GCDTool/GcdSignature.cs new file mode 100644 index 0000000..efab692 --- /dev/null +++ b/GCDTool/GcdSignature.cs @@ -0,0 +1,76 @@ +using Org.BouncyCastle.Crypto.Encodings; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.OpenSsl; +using System.Security.Cryptography; + +namespace GCDTool; + +/// +/// Class representing a GCD signature. +/// +sealed class GcdSignature +{ + /// + /// The length of the signature data before signing. + /// + public const int SIGNATURE_LENGTH = 0x74; + + /// + /// The length of the signature data after signing. + /// + public const int SIGNATURE_SIGNED_LENGTH = 0x80; + + /// + /// AES key Y used for encrypting the ARM 9 and ARM 7 binaries. + /// + public byte[] AesKeyY { get; } = new byte[16]; + + /// + /// The SHA-1 hash of the 0x180 bytes consisting of header[0..0x100] and header[0x180..0x200]. + /// + public byte[] HeaderSha1 { get; } = new byte[20]; + + /// + /// The SHA-1 hash of the original ARM 9 binary (without padding and AES encryption). + /// + public byte[] Arm9Sha1 { get; } = new byte[20]; + + /// + /// The SHA-1 hash of the original ARM 7 binary (without padding and AES encryption). + /// + public byte[] Arm7Sha1 { get; } = new byte[20]; + + /// + /// Serializes the signature as a byte array. + /// + /// The signature as byte array. + public byte[] ToByteArray() + { + var signature = new byte[SIGNATURE_LENGTH]; + AesKeyY.CopyTo(signature, 0x00); + HeaderSha1.CopyTo(signature, 0x10); + Arm9Sha1.CopyTo(signature, 0x24); + Arm7Sha1.CopyTo(signature, 0x38); + SHA1.HashData(signature.AsSpan(0, 0x60), signature.AsSpan(0x60)); + return signature; + } + + /// + /// Serializes the signature as a byte array and signs the result + /// using the RSA private key specified by . + /// + /// The path to the .der file containing the private RSA key to use. + /// A byte array containing the signed signature. + public byte[] ToSignedByteArray(string derKeyPath) + { + var rsaCryptoServiceProvider = new RSACryptoServiceProvider(); + rsaCryptoServiceProvider.ImportRSAPrivateKey(File.ReadAllBytes(derKeyPath), out _); + string pem = rsaCryptoServiceProvider.ExportRSAPrivateKeyPem(); + + var keyPair = (AsymmetricCipherKeyPair)new PemReader(new StringReader(pem)).ReadObject(); + var rsa = new Pkcs1Encoding(new RsaEngine()); + rsa.Init(true, keyPair.Private); + return rsa.ProcessBlock(ToByteArray(), 0, SIGNATURE_LENGTH); + } +} diff --git a/GCDTool/IO/EndianBinaryReader.cs b/GCDTool/IO/EndianBinaryReader.cs new file mode 100644 index 0000000..007f8af --- /dev/null +++ b/GCDTool/IO/EndianBinaryReader.cs @@ -0,0 +1,152 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using System.Text; + +namespace GCDTool.IO; + +sealed class EndianBinaryReader : IDisposable +{ + private bool _disposed; + private byte[]? _buffer; + + public Stream BaseStream { get; } + public Endianness Endianness { get; } + + public static Endianness SystemEndianness => + BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian; + + private bool Reverse => SystemEndianness != Endianness; + + public EndianBinaryReader(Stream baseStream, Endianness endianness = Endianness.LittleEndian) + { + if (!baseStream.CanRead) + { + throw new ArgumentException("Stream is not readable.", nameof(baseStream)); + } + + BaseStream = baseStream; + Endianness = endianness; + } + + ~EndianBinaryReader() + { + Dispose(false); + } + + [MemberNotNull(nameof(_buffer))] + private void FillBuffer(int bytes, int stride) + { + if (_buffer == null || _buffer.Length < bytes) + { + _buffer = new byte[bytes]; + } + + BaseStream.Read(_buffer, 0, bytes); + + if (Reverse && stride > 1) + { + for (int i = 0; i < bytes; i += stride) + { + Array.Reverse(_buffer, i, stride); + } + } + } + + public char ReadChar(Encoding encoding) + { + int size = GetEncodingSize(encoding); + FillBuffer(size, size); + return encoding.GetChars(_buffer, 0, size)[0]; + } + + public char[] ReadChars(Encoding encoding, int count) + { + int size = GetEncodingSize(encoding); + FillBuffer(size * count, size); + return encoding.GetChars(_buffer, 0, size * count); + } + + private static int GetEncodingSize(Encoding encoding) + { + if (encoding == Encoding.UTF8 || encoding == Encoding.ASCII) + { + return 1; + } + else if (encoding == Encoding.Unicode || encoding == Encoding.BigEndianUnicode) + { + return 2; + } + else + { + return 1; + } + } + + public string ReadStringNT(Encoding encoding) + { + string text = string.Empty; + do + { + text += ReadChar(encoding); + } while (!text.EndsWith("\0", StringComparison.Ordinal)); + + return text.Remove(text.Length - 1); + } + + public string ReadString(Encoding encoding, int count) + { + return new string(ReadChars(encoding, count)); + } + + public unsafe T Read() where T : unmanaged + { + int size = sizeof(T); + FillBuffer(size, size); + return MemoryMarshal.Read(_buffer); + } + + public unsafe T[] Read(int count) where T : unmanaged + { + int size = sizeof(T); + var result = new T[count]; + var byteResult = MemoryMarshal.Cast(result); + BaseStream.Read(byteResult); + + if (Reverse && size > 1) + { + for (int i = 0; i < size * count; i += size) + { + byteResult.Slice(i, size).Reverse(); + } + } + + return result; + } + + public void Close() + { + Dispose(); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + BaseStream?.Close(); + } + + _buffer = null; + _disposed = true; + } +} \ No newline at end of file diff --git a/GCDTool/IO/Endianness.cs b/GCDTool/IO/Endianness.cs new file mode 100644 index 0000000..ab0402d --- /dev/null +++ b/GCDTool/IO/Endianness.cs @@ -0,0 +1,7 @@ +namespace GCDTool.IO; + +enum Endianness +{ + BigEndian, + LittleEndian +} \ No newline at end of file diff --git a/GCDTool/IO/IOUtil.cs b/GCDTool/IO/IOUtil.cs new file mode 100644 index 0000000..1c8fe7a --- /dev/null +++ b/GCDTool/IO/IOUtil.cs @@ -0,0 +1,221 @@ +using System.Buffers.Binary; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace GCDTool.IO; + +static class IOUtil +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short ReadS16Le(byte[] data, int offset) + => BinaryPrimitives.ReadInt16LittleEndian(data.AsSpan(offset)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short ReadS16Le(ReadOnlySpan span) + => BinaryPrimitives.ReadInt16LittleEndian(span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short[] ReadS16Le(byte[] data, int offset, int count) + => ReadS16Le(data.AsSpan(offset), count); + + public static short[] ReadS16Le(ReadOnlySpan data, int count) + { + var res = MemoryMarshal.Cast(data.Slice(0, count * 2)).ToArray(); + if (!BitConverter.IsLittleEndian) + { + for (int i = 0; i < count; i++) + res[i] = BinaryPrimitives.ReverseEndianness(res[i]); + } + + return res; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteS16Le(byte[] data, int offset, short value) + => BinaryPrimitives.WriteInt16LittleEndian(data.AsSpan(offset), value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteS16Le(Span span, short value) + => BinaryPrimitives.WriteInt16LittleEndian(span, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteS16Le(byte[] data, int offset, ReadOnlySpan values) + => WriteS16Le(data.AsSpan(offset), values); + + public static void WriteS16Le(Span data, ReadOnlySpan values) + { + var dst = MemoryMarshal.Cast(data); + values.CopyTo(dst); + if (!BitConverter.IsLittleEndian) + { + for (int i = 0; i < values.Length; i++) + dst[i] = BinaryPrimitives.ReverseEndianness(dst[i]); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short ReadS16Be(byte[] data, int offset) + => BinaryPrimitives.ReadInt16BigEndian(data.AsSpan(offset)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static short ReadS16Be(ReadOnlySpan span) + => BinaryPrimitives.ReadInt16BigEndian(span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort[] ReadU16Le(byte[] data, int offset, int count) + => ReadU16Le(data.AsSpan(offset), count); + + public static ushort[] ReadU16Le(ReadOnlySpan data, int count) + { + var res = MemoryMarshal.Cast(data.Slice(0, count * 2)).ToArray(); + if (!BitConverter.IsLittleEndian) + { + for (int i = 0; i < count; i++) + res[i] = BinaryPrimitives.ReverseEndianness(res[i]); + } + + return res; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ReadU16Le(byte[] data, int offset) + => BinaryPrimitives.ReadUInt16LittleEndian(data.AsSpan(offset)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ReadU16Le(ReadOnlySpan span) + => BinaryPrimitives.ReadUInt16LittleEndian(span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteU16Le(byte[] data, int offset, ushort value) + => BinaryPrimitives.WriteUInt16LittleEndian(data.AsSpan(offset), value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteU16Le(Span span, ushort value) + => BinaryPrimitives.WriteUInt16LittleEndian(span, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteU16Le(byte[] data, int offset, ReadOnlySpan values) + => WriteU16Le(data.AsSpan(offset), values); + + public static void WriteU16Le(Span data, ReadOnlySpan values) + { + var dst = MemoryMarshal.Cast(data); + values.CopyTo(dst); + if (!BitConverter.IsLittleEndian) + { + for (int i = 0; i < values.Length; i++) + dst[i] = BinaryPrimitives.ReverseEndianness(dst[i]); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ReadU16Be(byte[] data, int offset) + => BinaryPrimitives.ReadUInt16BigEndian(data.AsSpan(offset)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ushort ReadU16Be(ReadOnlySpan span) + => BinaryPrimitives.ReadUInt16BigEndian(span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteU16Be(byte[] data, int offset, ushort value) + => BinaryPrimitives.WriteUInt16BigEndian(data.AsSpan(offset), value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteU16Be(Span span, ushort value) + => BinaryPrimitives.WriteUInt16BigEndian(span, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ReadU24Le(byte[] data, int offset) + => (uint)(data[offset] | data[offset + 1] << 8 | data[offset + 2] << 16); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ReadU24Le(ReadOnlySpan span) + => (uint)(span[0] | span[1] << 8 | span[2] << 16); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ReadU32Le(byte[] data, int offset) + => BinaryPrimitives.ReadUInt32LittleEndian(data.AsSpan(offset)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ReadU32Le(ReadOnlySpan span) + => BinaryPrimitives.ReadUInt32LittleEndian(span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint[] ReadU32Le(byte[] data, int offset, int count) + => ReadU32Le(data.AsSpan(offset), count); + + public static uint[] ReadU32Le(ReadOnlySpan data, int count) + { + var res = MemoryMarshal.Cast(data.Slice(0, count * 4)).ToArray(); + if (!BitConverter.IsLittleEndian) + { + for (int i = 0; i < count; i++) + res[i] = BinaryPrimitives.ReverseEndianness(res[i]); + } + + return res; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ReadU32Be(byte[] data, int offset) + => BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(offset)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ReadU32Be(ReadOnlySpan span) + => BinaryPrimitives.ReadUInt32BigEndian(span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteU32Le(byte[] data, int offset, uint value) + => BinaryPrimitives.WriteUInt32LittleEndian(data.AsSpan(offset), value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteU32Le(Span span, uint value) + => BinaryPrimitives.WriteUInt32LittleEndian(span, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteU32Le(byte[] data, int offset, ReadOnlySpan values) + => WriteU32Le(data.AsSpan(offset), values); + + public static void WriteU32Le(Span data, ReadOnlySpan values) + { + var dst = MemoryMarshal.Cast(data); + values.CopyTo(dst); + if (!BitConverter.IsLittleEndian) + { + for (int i = 0; i < values.Length; i++) + dst[i] = BinaryPrimitives.ReverseEndianness(dst[i]); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong ReadU64Le(byte[] data, int offset) + => BinaryPrimitives.ReadUInt64LittleEndian(data.AsSpan(offset)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong ReadU64Le(ReadOnlySpan span) + => BinaryPrimitives.ReadUInt64LittleEndian(span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong ReadU64Be(byte[] data, int offset) + => BinaryPrimitives.ReadUInt64BigEndian(data.AsSpan(offset)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong ReadU64Be(ReadOnlySpan span) + => BinaryPrimitives.ReadUInt64BigEndian(span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteU64Le(byte[] data, int offset, ulong value) + => BinaryPrimitives.WriteUInt64LittleEndian(data.AsSpan(offset), value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteU64Le(Span span, ulong value) + => BinaryPrimitives.WriteUInt64LittleEndian(span, value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteU64Be(byte[] data, int offset, ulong value) + => BinaryPrimitives.WriteUInt64BigEndian(data.AsSpan(offset), value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteU64Be(Span span, ulong value) + => BinaryPrimitives.WriteUInt64BigEndian(span, value); +} \ No newline at end of file diff --git a/GCDTool/Program.cs b/GCDTool/Program.cs new file mode 100644 index 0000000..3c796cb --- /dev/null +++ b/GCDTool/Program.cs @@ -0,0 +1,212 @@ +using CommandLine; +using CommandLine.Text; +using GCDTool.Elf; +using GCDTool.IO; +using GCDTool.Wram; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using System.Security.Cryptography; +using System.Text; + +namespace GCDTool; + +static class Program +{ + private const string GCDTOOL_HEADING = "== GCDTool by Gericom =="; + private const string AES_CIPHER_NAME = "AES/CTR/NoPadding"; + private const string ALGORITHM_AES = "AES"; + private const int AES_BLOCK_SIZE = 16; + private const uint ROM_CONTROL = 0x00416657; + private const uint ROM_CONTROL_SECURE = 0x081808F8; + private const ushort SECURE_AREA_DELAY = 3454; + + private static ReadOnlySpan AesKeyX + => [0x4E, 0x69, 0x6E, 0x74, 0x65, 0x6E, 0x64, 0x6F, 0x20, 0x44, 0x53, 0x00, 0x01, 0x23, 0x21, 0x00]; + + private static void Main(string[] args) + { + var parser = new Parser(with => with.HelpWriter = null); + var parserResult = parser.ParseArguments(args); + parserResult + .WithParsed(RunOptions) + .WithNotParsed(_ => + { + Console.WriteLine(HelpText.AutoBuild(parserResult, h => + { + h.AutoVersion = false; + h.Copyright = string.Empty; + h.Heading = GCDTOOL_HEADING; + return HelpText.DefaultParsingErrorsHandler(parserResult, h); + }, e => e)); + }); + } + + private static void RunOptions(CommandLineOptions options) + { + if (options.GameCode.Length != 4) + { + Console.WriteLine("Invalid game code specified. Should be 4 characters."); + return; + } + + uint gameCode = IOUtil.ReadU32Le(Encoding.ASCII.GetBytes(options.GameCode)); + + // This can be any key, used for encryption of the arm7 and arm9 binaries. + // To have reproducable results, we'll simply use an all-zeros key. + byte[] aesKeyY = new byte[16]; + + var gcdRom = new GcdRom + { + Header = new GcdHeader + { + GameCode = gameCode, + + RomControl = ROM_CONTROL, + RomControlSecure = ROM_CONTROL_SECURE, + SecureAreaDelay = SECURE_AREA_DELAY, + + // it appears to be unnecessary to specify the rom area + NitroRomRegionEnd = 0, + TwlRomRegionStart = 0, + + Flags = options.Arm9Speed67MHz ? 0 : GcdHeaderFlags.Arm9Speed134MHz + }, + }; + + var signature = new GcdSignature(); + aesKeyY.CopyTo(signature.AesKeyY, 0); + + if (!TryLoadArm9(options.Arm9Path, gcdRom, signature) || + !TryLoadArm7(options.Arm7Path, gcdRom, signature) || + !TryLoadWramConfig(options.WramConfigJsonPath, gcdRom)) + { + return; + } + + gcdRom.Header.GetSha1Hash(signature.HeaderSha1); + var signedSignature = signature.ToSignedByteArray(options.GcdKeyPath); + signedSignature.CopyTo(gcdRom.Header.Signature, 0); + + using (var stream = File.Create(options.OutputPath)) + { + gcdRom.Write(stream); + } + } + + private static bool TryLoadArm9(string arm9ElfPath, GcdRom gcdRom, GcdSignature signature) + { + var arm9Data = ParseElf(arm9ElfPath, out uint arm9LoadAddress); + if (arm9LoadAddress < 0x03000000) + { + Console.WriteLine("Error: ARM9 binary cannot be loaded to main memory. Use TWL wram instead."); + return false; + } + gcdRom.Header.Arm9LoadAddress = arm9LoadAddress; + gcdRom.Header.Arm9Size = (uint)arm9Data.Length; + SHA1.HashData(arm9Data, signature.Arm9Sha1); + + if ((arm9Data.Length % 512) != 0) + { + arm9Data = [.. arm9Data, .. new byte[512 - (arm9Data.Length % 512)]]; + } + gcdRom.Header.Arm9PaddedSize = (uint)arm9Data.Length; + EncryptAes(arm9Data, signature.AesKeyY); + gcdRom.EncryptedArm9Binary = arm9Data; + gcdRom.Header.Arm9RomOffset = 0x4000; + return true; + } + + private static bool TryLoadArm7(string arm7ElfPath, GcdRom gcdRom, GcdSignature signature) + { + var arm7Data = ParseElf(arm7ElfPath, out uint arm7LoadAddress); + if (arm7LoadAddress < 0x03000000) + { + Console.WriteLine("Error: ARM7 binary cannot be loaded to main memory. Use TWL wram instead."); + return false; + } + gcdRom.Header.Arm7LoadAddress = arm7LoadAddress; + gcdRom.Header.Arm7Size = (uint)arm7Data.Length; + SHA1.HashData(arm7Data, signature.Arm7Sha1); + + if ((arm7Data.Length % 512) != 0) + { + arm7Data = [.. arm7Data, .. new byte[512 - (arm7Data.Length % 512)]]; + } + gcdRom.Header.Arm7PaddedSize = (uint)arm7Data.Length; + EncryptAes(arm7Data, signature.AesKeyY); + gcdRom.EncryptedArm7Binary = arm7Data; + gcdRom.Header.Arm7RomOffset = (uint)(0x4000 + gcdRom.EncryptedArm9Binary.Length); + return true; + } + + private static byte[] ParseElf(string elfPath, out uint loadAddress) + { + loadAddress = 0; + var elf = new ElfFile(File.ReadAllBytes(elfPath)); + var programData = new MemoryStream(); + bool loadAddressSet = false; + foreach (var programHeader in elf.ProgramHeaderTable) + { + if (programHeader.SegmentType == ProgramHeaderTableEntry.ElfSegmentType.Load && + programHeader.SegmentData is not null && + programHeader.SegmentData.Length != 0) + { + if (!loadAddressSet) + { + loadAddress = programHeader.PhysicalAddress; + loadAddressSet = true; + } + programData.Write(programHeader.SegmentData); + } + } + + return programData.ToArray(); + } + + private static void ByteSwapAesBlocks(Span data) + { + for (int i = 0; i < data.Length; i += AES_BLOCK_SIZE) + { + data.Slice(i, AES_BLOCK_SIZE).Reverse(); + } + } + + private static byte[] CreateAesInitializationVector(uint dataLength) + { + var iv = new byte[16]; + IOUtil.WriteU32Le(iv, 0, dataLength); + IOUtil.WriteU32Le(iv, 4, (uint)-dataLength); + IOUtil.WriteU32Le(iv, 8, ~dataLength); + return iv; + } + + private static void EncryptAes(Span data, ReadOnlySpan keyY) + { + var scrambledKey = AesKeyScrambler.Scramble(AesKeyX, keyY); + Array.Reverse(scrambledKey); + ByteSwapAesBlocks(data); + var iv = CreateAesInitializationVector((uint)data.Length); + Array.Reverse(iv); + var cipher = CipherUtilities.GetCipher(AES_CIPHER_NAME); + cipher.Init(true, new ParametersWithIV( + ParameterUtilities.CreateKeyParameter(ALGORITHM_AES, scrambledKey), iv)); + cipher.ProcessBytes(data, data); + cipher.DoFinal(data); + ByteSwapAesBlocks(data); + } + + private static bool TryLoadWramConfig(string wramConfigJsonPath, GcdRom gcdRom) + { + try + { + string wramConfigJson = File.ReadAllText(wramConfigJsonPath); + gcdRom.Header.WramConfiguration = new WramConfigJsonReader().ReadWramConfigFromJson(wramConfigJson); + return true; + } + catch (Exception exception) + { + Console.WriteLine("Failed to parse wram configuration JSON: " + exception.Message); + return false; + } + } +} diff --git a/GCDTool/Wram/NtrWramMaster.cs b/GCDTool/Wram/NtrWramMaster.cs new file mode 100644 index 0000000..b18d281 --- /dev/null +++ b/GCDTool/Wram/NtrWramMaster.cs @@ -0,0 +1,10 @@ +namespace GCDTool.Wram; + +/// +/// The master of a NTR wram block. +/// +enum NtrWramMaster +{ + Arm9 = 0, + Arm7 = 1 +} \ No newline at end of file diff --git a/GCDTool/Wram/WramABlockMapping.cs b/GCDTool/Wram/WramABlockMapping.cs new file mode 100644 index 0000000..89e012d --- /dev/null +++ b/GCDTool/Wram/WramABlockMapping.cs @@ -0,0 +1,14 @@ +namespace GCDTool.Wram; + +/// +/// Specifies the mapping of TWL wram A block. +/// +readonly record struct WramABlockMapping(WramAMaster Master, WramASlot Slot, bool Enable) +{ + public static explicit operator byte(WramABlockMapping wramABlockMapping) + { + return (byte)(((byte)wramABlockMapping.Master & 1) + | (((byte)wramABlockMapping.Slot & 3) << 2) + | (wramABlockMapping.Enable ? 0x80 : 0)); + } +} \ No newline at end of file diff --git a/GCDTool/Wram/WramAMappedSlots.cs b/GCDTool/Wram/WramAMappedSlots.cs new file mode 100644 index 0000000..66bd7f8 --- /dev/null +++ b/GCDTool/Wram/WramAMappedSlots.cs @@ -0,0 +1,22 @@ +namespace GCDTool.Wram; + +/// +/// Specifies which TWL wram A slots are mapped in memory. +/// +enum WramAMappedSlots +{ + /// + /// Only slot 0 mapped. 64 kB mirrors. + /// + Slot0 = 0, + + /// + /// Slot 0 and 1 mapped. 128 kB mirrors. + /// + Slot01 = 2, + + /// + /// All slots mapped. 256 kB mirrors. + /// + All = 3 +} diff --git a/GCDTool/Wram/WramAMapping.cs b/GCDTool/Wram/WramAMapping.cs new file mode 100644 index 0000000..112927d --- /dev/null +++ b/GCDTool/Wram/WramAMapping.cs @@ -0,0 +1,63 @@ +namespace GCDTool.Wram; + +/// +/// Specifies the mapping of TWL wram A in memory. +/// +readonly record struct WramAMapping +{ + private const uint WRAM_START_ADDRESS = 0x03000000u; + private const uint WRAM_END_ADDRESS = 0x04000000u; + + private readonly uint _startOffset; + private readonly uint _endOffset; + + /// + /// The WRAM A slots that are mapped and mirrored in memory. + /// + public WramAMappedSlots MappedSlots { get; } + + /// + /// The start address of the WRAM A area in memory. + /// + public uint StartAddress => WRAM_START_ADDRESS + (_startOffset << 16); + + /// + /// The end address of the WRAM A area in memory. + /// + public uint EndAddress => WRAM_START_ADDRESS + (_endOffset << 16); + + public WramAMapping(uint startAddress, uint length, WramAMappedSlots mappedSlots) + { + if (startAddress < WRAM_START_ADDRESS) + { + throw new ArgumentException( + $"Start address (0x{startAddress:X8}) must be at least (0x{WRAM_START_ADDRESS:X8}).", nameof(startAddress)); + } + if ((startAddress & 0xFFFF) != 0) + { + throw new ArgumentException("Start address must be a multiple of 64 kB.", nameof(startAddress)); + } + if ((length & 0xFFFF) != 0) + { + throw new ArgumentException("Length must be a multiple of 64 kB.", nameof(length)); + } + uint endAddress = startAddress + length; + if (endAddress > WRAM_END_ADDRESS) + { + throw new ArgumentException( + $"Length is too large. Resulting end address (0x{endAddress:X8}) must be at most (0x{WRAM_END_ADDRESS:X8}).", + nameof(length)); + } + + _startOffset = (startAddress - WRAM_START_ADDRESS) >> 16; + _endOffset = (endAddress - WRAM_START_ADDRESS) >> 16; + MappedSlots = mappedSlots; + } + + public static explicit operator uint(WramAMapping wramAMapping) + { + return (wramAMapping._startOffset << 4) + | ((uint)wramAMapping.MappedSlots << 12) + | (wramAMapping._endOffset << 20); + } +} diff --git a/GCDTool/Wram/WramAMaster.cs b/GCDTool/Wram/WramAMaster.cs new file mode 100644 index 0000000..2c98df4 --- /dev/null +++ b/GCDTool/Wram/WramAMaster.cs @@ -0,0 +1,10 @@ +namespace GCDTool.Wram; + +/// +/// The master of a TWL wram A block. +/// +enum WramAMaster +{ + Arm9 = 0, + Arm7 = 1 +} \ No newline at end of file diff --git a/GCDTool/Wram/WramASlot.cs b/GCDTool/Wram/WramASlot.cs new file mode 100644 index 0000000..2f00481 --- /dev/null +++ b/GCDTool/Wram/WramASlot.cs @@ -0,0 +1,12 @@ +namespace GCDTool.Wram; + +/// +/// Specifies a slot of TWL wram A. +/// +enum WramASlot +{ + Slot0 = 0, + Slot1 = 1, + Slot2 = 2, + Slot3 = 3 +} \ No newline at end of file diff --git a/GCDTool/Wram/WramBBlockMapping.cs b/GCDTool/Wram/WramBBlockMapping.cs new file mode 100644 index 0000000..910091d --- /dev/null +++ b/GCDTool/Wram/WramBBlockMapping.cs @@ -0,0 +1,14 @@ +namespace GCDTool.Wram; + +/// +/// Specifies the mapping of TWL wram B block. +/// +readonly record struct WramBBlockMapping(WramBMaster Master, WramBCSlot Slot, bool Enable) +{ + public static explicit operator byte(WramBBlockMapping wramBBlockMapping) + { + return (byte)(((byte)wramBBlockMapping.Master & 3) + | (((byte)wramBBlockMapping.Slot & 7) << 2) + | (wramBBlockMapping.Enable ? 0x80 : 0)); + } +} \ No newline at end of file diff --git a/GCDTool/Wram/WramBCMappedSlots.cs b/GCDTool/Wram/WramBCMappedSlots.cs new file mode 100644 index 0000000..4e8264a --- /dev/null +++ b/GCDTool/Wram/WramBCMappedSlots.cs @@ -0,0 +1,27 @@ +namespace GCDTool.Wram; + +/// +/// Specifies which TWL wram B or C slots are mapped in memory. +/// +enum WramBCMappedSlots +{ + /// + /// Only slot 0 mapped. 32 kB mirrors. + /// + Slot0 = 0, + + /// + /// Only slot 0 and 1 mapped. 64 kB mirrors. + /// + Slot01 = 1, + + /// + /// Only slot 0, 1, 2 and 3 mapped. 128 kB mirrors. + /// + Slot0123 = 2, + + /// + /// All slots mapped. 256 kB mirrors. + /// + All = 3 +} \ No newline at end of file diff --git a/GCDTool/Wram/WramBCMapping.cs b/GCDTool/Wram/WramBCMapping.cs new file mode 100644 index 0000000..7ea6185 --- /dev/null +++ b/GCDTool/Wram/WramBCMapping.cs @@ -0,0 +1,63 @@ +namespace GCDTool.Wram; + +/// +/// Specifies the mapping of TWL wram B or C in memory. +/// +readonly record struct WramBCMapping +{ + private const uint WRAM_START_ADDRESS = 0x03000000u; + private const uint WRAM_END_ADDRESS = 0x04000000u; + + private readonly uint _startOffset; + private readonly uint _endOffset; + + /// + /// The WRAM A slots that are mapped and mirrored in memory. + /// + public WramBCMappedSlots MappedSlots { get; } + + /// + /// The start address of the WRAM B or C area in memory. + /// + public uint StartAddress => WRAM_START_ADDRESS + (_startOffset << 15); + + /// + /// The end address of the WRAM B or C area in memory. + /// + public uint EndAddress => WRAM_START_ADDRESS + (_endOffset << 15); + + public WramBCMapping(uint startAddress, uint length, WramBCMappedSlots mappedSlots) + { + if (startAddress < WRAM_START_ADDRESS) + { + throw new ArgumentException( + $"Start address (0x{startAddress:X8}) must be at least (0x{WRAM_START_ADDRESS:X8}).", nameof(startAddress)); + } + if ((startAddress & 0x7FFF) != 0) + { + throw new ArgumentException("Start address must be a multiple of 32 kB.", nameof(startAddress)); + } + if ((length & 0x7FFF) != 0) + { + throw new ArgumentException("Length must be a multiple of 32 kB.", nameof(length)); + } + uint endAddress = startAddress + length; + if (endAddress > WRAM_END_ADDRESS) + { + throw new ArgumentException( + $"Length is too large. Resulting end address (0x{endAddress:X8}) must be at most (0x{WRAM_END_ADDRESS:X8}).", + nameof(length)); + } + + _startOffset = (startAddress - WRAM_START_ADDRESS) >> 15; + _endOffset = (endAddress - WRAM_START_ADDRESS) >> 15; + MappedSlots = mappedSlots; + } + + public static explicit operator uint(WramBCMapping wramBCMapping) + { + return (wramBCMapping._startOffset << 3) + | ((uint)wramBCMapping.MappedSlots << 12) + | (wramBCMapping._endOffset << 19); + } +} diff --git a/GCDTool/Wram/WramBCSlot.cs b/GCDTool/Wram/WramBCSlot.cs new file mode 100644 index 0000000..bc767d8 --- /dev/null +++ b/GCDTool/Wram/WramBCSlot.cs @@ -0,0 +1,16 @@ +namespace GCDTool.Wram; + +/// +/// Specifies a slot of TWL wram B or C. +/// +enum WramBCSlot +{ + Slot0 = 0, + Slot1 = 1, + Slot2 = 2, + Slot3 = 3, + Slot4 = 4, + Slot5 = 5, + Slot6 = 6, + Slot7 = 7 +} \ No newline at end of file diff --git a/GCDTool/Wram/WramBMaster.cs b/GCDTool/Wram/WramBMaster.cs new file mode 100644 index 0000000..cd7af5f --- /dev/null +++ b/GCDTool/Wram/WramBMaster.cs @@ -0,0 +1,11 @@ +namespace GCDTool.Wram; + +/// +/// The master of a TWL wram B block. +/// +enum WramBMaster +{ + Arm9 = 0, + Arm7 = 1, + DspCode = 2 +} \ No newline at end of file diff --git a/GCDTool/Wram/WramCBlockMapping.cs b/GCDTool/Wram/WramCBlockMapping.cs new file mode 100644 index 0000000..3873498 --- /dev/null +++ b/GCDTool/Wram/WramCBlockMapping.cs @@ -0,0 +1,14 @@ +namespace GCDTool.Wram; + +/// +/// Specifies the mapping of TWL wram C block. +/// +readonly record struct WramCBlockMapping(WramCMaster Master, WramBCSlot Slot, bool Enable) +{ + public static explicit operator byte(WramCBlockMapping wramCBlockMapping) + { + return (byte)(((byte)wramCBlockMapping.Master & 3) + | (((byte)wramCBlockMapping.Slot & 7) << 2) + | (wramCBlockMapping.Enable ? 0x80 : 0)); + } +} \ No newline at end of file diff --git a/GCDTool/Wram/WramCMaster.cs b/GCDTool/Wram/WramCMaster.cs new file mode 100644 index 0000000..e3bc855 --- /dev/null +++ b/GCDTool/Wram/WramCMaster.cs @@ -0,0 +1,11 @@ +namespace GCDTool.Wram; + +/// +/// The master of a TWL wram C block. +/// +enum WramCMaster +{ + Arm9 = 0, + Arm7 = 1, + DspData = 2 +} \ No newline at end of file diff --git a/GCDTool/Wram/WramConfig.cs b/GCDTool/Wram/WramConfig.cs new file mode 100644 index 0000000..731dea2 --- /dev/null +++ b/GCDTool/Wram/WramConfig.cs @@ -0,0 +1,126 @@ +using GCDTool.IO; + +namespace GCDTool.Wram; + +/// +/// Class representing the initial wram configuration. +/// +sealed class WramConfig +{ + /// + /// The block mapping of TWL wram A. + /// + public WramABlockMapping[] TwlWramABlockMapping { get; } = new WramABlockMapping[4]; + + /// + /// The block mapping of TWL wram B. + /// + public WramBBlockMapping[] TwlWramBBlockMapping { get; } = new WramBBlockMapping[8]; + + /// + /// The block mapping of TWL wram C. + /// + public WramCBlockMapping[] TwlWramCBlockMapping { get; } = new WramCBlockMapping[8]; + + /// + /// The mapping of TWL wram A on the ARM9. + /// + public WramAMapping Arm9TwlWramAMapping { get; set; } + + /// + /// The mapping of TWL wram B on the ARM9. + /// + public WramBCMapping Arm9TwlWramBMapping { get; set; } + + /// + /// The mapping of TWL wram C on the ARM9. + /// + public WramBCMapping Arm9TwlWramCMapping { get; set; } + + /// + /// The mapping of TWL wram A on the ARM7. + /// + public WramAMapping Arm7TwlWramAMapping { get; set; } + + /// + /// The mapping of TWL wram B on the ARM7. + /// + public WramBCMapping Arm7TwlWramBMapping { get; set; } + + /// + /// The mapping of TWL wram C on the ARM7. + /// + public WramBCMapping Arm7TwlWramCMapping { get; set; } + + /// + /// For each TWL wram A block when locked, or otherwise. + /// + public bool[] TwlWramABlockLocked { get; } = new bool[4]; + + /// + /// For each TWL wram B block when locked, or otherwise. + /// + public bool[] TwlWramBBlockLocked { get; } = new bool[8]; + + /// + /// For each TWL wram C block when locked, or otherwise. + /// + public bool[] TwlWramCBlockLocked { get; } = new bool[8]; + + /// + /// The mapping of nitro wram. + /// + public NtrWramMaster[] NtrWramMapping { get; } = new NtrWramMaster[2]; + + /// + /// The mapping of vram C. + /// + public byte VramCMapping { get; set; } + + /// + /// The mapping of vram D. + /// + public byte VramDMapping { get; set; } + + /// + /// Writes the wram configuration to the given span. + /// + /// + public void Write(Span destination) + { + for (int i = 0; i < 4; i++) + { + destination[i] = (byte)TwlWramABlockMapping[i]; + } + for (int i = 0; i < 8; i++) + { + destination[4 + i] = (byte)TwlWramBBlockMapping[i]; + destination[12 + i] = (byte)TwlWramCBlockMapping[i]; + } + IOUtil.WriteU32Le(destination[0x14..], (uint)Arm9TwlWramAMapping); + IOUtil.WriteU32Le(destination[0x18..], (uint)Arm9TwlWramBMapping); + IOUtil.WriteU32Le(destination[0x1C..], (uint)Arm9TwlWramCMapping); + IOUtil.WriteU32Le(destination[0x20..], (uint)Arm7TwlWramAMapping); + IOUtil.WriteU32Le(destination[0x24..], (uint)Arm7TwlWramBMapping); + IOUtil.WriteU32Le(destination[0x28..], (uint)Arm7TwlWramCMapping); + destination[0x2C] = CreateLockMask(TwlWramABlockLocked); + destination[0x2D] = CreateLockMask(TwlWramBBlockLocked); + destination[0x2E] = CreateLockMask(TwlWramCBlockLocked); + destination[0x2F] = (byte)(((byte)NtrWramMapping[0] & 1) | (((byte)NtrWramMapping[1] & 1) << 1) + | (VramCMapping & 7) << 2 | (VramDMapping & 7) << 5); + } + + private byte CreateLockMask(ReadOnlySpan locks) + { + byte wramLocks = 0; + for (int i = 0; i < locks.Length; i++) + { + if (locks[i]) + { + wramLocks |= (byte)(1 << i); + } + } + + return wramLocks; + } +} diff --git a/GCDTool/Wram/WramConfigJsonReader.cs b/GCDTool/Wram/WramConfigJsonReader.cs new file mode 100644 index 0000000..1b03d51 --- /dev/null +++ b/GCDTool/Wram/WramConfigJsonReader.cs @@ -0,0 +1,254 @@ +using System.ComponentModel; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace GCDTool.Wram; + +/// +/// Class for reading from a JSON file. +/// +sealed class WramConfigJsonReader +{ + private const int WRAM_A_BLOCK_COUNT = 4; + private const int WRAM_BC_BLOCK_COUNT = 8; + + private const string MASTER_ARM9 = "arm9"; + private const string MASTER_ARM7 = "arm7"; + private const string MASTER_DSP = "dsp"; + + private const string SLOT_0 = "0"; + private const string SLOT_01 = "01"; + private const string SLOT_0123 = "0123"; + private const string SLOT_01234567 = "01234567"; + private const string SLOT_ALL = "all"; + + /// + /// Reads a from the given . + /// + /// The JSON string containing the WRAM configuration. + /// The constructed . + public WramConfig ReadWramConfigFromJson(string jsonString) + { + var wramConfig = new WramConfig(); + var json = JsonDocument.Parse(jsonString).RootElement; + ParseWramAConfig(wramConfig, json.GetProperty("wramA")); + ParseWramBConfig(wramConfig, json.GetProperty("wramB")); + ParseWramCConfig(wramConfig, json.GetProperty("wramC")); + + var ntrWram = json.GetProperty("ntrWram"); + if (ntrWram.GetArrayLength() != 2) + { + throw new InvalidDataException("'ntrWram' array should have exactly 2 items."); + } + + wramConfig.NtrWramMapping[0] = ParseNtrWramMapping(ntrWram[0].GetString()); + wramConfig.NtrWramMapping[1] = ParseNtrWramMapping(ntrWram[1].GetString()); + wramConfig.VramCMapping = json.GetProperty("vramC").GetByte(); + wramConfig.VramDMapping = json.GetProperty("vramD").GetByte(); + return wramConfig; + } + + private void ParseWramAConfig(WramConfig wramConfig, JsonElement wramAJson) + { + wramConfig.Arm9TwlWramAMapping = wramAJson.TryGetProperty("arm9", out var arm9) + ? ParseWramAMapping(arm9) + : new WramAMapping(0x03000000, 0, WramAMappedSlots.Slot0); // unmapped + + wramConfig.Arm7TwlWramAMapping = wramAJson.TryGetProperty("arm7", out var arm7) + ? ParseWramAMapping(arm7) + : new WramAMapping(0x03000000, 0, WramAMappedSlots.Slot0); // unmapped + + var blocks = wramAJson.GetProperty("blocks"); + if (blocks.GetArrayLength() != WRAM_A_BLOCK_COUNT) + { + throw new InvalidDataException($"Wram A 'blocks' array should have exactly {WRAM_A_BLOCK_COUNT} items."); + } + for (int i = 0; i < WRAM_A_BLOCK_COUNT; i++) + { + wramConfig.TwlWramABlockMapping[i] = ParseWramABlockMapping( + blocks[i], out wramConfig.TwlWramABlockLocked[i]); + } + } + + private void ParseWramBConfig(WramConfig wramConfig, JsonElement wramBJson) + { + wramConfig.Arm9TwlWramBMapping = wramBJson.TryGetProperty("arm9", out var arm9) + ? ParseWramBCMapping(arm9) + : new WramBCMapping(0x03000000, 0, WramBCMappedSlots.Slot0); // unmapped + + wramConfig.Arm7TwlWramBMapping = wramBJson.TryGetProperty("arm7", out var arm7) + ? ParseWramBCMapping(arm7) + : new WramBCMapping(0x03000000, 0, WramBCMappedSlots.Slot0); // unmapped + + var blocks = wramBJson.GetProperty("blocks"); + if (blocks.GetArrayLength() != WRAM_BC_BLOCK_COUNT) + { + throw new InvalidDataException($"Wram B 'blocks' array should have exactly {WRAM_BC_BLOCK_COUNT} items."); + } + for (int i = 0; i < WRAM_BC_BLOCK_COUNT; i++) + { + wramConfig.TwlWramBBlockMapping[i] = ParseWramBBlockMapping( + blocks[i], out wramConfig.TwlWramBBlockLocked[i]); + } + } + + private void ParseWramCConfig(WramConfig wramConfig, JsonElement wramCJson) + { + wramConfig.Arm9TwlWramCMapping = wramCJson.TryGetProperty("arm9", out var arm9) + ? ParseWramBCMapping(arm9) + : new WramBCMapping(0x03000000, 0, WramBCMappedSlots.Slot0); // unmapped + + wramConfig.Arm7TwlWramCMapping = wramCJson.TryGetProperty("arm7", out var arm7) + ? ParseWramBCMapping(arm7) + : new WramBCMapping(0x03000000, 0, WramBCMappedSlots.Slot0); // unmapped + + var blocks = wramCJson.GetProperty("blocks"); + if (blocks.GetArrayLength() != WRAM_BC_BLOCK_COUNT) + { + throw new InvalidDataException($"Wram C 'blocks' array should have exactly {WRAM_BC_BLOCK_COUNT} items."); + } + for (int i = 0; i < WRAM_BC_BLOCK_COUNT; i++) + { + wramConfig.TwlWramCBlockMapping[i] = ParseWramCBlockMapping( + blocks[i], out wramConfig.TwlWramCBlockLocked[i]); + } + } + + private WramAMapping ParseWramAMapping(JsonElement mappingJson) + { + var wramMapping = mappingJson.Deserialize() + ?? throw new InvalidDataException("Could not parse wram A mapping."); + return new( + ParseHexString(wramMapping.Start), + ParseHexString(wramMapping.Length), + wramMapping.Slots switch + { + SLOT_0 => WramAMappedSlots.Slot0, + SLOT_01 => WramAMappedSlots.Slot01, + SLOT_0123 or SLOT_ALL => WramAMappedSlots.All, + _ => throw new InvalidDataException("Invalid value specified for wram A 'slots'.") + }); + } + + private WramBCMapping ParseWramBCMapping(JsonElement mappingJson) + { + var wramMapping = mappingJson.Deserialize() + ?? throw new InvalidDataException("Could not parse wram B or C mapping."); + return new( + ParseHexString(wramMapping.Start), + ParseHexString(wramMapping.Length), + wramMapping.Slots switch + { + SLOT_0 => WramBCMappedSlots.Slot0, + SLOT_01 => WramBCMappedSlots.Slot01, + SLOT_0123 => WramBCMappedSlots.Slot0123, + SLOT_01234567 or SLOT_ALL => WramBCMappedSlots.All, + _ => throw new InvalidDataException("Invalid value specified for wram B or C 'slots'.") + }); + } + + private WramABlockMapping ParseWramABlockMapping(JsonElement blockMappingJson, out bool locked) + { + var blockMapping = blockMappingJson.Deserialize() + ?? throw new InvalidDataException("Could not parse wram A block mapping."); + if (blockMapping.Slot >= WRAM_A_BLOCK_COUNT) + { + throw new InvalidDataException("Invalid slot specified for wram A block mapping."); + } + locked = blockMapping.Locked; + return new( + blockMapping.Master switch + { + MASTER_ARM7 => WramAMaster.Arm7, + MASTER_ARM9 => WramAMaster.Arm9, + _ => throw new InvalidDataException("Invalid master specified for wram A block mapping.") + }, + (WramASlot)blockMapping.Slot, + blockMapping.Enabled); + } + + private WramBBlockMapping ParseWramBBlockMapping(JsonElement blockMappingJson, out bool locked) + { + var blockMapping = blockMappingJson.Deserialize() + ?? throw new InvalidDataException("Could not parse wram B block mapping."); + if (blockMapping.Slot >= WRAM_BC_BLOCK_COUNT) + { + throw new InvalidDataException("Invalid slot specified for wram B block mapping."); + } + locked = blockMapping.Locked; + return new( + blockMapping.Master switch + { + MASTER_ARM9 => WramBMaster.Arm9, + MASTER_ARM7 => WramBMaster.Arm7, + MASTER_DSP => WramBMaster.DspCode, + _ => throw new InvalidDataException("Invalid master specified for wram B block mapping.") + }, + (WramBCSlot)blockMapping.Slot, + blockMapping.Enabled); + } + + private WramCBlockMapping ParseWramCBlockMapping(JsonElement blockMappingJson, out bool locked) + { + var blockMapping = blockMappingJson.Deserialize() + ?? throw new InvalidDataException("Could not parse wram C block mapping."); + if (blockMapping.Slot >= WRAM_BC_BLOCK_COUNT) + { + throw new InvalidDataException("Invalid slot specified for wram C block mapping."); + } + locked = blockMapping.Locked; + return new( + blockMapping.Master switch + { + MASTER_ARM9 => WramCMaster.Arm9, + MASTER_ARM7 => WramCMaster.Arm7, + MASTER_DSP => WramCMaster.DspData, + _ => throw new InvalidDataException("Invalid master specified for wram C block mapping.") + }, + (WramBCSlot)blockMapping.Slot, + blockMapping.Enabled); + } + + private NtrWramMaster ParseNtrWramMapping(string? mapping) => mapping switch + { + MASTER_ARM9 => NtrWramMaster.Arm9, + MASTER_ARM7 => NtrWramMaster.Arm7, + _ => throw new InvalidDataException("Invalid master specified for ntr wram mapping.") + }; + + private uint ParseHexString(string hexString) + { + if (new UInt32Converter().ConvertFromString(hexString) is not uint value) + { + throw new InvalidDataException("Invalid start address or length specified."); + } + return value; + } + + private sealed class JsonWramMapping + { + [JsonPropertyName("start")] + public string Start { get; set; } = "0x03000000"; + + [JsonPropertyName("length")] + public string Length { get; set; } = "0"; + + [JsonPropertyName("slots")] + public string Slots { get; set; } = SLOT_0; + } + + private sealed class JsonBlockMapping + { + [JsonPropertyName("master")] + public string Master { get; set; } = MASTER_ARM9; + + [JsonPropertyName("slot")] + public uint Slot { get; set; } = 0; + + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } = true; + + [JsonPropertyName("locked")] + public bool Locked { get; set; } = false; + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..75a9a55 --- /dev/null +++ b/readme.md @@ -0,0 +1,88 @@ +# GCDTool +Tool to create a GCD rom for DSi ntrboot. + +## Usage +`GCDTool --arm9 arm9.elf --arm7 arm7.elf --wram wramConfig.json --rsakey gcdKey.der output.gcd` + +### Arguments +#### Mandatory +- `--arm9` specifies the arm9 elf file; +- `--arm7` specifies the arm7 elf file; +- `--wram` specifies the wram configuration json file (see below); +- `--rsakey` specifies the der file with the RSA private key to use. + +The arm7 and arm9 binaries should have a load address in internal ram (for example TWL wram), as main memory is not yet enabled when the GCD rom is booted. + +#### Optional +- `--gamecode` specifies the 4 character game code to use (default: ####); +- `--arm9-67-mhz` runs the arm9 at 67 MHz instead of 134 MHz. + +### Wram configuration JSON file +Specifies the initial wram configuration to use. + +#### Example +```json +{ + "wramA": { + "arm7": { "start": "0x03800000", "length": "0x40000", "slots": "all" }, + "blocks": [ + { "master": "arm7", "slot": 0 }, + { "master": "arm7", "slot": 1 }, + { "master": "arm7", "slot": 2 }, + { "master": "arm7", "slot": 3 } + ] + }, + "wramB": { + "arm9": { "start": "0x03800000", "length": "0x40000", "slots": "all" }, + "blocks": [ + { "master": "arm9", "slot": 0 }, + { "master": "arm9", "slot": 1 }, + { "master": "arm9", "slot": 2 }, + { "master": "arm9", "slot": 3 }, + { "master": "arm9", "slot": 4 }, + { "master": "arm9", "slot": 5 }, + { "master": "arm9", "slot": 6 }, + { "master": "arm9", "slot": 7 } + ] + }, + "wramC": { + "arm9": { "start": "0x03840000", "length": "0x40000", "slots": "all" }, + "blocks": [ + { "master": "arm9", "slot": 0 }, + { "master": "arm9", "slot": 1 }, + { "master": "arm9", "slot": 2 }, + { "master": "arm9", "slot": 3 }, + { "master": "arm9", "slot": 4 }, + { "master": "arm9", "slot": 5 }, + { "master": "arm9", "slot": 6 }, + { "master": "arm9", "slot": 7 } + ] + }, + "ntrWram" : [ "arm7", "arm7" ], + "vramC": 7, + "vramD": 7 +} +``` + +#### TWL wram mapping +For TWL wram A, B and C the mapping in memory can be specified for both the arm9 and arm7 side with the `"arm9"` and `"arm7"` properties (can be omitted to not map the ram at all). Start and length must be 64 kB aligned for wram A, and 32 kB aligned for wram B and C. `"slots"` specifies which slots are actually mapped in memory, and as such also the size of the mirroring. For wram A the options are `"0"` (64 kB), `"01"` (128 kB) and `"all"` or `"0123"` (256 kB). For wram B and C the options are `"0"` (32 kB), `"01"` (64 kB), `"0123"` (128 kB) and `"all"` or `"01234567"` (256 kB). + +#### TWL wram block mapping +For each block of TWL wram the following properties are available: +- `"master"` specifies what the block is mapped to. All wrams support `"arm9"` and `"arm7"`, and wram B and C additionally support `"dsp"` for dsp code and data respectively; +- `"slot"` specifies the slot the block is mapped to; +- `"enabled"` specifies whether the block is enabled. This defaults to `true` and can be omitted; +- `"locked"` specifies whether the mapping of the block should be locked, such that the arm9 cannot change it. This defaults to `false` and can be omitted. + +When `"enabled"` is set to `false`, `"master"` and `"slot"` can be omitted. + +#### NTR wram mapping +Specifies for the two NTR wram blocks whether they are mapped to `"arm9"` or `"arm7"`. + +#### Vram mapping +Not sure how this works. There are only 3 bits available, but the vram C and D mapping registers need more bits properly specify the mapping and enable it. Default value is 7 for both. + +## Booting the GCD rom +The GCD rom needs to be flashed to a (dev) flashcard or other means of emulating a DS cartridge. Make sure the blowfish table is used that is included in the generated .gcd file (p table at `0x1600` and s boxes at `0x1C00`). + +With the cartridge inserted, the GCD rom is booted by putting a small magnet between the ABXY buttons (to trigger the lid close detection), holding START, SELECT and X and pressing the power button.