├── .gitattributes
├── .gitignore
├── GCDTool.sln
├── GCDTool
├── AesKeyScrambler.cs
├── Blowfish.cs
├── CommandLineOptions.cs
├── Elf
│ ├── ElfFile.cs
│ ├── ElfHeader.cs
│ ├── ElfSectionType.cs
│ ├── ProgramHeaderTableEntry.cs
│ ├── SectionHeaderTableEntry.cs
│ └── Sections
│ │ ├── ElfSection.cs
│ │ ├── ElfSectionFactory.cs
│ │ ├── ElfStrtab.cs
│ │ ├── ElfSymbol.cs
│ │ └── ElfSymtab.cs
├── GCDTool.csproj
├── GcdBlowfish.cs
├── GcdHeader.cs
├── GcdHeaderFlags.cs
├── GcdRom.cs
├── GcdSignature.cs
├── IO
│ ├── EndianBinaryReader.cs
│ ├── Endianness.cs
│ └── IOUtil.cs
├── Program.cs
└── Wram
│ ├── NtrWramMaster.cs
│ ├── WramABlockMapping.cs
│ ├── WramAMappedSlots.cs
│ ├── WramAMapping.cs
│ ├── WramAMaster.cs
│ ├── WramASlot.cs
│ ├── WramBBlockMapping.cs
│ ├── WramBCMappedSlots.cs
│ ├── WramBCMapping.cs
│ ├── WramBCSlot.cs
│ ├── WramBMaster.cs
│ ├── WramCBlockMapping.cs
│ ├── WramCMaster.cs
│ ├── WramConfig.cs
│ └── WramConfigJsonReader.cs
└── readme.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | project.fragment.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | #*.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.jfm
193 | *.pfx
194 | *.publishsettings
195 | node_modules/
196 | orleans.codegen.cs
197 |
198 | # Since there are multiple workflows, uncomment next line to ignore bower_components
199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
200 | #bower_components/
201 |
202 | # RIA/Silverlight projects
203 | Generated_Code/
204 |
205 | # Backup & report files from converting an old project file
206 | # to a newer Visual Studio version. Backup files are not needed,
207 | # because we have git ;-)
208 | _UpgradeReport_Files/
209 | Backup*/
210 | UpgradeLog*.XML
211 | UpgradeLog*.htm
212 |
213 | # SQL Server files
214 | *.mdf
215 | *.ldf
216 |
217 | # Business Intelligence projects
218 | *.rdl.data
219 | *.bim.layout
220 | *.bim_*.settings
221 |
222 | # Microsoft Fakes
223 | FakesAssemblies/
224 |
225 | # GhostDoc plugin setting file
226 | *.GhostDoc.xml
227 |
228 | # Node.js Tools for Visual Studio
229 | .ntvs_analysis.dat
230 |
231 | # Visual Studio 6 build log
232 | *.plg
233 |
234 | # Visual Studio 6 workspace options file
235 | *.opt
236 |
237 | # Visual Studio LightSwitch build output
238 | **/*.HTMLClient/GeneratedArtifacts
239 | **/*.DesktopClient/GeneratedArtifacts
240 | **/*.DesktopClient/ModelManifest.xml
241 | **/*.Server/GeneratedArtifacts
242 | **/*.Server/ModelManifest.xml
243 | _Pvt_Extensions
244 |
245 | # Paket dependency manager
246 | .paket/paket.exe
247 | paket-files/
248 |
249 | # FAKE - F# Make
250 | .fake/
251 |
252 | # JetBrains Rider
253 | .idea/
254 | *.sln.iml
255 |
256 | # CodeRush
257 | .cr/
258 |
259 | # Python Tools for Visual Studio (PTVS)
260 | __pycache__/
261 | *.pyc
--------------------------------------------------------------------------------
/GCDTool.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.8.34309.116
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GCDTool", "GCDTool\GCDTool.csproj", "{C9C84AF7-5F36-4623-AE76-19AC55B64450}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {C9C84AF7-5F36-4623-AE76-19AC55B64450}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {C9C84AF7-5F36-4623-AE76-19AC55B64450}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {C9C84AF7-5F36-4623-AE76-19AC55B64450}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {C9C84AF7-5F36-4623-AE76-19AC55B64450}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {76A73BFB-4C65-473A-821E-CDCA3B40E343}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/GCDTool/AesKeyScrambler.cs:
--------------------------------------------------------------------------------
1 | using System.Buffers.Binary;
2 |
3 | namespace GCDTool;
4 |
5 | ///
6 | /// Static class implementing the DSi AES key scrambler.
7 | ///
8 | public static class AesKeyScrambler
9 | {
10 | private static readonly UInt128 scramblerMagic = new(0xFFFEFB4E29590258ul, 0x2A680F5F1A4F3E79ul);
11 |
12 | private const int SCRAMBLER_ROL = 42;
13 |
14 | ///
15 | /// Scrambles the given and .
16 | ///
17 | /// Input key X.
18 | /// Input key Y.
19 | /// The scrambled key.
20 | public static byte[] Scramble(ReadOnlySpan keyX, ReadOnlySpan keyY)
21 | {
22 | UInt128 scrambledKey = Scramble(
23 | BinaryPrimitives.ReadUInt128LittleEndian(keyX),
24 | BinaryPrimitives.ReadUInt128LittleEndian(keyY));
25 | var result = new byte[16];
26 | BinaryPrimitives.WriteUInt128LittleEndian(result, scrambledKey);
27 | return result;
28 | }
29 |
30 | ///
31 | /// Scrambles the given and .
32 | ///
33 | /// Input key X.
34 | /// Input key Y.
35 | /// The scrambled key.
36 | public static UInt128 Scramble(UInt128 keyX, UInt128 keyY)
37 | {
38 | return UInt128.RotateLeft((keyX ^ keyY) + scramblerMagic, SCRAMBLER_ROL);
39 | }
40 | }
--------------------------------------------------------------------------------
/GCDTool/Blowfish.cs:
--------------------------------------------------------------------------------
1 | using GCDTool.IO;
2 |
3 | namespace GCDTool;
4 |
5 | ///
6 | /// Class for performing Blowfish encryption and decryption.
7 | ///
8 | public sealed class Blowfish
9 | {
10 | public const int BLOCK_LENGTH = 8;
11 | public const int KEY_TABLE_LENGTH = 0x1048;
12 | public const int P_TABLE_ENTRY_COUNT = 18;
13 | public const int S_BOX_COUNT = 4;
14 | public const int S_BOX_ENTRY_COUNT = 256;
15 |
16 | private const string DATA_LENGTH_NOT_MULTIPLE_OF_8_EXCEPTION_MESSAGE = "Data length must be a multiple of 8.";
17 | private const string DESTINATION_BUFFER_TOO_SMALL_EXCEPTION_MESSAGE = "Not enough space in destination buffer.";
18 |
19 | private readonly uint[] _pTable;
20 | private readonly uint[][] _sBoxes;
21 |
22 | public Blowfish(uint[] pTable, uint[][] sBoxes)
23 | {
24 | if (pTable.Length != P_TABLE_ENTRY_COUNT)
25 | {
26 | throw new ArgumentException($"Size of p table should be {P_TABLE_ENTRY_COUNT}", nameof(pTable));
27 | }
28 |
29 | if (sBoxes.Length != S_BOX_COUNT)
30 | {
31 | throw new ArgumentException($"Number of s boxes should be {S_BOX_COUNT}", nameof(sBoxes));
32 | }
33 |
34 | for (int i = 0; i < S_BOX_COUNT; i++)
35 | {
36 | if (sBoxes[i].Length != S_BOX_ENTRY_COUNT)
37 | {
38 | throw new ArgumentException($"Size of s box {i} should be {S_BOX_ENTRY_COUNT}", nameof(sBoxes));
39 | }
40 | }
41 |
42 | _pTable = pTable;
43 | _sBoxes = sBoxes;
44 | }
45 |
46 | public Blowfish(ReadOnlySpan keyTable)
47 | {
48 | if (keyTable.Length < KEY_TABLE_LENGTH)
49 | {
50 | throw new ArgumentException(nameof(keyTable));
51 | }
52 |
53 | _pTable = IOUtil.ReadU32Le(keyTable, P_TABLE_ENTRY_COUNT);
54 | _sBoxes = new uint[S_BOX_COUNT][];
55 | _sBoxes[0] = IOUtil.ReadU32Le(keyTable[0x48..], S_BOX_ENTRY_COUNT);
56 | _sBoxes[1] = IOUtil.ReadU32Le(keyTable[0x448..], S_BOX_ENTRY_COUNT);
57 | _sBoxes[2] = IOUtil.ReadU32Le(keyTable[0x848..], S_BOX_ENTRY_COUNT);
58 | _sBoxes[3] = IOUtil.ReadU32Le(keyTable[0xC48..], S_BOX_ENTRY_COUNT);
59 | }
60 |
61 | ///
62 | /// Encrypts bytes of data in the given buffer
63 | /// starting at . must be a multiple of 8.
64 | ///
65 | /// The data buffer.
66 | /// The offset in the buffer to start encrypting.
67 | /// The number of bytes to encrypt. Should be a multiple of 8.
68 | public void Encrypt(byte[] data, int offset, int length)
69 | {
70 | Encrypt(data.AsSpan(offset, length));
71 | }
72 |
73 | ///
74 | /// Encrypts the data in the given span in place.
75 | /// The length of the span must be a multiple of 8.
76 | ///
77 | /// The data to encrypt.
78 | public void Encrypt(Span data)
79 | {
80 | ThrowIfDataLengthNotMultipleOf8(data.Length, nameof(data));
81 |
82 | for (int i = 0; i < data.Length; i += BLOCK_LENGTH)
83 | {
84 | ulong val = Encrypt(IOUtil.ReadU64Le(data[i..]));
85 | IOUtil.WriteU64Le(data[i..], val);
86 | }
87 | }
88 |
89 | ///
90 | /// Encrypts a single 64 bit value.
91 | ///
92 | /// The value to encrypt.
93 | /// The encrypted value.
94 | public ulong Encrypt(ulong val)
95 | {
96 | uint y = (uint)(val & 0xFFFFFFFF);
97 | uint x = (uint)(val >> 32);
98 | for (int i = 0; i < 16; i++)
99 | {
100 | uint z = _pTable[i] ^ x;
101 | uint a = _sBoxes[0][z >> 24 & 0xFF];
102 | uint b = _sBoxes[1][z >> 16 & 0xFF];
103 | uint c = _sBoxes[2][z >> 8 & 0xFF];
104 | uint d = _sBoxes[3][z & 0xFF];
105 | x = d + (c ^ b + a) ^ y;
106 | y = z;
107 | }
108 |
109 | return x ^ _pTable[16] | (ulong)(y ^ _pTable[17]) << 32;
110 | }
111 |
112 | ///
113 | /// Decrypts bytes of data starting as in
114 | /// and writes it to starting from .
115 | /// must be a multiple of 8.
116 | ///
117 | /// The buffer containing the data to decrypt.
118 | /// The offset in where the encrypted data starts.
119 | /// The amount of data to decrypt. Should be a multiple of 8
120 | /// The buffer to write the decrypted data to.
121 | /// The offset in where the decrypted data should be written.
122 | public void Decrypt(byte[] src, int srcOffset, int length, byte[] dst, int dstOffset)
123 | {
124 | Decrypt(src.AsSpan(srcOffset, length), dst.AsSpan(dstOffset, length));
125 | }
126 |
127 | ///
128 | /// Decrypts the data in the given span in place.
129 | /// The length of the span must be a multiple of 8.
130 | ///
131 | /// The data to decrypt.
132 | public void Decrypt(Span data)
133 | {
134 | Decrypt(data, data);
135 | }
136 |
137 | ///
138 | /// Decrypts the data in the given span and writes it to the span.
139 | /// The length of the span must be a multiple of 8.
140 | /// must have a length equal to or larger than .
141 | ///
142 | /// The data to decrypt.
143 | /// The span to write the decrypted data to.
144 | public void Decrypt(ReadOnlySpan src, Span dst)
145 | {
146 | ThrowIfDataLengthNotMultipleOf8(src.Length, nameof(src));
147 |
148 | if (dst.Length < src.Length)
149 | {
150 | throw new ArgumentException(DESTINATION_BUFFER_TOO_SMALL_EXCEPTION_MESSAGE, nameof(dst));
151 | }
152 |
153 | for (int i = 0; i < src.Length; i += BLOCK_LENGTH)
154 | {
155 | ulong val = Decrypt(IOUtil.ReadU64Le(src[i..]));
156 | IOUtil.WriteU64Le(dst[i..], val);
157 | }
158 | }
159 |
160 | ///
161 | /// Decrypts a single 64 bit value.
162 | ///
163 | /// The value to decrypt.
164 | /// The decrypted value.
165 | public ulong Decrypt(ulong val)
166 | {
167 | uint y = (uint)(val & 0xFFFFFFFF);
168 | uint x = (uint)(val >> 32);
169 | for (int i = 17; i >= 2; i--)
170 | {
171 | uint z = _pTable[i] ^ x;
172 | uint a = _sBoxes[0][z >> 24 & 0xFF];
173 | uint b = _sBoxes[1][z >> 16 & 0xFF];
174 | uint c = _sBoxes[2][z >> 8 & 0xFF];
175 | uint d = _sBoxes[3][z & 0xFF];
176 | x = d + (c ^ b + a) ^ y;
177 | y = z;
178 | }
179 |
180 | return x ^ _pTable[1] | (ulong)(y ^ _pTable[0]) << 32;
181 | }
182 |
183 | private void ThrowIfDataLengthNotMultipleOf8(int dataLength, string paramName)
184 | {
185 | if ((dataLength & 7) != 0)
186 | {
187 | throw new ArgumentException(DATA_LENGTH_NOT_MULTIPLE_OF_8_EXCEPTION_MESSAGE, paramName);
188 | }
189 | }
190 | }
--------------------------------------------------------------------------------
/GCDTool/CommandLineOptions.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 |
3 | namespace GCDTool;
4 |
5 | ///
6 | /// Class containing the command line options of GCDTool.
7 | ///
8 | sealed class CommandLineOptions
9 | {
10 | [Option("arm9", Required = true, HelpText = "ARM9 elf file path.")]
11 | public required string Arm9Path { get; init; }
12 |
13 | [Option("arm7", Required = true, HelpText = "ARM7 elf file path.")]
14 | public required string Arm7Path { get; init; }
15 |
16 | [Option("wram", Required = true, HelpText = "Initial WRAM configuration json file path.")]
17 | public required string WramConfigJsonPath { get; init; }
18 |
19 | [Option("rsakey", Required = true, HelpText = "GCD RSA private key der file path.")]
20 | public required string GcdKeyPath { get; init; }
21 |
22 | [Option("gamecode", Default = "####", Required = false, HelpText = "The gamecode to use.")]
23 | public required string GameCode { get; init; }
24 |
25 | [Option("arm9-67-mhz", Default = false, Required = false, HelpText = "When true, the ARM9 will start at 67 MHz instead of 134 MHz.")]
26 | public bool Arm9Speed67MHz { get; init; }
27 |
28 | [Value(0, MetaName = "output path", Required = true, HelpText = "The path of the gcd file to create.")]
29 | public required string OutputPath { get; init; }
30 | }
31 |
--------------------------------------------------------------------------------
/GCDTool/Elf/ElfFile.cs:
--------------------------------------------------------------------------------
1 | using GCDTool.Elf.Sections;
2 | using GCDTool.IO;
3 |
4 | namespace GCDTool.Elf;
5 |
6 | sealed class ElfFile
7 | {
8 | public ElfFile(byte[] data)
9 | {
10 | var er = new EndianBinaryReader(new MemoryStream(data), Endianness.LittleEndian);
11 | Header = new ElfHeader(er);
12 |
13 | er.BaseStream.Position = Header.ProgramHeaderTableOffset;
14 | ProgramHeaderTable = new ProgramHeaderTableEntry[Header.ProgramHeaderTableEntryCount];
15 | for (int i = 0; i < Header.ProgramHeaderTableEntryCount; i++)
16 | {
17 | ProgramHeaderTable[i] = new ProgramHeaderTableEntry(er);
18 | }
19 |
20 | er.BaseStream.Position = Header.SectionHeaderTableOffset;
21 | SectionHeaderTable = new SectionHeaderTableEntry[Header.SectionHeaderTableEntryCount];
22 | for (int i = 0; i < Header.SectionHeaderTableEntryCount; i++)
23 | {
24 | SectionHeaderTable[i] = new SectionHeaderTableEntry(er);
25 | }
26 |
27 | er.Close();
28 |
29 | var namestab = new ElfStrtab(SectionHeaderTable[Header.SectionNamesIndex], string.Empty);
30 | Sections = new ElfSection[SectionHeaderTable.Length];
31 | var sectionFactory = new ElfSectionFactory();
32 | for (int i = 0; i < SectionHeaderTable.Length; i++)
33 | {
34 | Sections[i] = sectionFactory.CreateElfSection(SectionHeaderTable[i],
35 | namestab.GetString(SectionHeaderTable[i].NameOffset));
36 | }
37 | }
38 |
39 | public ElfHeader Header;
40 |
41 | public ProgramHeaderTableEntry[] ProgramHeaderTable;
42 |
43 | public SectionHeaderTableEntry[] SectionHeaderTable;
44 |
45 | public ElfSection[] Sections { get; }
46 |
47 | public ElfSection? GetSectionByName(string name)
48 | {
49 | return Sections.FirstOrDefault(section => section.Name == name);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/GCDTool/Elf/ElfHeader.cs:
--------------------------------------------------------------------------------
1 | using GCDTool.IO;
2 |
3 | namespace GCDTool.Elf;
4 |
5 | sealed class ElfHeader
6 | {
7 | private const uint ElfHeaderMagic = 0x464C457F;
8 |
9 | public ElfHeader(EndianBinaryReader er)
10 | {
11 | Magic = er.Read();
12 | if (Magic != ElfHeaderMagic)
13 | {
14 | throw new InvalidDataException("Elf magic invalid!");
15 | }
16 |
17 | BitFormat = er.Read();
18 | Endianness = er.Read();
19 | Version = er.Read();
20 | Abi = er.Read();
21 | AbiVersion = er.Read();
22 | Padding = er.Read(7);
23 | ObjectType = er.Read();
24 | Architecture = er.Read();
25 | Version2 = er.Read();
26 | EntryPoint = er.Read();
27 | ProgramHeaderTableOffset = er.Read();
28 | SectionHeaderTableOffset = er.Read();
29 | Flags = er.Read();
30 | HeaderSize = er.Read();
31 | ProgramHeaderTableEntrySize = er.Read();
32 | ProgramHeaderTableEntryCount = er.Read();
33 | SectionHeaderTableEntrySize = er.Read();
34 | SectionHeaderTableEntryCount = er.Read();
35 | SectionNamesIndex = er.Read();
36 | }
37 |
38 | public uint Magic;
39 | public byte BitFormat;
40 | public byte Endianness;
41 | public byte Version;
42 | public byte Abi;
43 | public byte AbiVersion;
44 | public byte[] Padding;//7
45 | public ushort ObjectType;
46 | public ushort Architecture;
47 | public uint Version2;
48 | public uint EntryPoint;
49 | public uint ProgramHeaderTableOffset;
50 | public uint SectionHeaderTableOffset;
51 | public uint Flags;
52 | public ushort HeaderSize;
53 | public ushort ProgramHeaderTableEntrySize;
54 | public ushort ProgramHeaderTableEntryCount;
55 | public ushort SectionHeaderTableEntrySize;
56 | public ushort SectionHeaderTableEntryCount;
57 | public ushort SectionNamesIndex;
58 | }
--------------------------------------------------------------------------------
/GCDTool/Elf/ElfSectionType.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool.Elf;
2 |
3 | enum ElfSectionType : uint
4 | {
5 | Null = 0,
6 | Progbits = 1,
7 | Symtab = 2,
8 | Strtab = 3,
9 | Rela = 4,
10 | Hash = 5,
11 | Dynamic = 6,
12 | Note = 7,
13 | Nobits = 8,
14 | Rel = 9,
15 | Shlib = 10,
16 | Dynsym = 11,
17 | InitArray = 14,
18 | FiniArray = 15,
19 | PreinitArray = 16,
20 | Group = 17,
21 | SymtabShndx = 18,
22 | Num = 19,
23 | Loos = 0x60000000,
24 | Hios = 0x6fffffff,
25 | Loproc = 0x70000000,
26 | Hiproc = 0x7fffffff,
27 | Louser = 0x80000000,
28 | Hiuser = 0xffffffff
29 | }
--------------------------------------------------------------------------------
/GCDTool/Elf/ProgramHeaderTableEntry.cs:
--------------------------------------------------------------------------------
1 | using GCDTool.IO;
2 |
3 | namespace GCDTool.Elf;
4 |
5 | sealed class ProgramHeaderTableEntry
6 | {
7 | public enum ElfSegmentType : uint
8 | {
9 | Null = 0,
10 | Load = 1,
11 | Dynamic = 2,
12 | Interp = 3,
13 | Note = 4,
14 | Shlib = 5,
15 | Phdr = 6,
16 | Tls = 7,
17 | Num = 8,
18 | Loos = 0x60000000,
19 | Hios = 0x6fffffff,
20 | Loproc = 0x70000000,
21 | Hiproc = 0x7fffffff
22 | }
23 |
24 | public ProgramHeaderTableEntry(EndianBinaryReader er)
25 | {
26 | SegmentType = er.Read();
27 | FileImageOffset = er.Read();
28 | VirtualAddress = er.Read();
29 | PhysicalAddress = er.Read();
30 | FileImageSize = er.Read();
31 | MemorySize = er.Read();
32 | Flags = er.Read();
33 | Alignment = er.Read();
34 |
35 | if (FileImageSize != 0)
36 | {
37 | long curpos = er.BaseStream.Position;
38 | er.BaseStream.Position = FileImageOffset;
39 | SegmentData = er.Read((int)FileImageSize);
40 | er.BaseStream.Position = curpos;
41 | }
42 | }
43 |
44 | public ElfSegmentType SegmentType;
45 | public uint FileImageOffset;
46 | public uint VirtualAddress;
47 | public uint PhysicalAddress;
48 | public uint FileImageSize;
49 | public uint MemorySize;
50 | public uint Flags;
51 | public uint Alignment;
52 |
53 | public byte[] SegmentData = [];
54 | }
55 |
--------------------------------------------------------------------------------
/GCDTool/Elf/SectionHeaderTableEntry.cs:
--------------------------------------------------------------------------------
1 | using GCDTool.IO;
2 |
3 | namespace GCDTool.Elf;
4 |
5 | sealed class SectionHeaderTableEntry
6 | {
7 | public SectionHeaderTableEntry(EndianBinaryReader er)
8 | {
9 | NameOffset = er.Read();
10 | SectionType = er.Read();
11 | Flags = er.Read();
12 | VirtualAddress = er.Read();
13 | FileImageOffset = er.Read();
14 | FileImageSize = er.Read();
15 | Link = er.Read();
16 | Info = er.Read();
17 | Alignment = er.Read();
18 | EntrySize = er.Read();
19 |
20 | if (FileImageSize != 0)
21 | {
22 | long curpos = er.BaseStream.Position;
23 | er.BaseStream.Position = FileImageOffset;
24 | SectionData = er.Read((int)FileImageSize);
25 | er.BaseStream.Position = curpos;
26 | }
27 | }
28 |
29 | public uint NameOffset;
30 | public ElfSectionType SectionType;
31 | public uint Flags;
32 | public uint VirtualAddress;
33 | public uint FileImageOffset;
34 | public uint FileImageSize;
35 | public uint Link;
36 | public uint Info;
37 | public uint Alignment;
38 | public uint EntrySize;
39 |
40 | public byte[] SectionData = [];
41 | }
42 |
--------------------------------------------------------------------------------
/GCDTool/Elf/Sections/ElfSection.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool.Elf.Sections;
2 |
3 | class ElfSection
4 | {
5 | public SectionHeaderTableEntry SectionHeader { get; }
6 | public string Name { get; }
7 |
8 | public ElfSection(SectionHeaderTableEntry sectionHeader, string name)
9 | {
10 | SectionHeader = sectionHeader;
11 | Name = name;
12 | }
13 |
14 | public override string ToString()
15 | {
16 | return Name;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/GCDTool/Elf/Sections/ElfSectionFactory.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool.Elf.Sections;
2 |
3 | sealed class ElfSectionFactory
4 | {
5 | public ElfSection CreateElfSection(SectionHeaderTableEntry section, string name)
6 | {
7 | return section.SectionType switch
8 | {
9 | ElfSectionType.Strtab => new ElfStrtab(section, name),
10 | ElfSectionType.Symtab => new ElfSymtab(section, name),
11 | _ => new ElfSection(section, name),
12 | };
13 | }
14 | }
--------------------------------------------------------------------------------
/GCDTool/Elf/Sections/ElfStrtab.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool.Elf.Sections;
2 |
3 | sealed class ElfStrtab : ElfSection
4 | {
5 | public ElfStrtab(SectionHeaderTableEntry section, string name)
6 | : base(section, name) { }
7 |
8 | public string GetString(uint offset)
9 | {
10 | string result = string.Empty;
11 | while (offset < SectionHeader.SectionData.Length)
12 | {
13 | char c = (char)SectionHeader.SectionData[offset++];
14 | if (c == '\0')
15 | {
16 | return result;
17 | }
18 | result += c;
19 | }
20 | return result;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/GCDTool/Elf/Sections/ElfSymbol.cs:
--------------------------------------------------------------------------------
1 | using GCDTool.IO;
2 |
3 | namespace GCDTool.Elf.Sections;
4 |
5 | sealed class ElfSymbol
6 | {
7 | public ElfSymbol(EndianBinaryReader er)
8 | {
9 | NameOffset = er.Read();
10 | Value = er.Read();
11 | Size = er.Read();
12 | Info = er.Read();
13 | Other = er.Read();
14 | SectionIndex = er.Read();
15 | }
16 |
17 | public uint NameOffset;
18 | public uint Value;
19 | public uint Size;
20 | public byte Info;
21 | public byte Other;
22 | public ushort SectionIndex;
23 | }
--------------------------------------------------------------------------------
/GCDTool/Elf/Sections/ElfSymtab.cs:
--------------------------------------------------------------------------------
1 | using GCDTool.IO;
2 |
3 | namespace GCDTool.Elf.Sections;
4 |
5 | sealed class ElfSymtab : ElfSection
6 | {
7 | public IReadOnlyList Symbols { get; }
8 |
9 | public ElfSymtab(SectionHeaderTableEntry section, string name)
10 | : base(section, name)
11 | {
12 | uint nrEntries = section.FileImageSize / section.EntrySize;
13 | var symbols = new ElfSymbol[nrEntries];
14 | var er = new EndianBinaryReader(new MemoryStream(section.SectionData), Endianness.LittleEndian);
15 | for (int i = 0; i < nrEntries; i++)
16 | {
17 | symbols[i] = new ElfSymbol(er);
18 | }
19 | Symbols = symbols;
20 | er.Close();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/GCDTool/GCDTool.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | enable
7 | enable
8 | Gericom
9 | True
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/GCDTool/GcdBlowfish.cs:
--------------------------------------------------------------------------------
1 | using GCDTool.IO;
2 | using System.Buffers.Binary;
3 |
4 | namespace GCDTool;
5 |
6 | ///
7 | /// Static helper class for the GCD blowfish table.
8 | ///
9 | static class GcdBlowfish
10 | {
11 | ///
12 | /// Creates the GCD blowfish table corresponding to the given .
13 | ///
14 | /// The game code.
15 | /// The transformed blowfish table.
16 | public static byte[] GetTransformedKeyTable(uint gameCode)
17 | {
18 | var keyTable = KeyTable.ToArray();
19 | uint keyCode = BinaryPrimitives.ReverseEndianness(gameCode);
20 | for (int i = 0; i < Blowfish.P_TABLE_ENTRY_COUNT; i++)
21 | {
22 | IOUtil.WriteU32Le(keyTable, i * 4,
23 | IOUtil.ReadU32Le(keyTable, i * 4) ^ keyCode);
24 | }
25 |
26 | var scratch = new byte[8];
27 | for (int i = 0; i < Blowfish.KEY_TABLE_LENGTH; i += Blowfish.BLOCK_LENGTH)
28 | {
29 | var blowfish = new Blowfish(keyTable);
30 | blowfish.Encrypt(scratch);
31 | Array.Copy(scratch, 4, keyTable, i, 4);
32 | Array.Copy(scratch, 0, keyTable, i + 4, 4);
33 | }
34 |
35 | return keyTable;
36 | }
37 |
38 | ///
39 | /// The untransformed GCD blowfish table.
40 | ///
41 | public static ReadOnlySpan KeyTable =>
42 | [
43 | 0xEE, 0xA8, 0x95, 0x75, 0x2D, 0xF3, 0xFF, 0x84, 0xC6, 0xAE, 0xF8, 0x58,
44 | 0xC2, 0x44, 0x64, 0x6F, 0xBC, 0xCF, 0xA6, 0x10, 0x13, 0xB8, 0xE1, 0xBE,
45 | 0xC3, 0xAB, 0xEF, 0x88, 0xCD, 0x26, 0x20, 0xC7, 0x3A, 0x91, 0x0B, 0xC0,
46 | 0xC0, 0x74, 0xB0, 0x9F, 0x0F, 0x83, 0xD4, 0x56, 0xE5, 0xDE, 0xAB, 0x69,
47 | 0xF2, 0x5F, 0x6E, 0xCF, 0x2F, 0xBE, 0xFE, 0xD7, 0xE2, 0xD5, 0xF5, 0x84,
48 | 0xDA, 0xCC, 0xA1, 0x73, 0x99, 0x59, 0x20, 0x44, 0x63, 0x8F, 0x27, 0x74,
49 | 0x53, 0x72, 0x90, 0xF0, 0x8F, 0xD4, 0x95, 0x1C, 0x99, 0xCE, 0xDB, 0x7C,
50 | 0x8A, 0x50, 0xB9, 0xA8, 0x9E, 0x9F, 0x37, 0x79, 0xFE, 0x44, 0x91, 0x12,
51 | 0x4D, 0x55, 0xB3, 0xD2, 0xC6, 0x02, 0xD7, 0x72, 0x43, 0x81, 0x05, 0xCE,
52 | 0xB8, 0x11, 0xB4, 0x72, 0xE7, 0x2A, 0xCF, 0x9A, 0x95, 0xD1, 0xC0, 0x62,
53 | 0xE5, 0x61, 0x08, 0x7D, 0xF6, 0xC8, 0x3A, 0x33, 0x59, 0x7B, 0xC1, 0xAF,
54 | 0x12, 0xAB, 0xAB, 0x7F, 0xDF, 0xB8, 0x20, 0x5A, 0xE1, 0x08, 0xC9, 0x43,
55 | 0x54, 0x7B, 0x05, 0xDF, 0x17, 0x7D, 0x23, 0x65, 0x1C, 0x58, 0x88, 0x89,
56 | 0xD9, 0xCA, 0x02, 0x1D, 0x8A, 0x3C, 0xE6, 0xDD, 0x12, 0xEE, 0x2F, 0x30,
57 | 0x9D, 0xDD, 0xBE, 0x0A, 0x74, 0xB5, 0x6F, 0x58, 0xAD, 0x0A, 0x13, 0x10,
58 | 0x35, 0xAD, 0x1E, 0x0A, 0x70, 0x08, 0x6F, 0xFE, 0x02, 0x4F, 0xAC, 0x8C,
59 | 0x2C, 0xEF, 0x56, 0x9C, 0xCD, 0x9B, 0xAB, 0xC0, 0x52, 0x9A, 0xAF, 0x7F,
60 | 0xCD, 0x2D, 0x82, 0xEF, 0xC2, 0xFD, 0xC0, 0x2B, 0x57, 0xA5, 0x22, 0xDE,
61 | 0x67, 0x28, 0xF8, 0xCC, 0x19, 0x9D, 0x2A, 0x58, 0x15, 0x07, 0x79, 0xD2,
62 | 0x89, 0xF6, 0x8D, 0xDC, 0xE2, 0xA6, 0x0C, 0x25, 0x6D, 0x23, 0x35, 0xFE,
63 | 0xD6, 0xE2, 0x48, 0x86, 0xE7, 0xF6, 0x0B, 0xAA, 0x20, 0x06, 0x24, 0xD1,
64 | 0xA7, 0x5E, 0x3A, 0x41, 0x43, 0x91, 0xF5, 0x4C, 0x72, 0x54, 0x4F, 0x4F,
65 | 0x08, 0x9A, 0xF2, 0xA5, 0x8F, 0x4E, 0xFF, 0x1F, 0x2D, 0xEC, 0xF0, 0x14,
66 | 0x2A, 0xF4, 0xD6, 0x47, 0xF0, 0x21, 0xB0, 0x85, 0x0A, 0xF2, 0x36, 0x78,
67 | 0xD7, 0xD0, 0x08, 0xD9, 0xD5, 0x9D, 0xCC, 0xBD, 0xFB, 0x0B, 0xAC, 0xA6,
68 | 0xAF, 0x7D, 0xFB, 0x96, 0xFF, 0x76, 0x54, 0xB6, 0x51, 0x9B, 0xE9, 0xBD,
69 | 0x8E, 0x4B, 0xC8, 0xE8, 0x30, 0x86, 0xC8, 0x72, 0x79, 0x09, 0x8D, 0x3F,
70 | 0xDC, 0x45, 0x1E, 0x5C, 0xDB, 0xB1, 0x55, 0x75, 0x90, 0x5A, 0xDB, 0x3E,
71 | 0x66, 0xAC, 0x7F, 0xB0, 0xBA, 0x6A, 0x31, 0xF7, 0xBD, 0x88, 0xA0, 0x84,
72 | 0x9D, 0xFB, 0xB2, 0xF0, 0xE1, 0x48, 0x4B, 0x01, 0xE3, 0x67, 0x09, 0x6D,
73 | 0xE4, 0x60, 0x8D, 0xA3, 0xF2, 0xED, 0x8E, 0x14, 0x64, 0x88, 0x89, 0x81,
74 | 0xAA, 0x73, 0x0D, 0xFE, 0xD5, 0x7B, 0xC3, 0x58, 0x45, 0xC3, 0xE2, 0x8C,
75 | 0xE7, 0x1D, 0x95, 0x82, 0xA6, 0x1A, 0xBE, 0x17, 0x1B, 0xAD, 0xE8, 0xBF,
76 | 0x76, 0x41, 0x6B, 0x4D, 0x3D, 0xA4, 0x0D, 0x3A, 0xAC, 0xDC, 0xA4, 0x50,
77 | 0x2E, 0x28, 0xF1, 0x69, 0x76, 0x95, 0xB4, 0x16, 0xFE, 0x1F, 0x52, 0x6B,
78 | 0x74, 0x81, 0x58, 0x67, 0x28, 0x1D, 0xDE, 0xB3, 0xBD, 0x8C, 0x19, 0x06,
79 | 0xEA, 0x40, 0xA2, 0xEE, 0x8E, 0x35, 0x3E, 0xD1, 0x6C, 0xB0, 0x64, 0x36,
80 | 0x27, 0xE2, 0xF4, 0x59, 0xD3, 0x22, 0x41, 0xB5, 0xE3, 0x71, 0xBB, 0x94,
81 | 0x94, 0xFB, 0x15, 0x03, 0xD6, 0x01, 0x73, 0x64, 0x0D, 0x1F, 0x3F, 0x94,
82 | 0xC4, 0xAE, 0x2A, 0x7A, 0xF7, 0x88, 0xBF, 0x51, 0x1A, 0x09, 0x2C, 0x71,
83 | 0x3E, 0x6E, 0x3B, 0x6D, 0x52, 0x4D, 0xB2, 0x6E, 0xA4, 0x2C, 0xC8, 0x9F,
84 | 0x8E, 0x18, 0xFC, 0x8F, 0x0A, 0x14, 0x31, 0xBE, 0x56, 0x57, 0x3E, 0x1D,
85 | 0x6E, 0xE1, 0x74, 0xC5, 0x93, 0xA7, 0x05, 0xB1, 0xEC, 0x58, 0xF6, 0x94,
86 | 0x4C, 0x86, 0xD4, 0x6E, 0xCF, 0xE3, 0xBA, 0xB2, 0x15, 0xCF, 0x42, 0xE3,
87 | 0x27, 0x4D, 0xBC, 0xF2, 0xB1, 0x34, 0x03, 0x09, 0x42, 0x2A, 0x63, 0xC2,
88 | 0x92, 0x00, 0x4F, 0x88, 0x51, 0xBD, 0x9A, 0xA5, 0xF9, 0x03, 0xD2, 0xE9,
89 | 0x57, 0x22, 0x12, 0x7D, 0xAA, 0x09, 0xA7, 0x02, 0x9B, 0xC3, 0x9F, 0xAC,
90 | 0xED, 0xBE, 0x40, 0x48, 0xA4, 0x37, 0xBE, 0x74, 0xD3, 0xA4, 0xB9, 0x24,
91 | 0xA7, 0xBC, 0x78, 0x81, 0xE5, 0x85, 0x6B, 0x60, 0xB2, 0x46, 0xCB, 0x1D,
92 | 0x60, 0x20, 0x11, 0xE6, 0x8A, 0x00, 0x44, 0xBD, 0x64, 0x29, 0xBE, 0xD1,
93 | 0x32, 0x50, 0xCC, 0xCF, 0x43, 0x0E, 0xE1, 0x74, 0xAB, 0xC5, 0xFA, 0x53,
94 | 0xA4, 0xE0, 0xA6, 0x84, 0x7A, 0x99, 0x80, 0x90, 0x00, 0x01, 0x1A, 0xA3,
95 | 0x1C, 0x1C, 0xE2, 0xCE, 0x73, 0xE9, 0xCE, 0xEB, 0xC7, 0xA4, 0xE9, 0x77,
96 | 0x9B, 0x1B, 0x26, 0x3A, 0x16, 0xCA, 0x49, 0xCE, 0x2D, 0x43, 0x3A, 0xEC,
97 | 0xBF, 0xC1, 0x10, 0x14, 0x24, 0xC8, 0x22, 0x8D, 0x66, 0x82, 0x01, 0xB0,
98 | 0xE2, 0xE2, 0x64, 0x79, 0x14, 0xEB, 0xC6, 0xFC, 0x35, 0xE0, 0xE6, 0x12,
99 | 0x0A, 0x9D, 0x68, 0xCA, 0xFC, 0x39, 0x9F, 0x5F, 0xE9, 0xF7, 0x14, 0xBF,
100 | 0xF2, 0x13, 0x78, 0x5D, 0x01, 0x74, 0x34, 0x87, 0x60, 0x76, 0x10, 0x1A,
101 | 0xB7, 0x1B, 0xAA, 0xFE, 0xA2, 0xB1, 0xEB, 0x53, 0x57, 0xA4, 0x47, 0xFC,
102 | 0x17, 0xDE, 0x1C, 0xF7, 0x36, 0x9A, 0x37, 0x25, 0x5D, 0x20, 0xED, 0xB2,
103 | 0x36, 0xBF, 0x25, 0x12, 0xD4, 0x6B, 0x53, 0x14, 0xDF, 0x8C, 0x13, 0x31,
104 | 0xF4, 0xB7, 0xEC, 0xEE, 0x19, 0x5B, 0x6E, 0xBB, 0x0F, 0x7D, 0x87, 0x39,
105 | 0x24, 0xAC, 0x66, 0x43, 0x3C, 0x89, 0x13, 0xFB, 0xB2, 0x39, 0x70, 0x3A,
106 | 0x37, 0x80, 0x1B, 0x3A, 0x34, 0x86, 0x66, 0xFC, 0x83, 0xB7, 0x3F, 0x0A,
107 | 0x26, 0x05, 0x38, 0x50, 0x1A, 0x4B, 0x0E, 0x9C, 0x98, 0x93, 0x2B, 0x46,
108 | 0x2D, 0x7D, 0x6C, 0x0E, 0x9E, 0x92, 0x9A, 0x68, 0xE6, 0x01, 0x57, 0x71,
109 | 0x7D, 0xD1, 0x61, 0x2E, 0x45, 0x91, 0xDA, 0x09, 0xA1, 0x3B, 0x14, 0xFB,
110 | 0x59, 0x92, 0x19, 0x5B, 0xEE, 0xA5, 0xE3, 0xC4, 0x9A, 0xDA, 0x4B, 0x0B,
111 | 0x6C, 0x52, 0x8C, 0xE1, 0x1A, 0x1C, 0x17, 0x73, 0x4B, 0xA9, 0x72, 0x15,
112 | 0x29, 0xB7, 0x15, 0x6D, 0x91, 0x9E, 0x50, 0x71, 0x6E, 0xC7, 0x1B, 0x57,
113 | 0x18, 0x10, 0x72, 0x8C, 0x42, 0x17, 0xCC, 0x1F, 0x75, 0x2C, 0x60, 0xA6,
114 | 0xA9, 0x88, 0x7F, 0x78, 0xC4, 0xF9, 0x98, 0x05, 0x1B, 0xC6, 0xDD, 0x5A,
115 | 0x41, 0x7B, 0x9B, 0x8E, 0x72, 0xAF, 0x4D, 0x2F, 0x2A, 0x9E, 0xF0, 0xD9,
116 | 0xCA, 0x54, 0x60, 0xE7, 0x4E, 0x0B, 0xBC, 0x28, 0x32, 0x0C, 0x32, 0x4A,
117 | 0x7E, 0x68, 0x2B, 0xDF, 0xCC, 0xFB, 0xDF, 0x2C, 0x69, 0x6B, 0xF1, 0x77,
118 | 0x65, 0x56, 0x42, 0x5D, 0xD9, 0x7F, 0x20, 0xCF, 0x6D, 0x16, 0x4D, 0x2E,
119 | 0x57, 0x79, 0xE5, 0x46, 0x39, 0x0F, 0xF5, 0x37, 0x4A, 0xB7, 0x42, 0x67,
120 | 0x47, 0xDF, 0x21, 0xBB, 0x64, 0x09, 0xDF, 0xE2, 0x89, 0xD9, 0xD6, 0x0D,
121 | 0x6E, 0x5A, 0x63, 0x6F, 0x57, 0xA4, 0x05, 0x8F, 0x10, 0xC0, 0x5D, 0x6E,
122 | 0x43, 0x6F, 0xE7, 0xEA, 0x6A, 0x20, 0x5C, 0x13, 0x38, 0xD9, 0x6B, 0x87,
123 | 0x07, 0xC6, 0xCB, 0x1C, 0x28, 0x0E, 0x60, 0xAF, 0xE0, 0x4F, 0xAA, 0x0D,
124 | 0x33, 0xFE, 0xE0, 0x95, 0xB1, 0x44, 0x3E, 0x09, 0xAD, 0xB2, 0x16, 0x1D,
125 | 0xA2, 0x0B, 0x71, 0x8D, 0xD9, 0xC1, 0xA3, 0x3C, 0xE1, 0xA8, 0x54, 0x8F,
126 | 0xC8, 0x1E, 0xB1, 0x7E, 0x0E, 0x2A, 0xB8, 0xCD, 0x6D, 0xFF, 0x1A, 0xDE,
127 | 0xE2, 0x2C, 0xAE, 0x68, 0x3E, 0xCA, 0xE1, 0x80, 0x2C, 0x0C, 0xD6, 0x67,
128 | 0xDA, 0xD1, 0x6A, 0xAC, 0x02, 0xC8, 0x30, 0x53, 0x4D, 0xA0, 0x67, 0x5F,
129 | 0x9D, 0x3C, 0x6F, 0x5D, 0xB0, 0x25, 0x47, 0x1F, 0x69, 0x4C, 0x4A, 0x09,
130 | 0x21, 0xEA, 0x1D, 0x4C, 0xD5, 0xCE, 0x09, 0xFD, 0x41, 0xC3, 0x0F, 0x27,
131 | 0xF5, 0x81, 0x60, 0xF1, 0xB4, 0xBD, 0xDC, 0x54, 0xF5, 0x6B, 0xA3, 0x72,
132 | 0x6A, 0xC1, 0xAB, 0xBE, 0x02, 0xBB, 0x82, 0xDD, 0x20, 0x5D, 0xE7, 0xF0,
133 | 0xB0, 0x64, 0x6A, 0xEA, 0x2C, 0x5C, 0xCB, 0x85, 0xAA, 0x5A, 0x3A, 0xD5,
134 | 0xD1, 0x91, 0x0F, 0x40, 0x5E, 0x69, 0x20, 0xC0, 0x76, 0x5C, 0xD0, 0xA3,
135 | 0x46, 0x04, 0xC0, 0x8A, 0xCE, 0xE1, 0xEB, 0xE0, 0x13, 0xCA, 0xD2, 0x7E,
136 | 0xCA, 0xBB, 0x41, 0x7F, 0xC0, 0xFF, 0xDA, 0x09, 0x09, 0x9C, 0xD3, 0x16,
137 | 0x9E, 0xFE, 0x17, 0xD4, 0x36, 0x4B, 0xFC, 0x11, 0x11, 0x21, 0xB9, 0x87,
138 | 0x89, 0xEA, 0x1C, 0x58, 0x6D, 0x51, 0x51, 0x6A, 0x94, 0xF9, 0x91, 0xA6,
139 | 0xAB, 0xC6, 0xE5, 0x06, 0x80, 0x07, 0x20, 0x81, 0xB7, 0x60, 0xEA, 0xE6,
140 | 0xAF, 0x67, 0x09, 0x8D, 0x79, 0x1D, 0xF8, 0x32, 0x78, 0x3B, 0xF6, 0x1B,
141 | 0xBB, 0x70, 0xFE, 0xEE, 0x42, 0x3B, 0x12, 0x74, 0x16, 0x5B, 0x34, 0xF7,
142 | 0x88, 0x13, 0xAA, 0x7A, 0x94, 0xA3, 0x52, 0x01, 0xA1, 0x3E, 0x46, 0xB5,
143 | 0x33, 0x86, 0x96, 0xE7, 0x17, 0x0F, 0xB0, 0xF6, 0x26, 0x59, 0xE6, 0xFE,
144 | 0x88, 0x5F, 0xC9, 0xFA, 0x38, 0x88, 0x78, 0xF7, 0xC8, 0x27, 0x4D, 0x40,
145 | 0x5A, 0x72, 0x40, 0x0A, 0x18, 0x86, 0x94, 0xAC, 0x6F, 0x96, 0xFB, 0xB2,
146 | 0xC3, 0x40, 0x0B, 0x73, 0xA0, 0xB3, 0xA5, 0x3F, 0x57, 0x7B, 0x4A, 0x2A,
147 | 0x94, 0x09, 0xEE, 0xE2, 0x03, 0x2B, 0x66, 0xE2, 0x53, 0xC2, 0x17, 0xA5,
148 | 0xC2, 0x9A, 0xCD, 0x5F, 0x1A, 0x86, 0xF1, 0x7C, 0x92, 0x6D, 0x9C, 0xFB,
149 | 0xF6, 0xFC, 0x6C, 0x72, 0x55, 0x08, 0xC2, 0x6B, 0x57, 0x77, 0x3C, 0xE0,
150 | 0xFC, 0xAE, 0xF7, 0x15, 0xB2, 0xF4, 0x90, 0x04, 0x9E, 0x9D, 0xC4, 0xC5,
151 | 0x03, 0xC4, 0x1B, 0xD9, 0x16, 0x90, 0x79, 0x84, 0x2F, 0x5D, 0x8B, 0x5E,
152 | 0x68, 0xEF, 0x8A, 0xDD, 0x7A, 0xB8, 0x25, 0x42, 0x70, 0x27, 0x91, 0xD3,
153 | 0xF2, 0x4D, 0xF8, 0x56, 0x69, 0xD4, 0x91, 0xE1, 0x14, 0x65, 0x30, 0xB6,
154 | 0x61, 0x74, 0x6B, 0xF6, 0x42, 0x8B, 0x52, 0xAA, 0x11, 0xD2, 0xD8, 0xD7,
155 | 0x09, 0xE8, 0x09, 0x04, 0x89, 0x54, 0x99, 0x3E, 0xD0, 0x2C, 0x4E, 0x98,
156 | 0x26, 0x87, 0xDA, 0x21, 0xCE, 0xDC, 0x01, 0xA9, 0x27, 0xD6, 0x9E, 0xC5,
157 | 0x95, 0xA3, 0x08, 0xA5, 0x10, 0x1C, 0x7E, 0xE6, 0x94, 0xC5, 0x86, 0x77,
158 | 0x12, 0x9C, 0xB4, 0x7D, 0xFA, 0x48, 0xA0, 0xF3, 0x39, 0xC9, 0x03, 0x6C,
159 | 0xB2, 0x38, 0xE5, 0x5F, 0xF1, 0x94, 0x8D, 0xE6, 0x18, 0x68, 0x11, 0x74,
160 | 0x38, 0x1B, 0x04, 0xBC, 0x9F, 0xF5, 0xE9, 0x37, 0xB4, 0xD6, 0xF0, 0xD5,
161 | 0x09, 0xDC, 0xBE, 0x3A, 0xCF, 0x7F, 0x27, 0xF2, 0xCA, 0x90, 0xC0, 0xB1,
162 | 0xAF, 0xBE, 0x9F, 0x17, 0x69, 0xF2, 0x99, 0x67, 0xBA, 0xE1, 0xF2, 0x4E,
163 | 0xFE, 0x30, 0x85, 0x9A, 0xD3, 0xBA, 0x29, 0xE6, 0x06, 0x18, 0x2D, 0xEF,
164 | 0x1A, 0xD4, 0xE0, 0xEF, 0x38, 0x80, 0xBD, 0x8F, 0xB8, 0xB3, 0x93, 0xAE,
165 | 0x9A, 0xF6, 0x6A, 0xB9, 0x0A, 0xF3, 0x76, 0xE9, 0xF2, 0xED, 0x35, 0x37,
166 | 0x80, 0xAB, 0x0A, 0xA1, 0x46, 0xC3, 0x2B, 0x57, 0xE5, 0x52, 0xD3, 0xD9,
167 | 0xFB, 0x82, 0x83, 0xA8, 0x6A, 0x9A, 0x6D, 0xC3, 0xF5, 0x69, 0x21, 0x62,
168 | 0x47, 0x81, 0x66, 0x2B, 0x94, 0x8E, 0x8D, 0xC6, 0xDF, 0x25, 0x37, 0xD8,
169 | 0xC0, 0x8D, 0xCA, 0x75, 0x7D, 0xF0, 0xFE, 0xE0, 0x8F, 0x34, 0x20, 0x30,
170 | 0xC5, 0x41, 0xE9, 0xD7, 0x00, 0xDA, 0x18, 0x0F, 0xCA, 0xCA, 0x09, 0x99,
171 | 0x03, 0x4C, 0x5F, 0x1B, 0x6E, 0xA6, 0x1B, 0x61, 0x8D, 0x5C, 0xC2, 0xE3,
172 | 0x54, 0x16, 0xFE, 0x87, 0x9E, 0x09, 0xED, 0x20, 0x51, 0x62, 0xEF, 0x2E,
173 | 0x47, 0x27, 0xB6, 0xF8, 0x16, 0x37, 0x86, 0xA8, 0x0A, 0x4E, 0x65, 0x2E,
174 | 0x15, 0x55, 0x98, 0x17, 0xF0, 0xA7, 0x21, 0xD1, 0x5B, 0x5A, 0x54, 0xF0,
175 | 0xA3, 0x9D, 0x9F, 0xED, 0x10, 0x7B, 0x60, 0xFE, 0x9B, 0x59, 0xFC, 0x3A,
176 | 0x05, 0x33, 0xC1, 0xA3, 0x0A, 0xA2, 0x67, 0x96, 0x0C, 0x9F, 0x63, 0xBD,
177 | 0xA0, 0xE3, 0x47, 0xE6, 0xB2, 0x04, 0x13, 0x0E, 0xEC, 0x1C, 0xA3, 0x26,
178 | 0x73, 0x52, 0x43, 0xD1, 0x41, 0x53, 0x84, 0x33, 0xF5, 0x83, 0x33, 0xBE,
179 | 0x7F, 0xE0, 0xE5, 0x22, 0xD2, 0xAE, 0x16, 0xB5, 0xC1, 0xD3, 0xDA, 0xE7,
180 | 0xEC, 0x2E, 0x0B, 0x7E, 0x35, 0xD2, 0x09, 0x77, 0x97, 0x33, 0x92, 0x48,
181 | 0xA6, 0x62, 0x6B, 0x08, 0x43, 0x08, 0x27, 0x05, 0x32, 0x3D, 0x07, 0x48,
182 | 0x8E, 0xDB, 0x22, 0x3E, 0xEB, 0xD2, 0x0F, 0x7B, 0x1D, 0x53, 0x99, 0x91,
183 | 0xFC, 0x9E, 0x7E, 0xA4, 0x31, 0xBF, 0xB1, 0x76, 0x31, 0x6F, 0x7B, 0xEE,
184 | 0xC0, 0xE2, 0x6D, 0xDC, 0x2C, 0x4D, 0x0D, 0x12, 0x8B, 0x1A, 0x7D, 0x1C,
185 | 0x42, 0x21, 0x19, 0xD9, 0x66, 0xCE, 0x51, 0x52, 0xCF, 0x0A, 0xDD, 0x26,
186 | 0x04, 0x3F, 0x07, 0x18, 0xA3, 0xF0, 0x01, 0x59, 0xA0, 0xFE, 0x24, 0xFC,
187 | 0x4B, 0x47, 0xD4, 0xE9, 0xDD, 0x7B, 0xDA, 0xBB, 0xF6, 0x45, 0xCB, 0x66,
188 | 0x90, 0x7A, 0x13, 0x66, 0x36, 0xA0, 0x10, 0xA0, 0x6C, 0xF9, 0xFF, 0xA3,
189 | 0x4F, 0x19, 0xC8, 0x61, 0x2B, 0x0B, 0x3E, 0x55, 0xE2, 0x4D, 0xB5, 0x72,
190 | 0xE9, 0xEF, 0xAE, 0x59, 0xB4, 0xDA, 0x01, 0xDA, 0x91, 0x8B, 0xC4, 0xDA,
191 | 0x7A, 0x6E, 0xA6, 0xBB, 0xE2, 0x9D, 0x81, 0xA3, 0xCE, 0x0F, 0x95, 0x1F,
192 | 0x97, 0xD6, 0xC4, 0xA3, 0xCE, 0x8D, 0xE2, 0xC4, 0xEE, 0x79, 0x01, 0x53,
193 | 0x68, 0xC0, 0x7C, 0xAA, 0x6A, 0x14, 0x44, 0x1B, 0x09, 0xD2, 0x67, 0xB2,
194 | 0xBE, 0xF1, 0x4F, 0x98, 0x6B, 0x79, 0xFD, 0x90, 0x5F, 0x2A, 0xE4, 0xE0,
195 | 0xFB, 0x33, 0xD0, 0x36, 0x82, 0xF4, 0xC2, 0x13, 0x6D, 0xA8, 0x19, 0x3B,
196 | 0x56, 0xD2, 0x0A, 0xFF, 0x47, 0xCB, 0x6A, 0xF0, 0xA0, 0xBF, 0x4F, 0x0D,
197 | 0xD3, 0x6F, 0x24, 0x53, 0xD5, 0x6D, 0x29, 0xF9, 0x4A, 0x5D, 0xBB, 0x42,
198 | 0x75, 0x6E, 0xD8, 0xD4, 0x50, 0x26, 0x44, 0x96, 0x51, 0xBC, 0xDE, 0x0B,
199 | 0x8D, 0xE9, 0xCE, 0xB3, 0xE2, 0x83, 0x8C, 0x18, 0x50, 0x28, 0x63, 0xBE,
200 | 0x59, 0xE6, 0x0C, 0x9A, 0xCD, 0x50, 0x12, 0xC5, 0x77, 0x9F, 0x1B, 0x14,
201 | 0xD4, 0x44, 0x28, 0x29, 0xD4, 0x50, 0xCE, 0xA8, 0xA3, 0x71, 0xF4, 0xE6,
202 | 0xF9, 0x7E, 0x1B, 0xE5, 0x79, 0x3B, 0xE3, 0xEC, 0x6A, 0x7A, 0xC1, 0x1E,
203 | 0x5E, 0x12, 0xA0, 0x01, 0x04, 0x1E, 0x07, 0x09, 0xF4, 0x2D, 0xE7, 0xBF,
204 | 0xCF, 0xD1, 0x0E, 0x17, 0xAC, 0x59, 0xA2, 0x2F, 0x4F, 0x70, 0x21, 0x48,
205 | 0x04, 0x27, 0xB8, 0x95, 0x6A, 0x0C, 0x1E, 0xCD, 0xCE, 0x33, 0x7A, 0xB5,
206 | 0xFE, 0x04, 0x72, 0x05, 0x6B, 0xDE, 0x17, 0x76, 0xD5, 0xF1, 0x72, 0xAA,
207 | 0x1A, 0xD6, 0x46, 0x4B, 0x78, 0x73, 0x79, 0xAC, 0xCF, 0xD9, 0x84, 0x49,
208 | 0x22, 0x60, 0x72, 0x40, 0x5C, 0x5B, 0x42, 0x2B, 0xAB, 0x5D, 0xA2, 0x4C,
209 | 0xA3, 0x3A, 0xE2, 0xA0, 0x7A, 0x52, 0xE1, 0x4F, 0xAC, 0xB7, 0xD3, 0x6F,
210 | 0x60, 0xBA, 0x47, 0xEB, 0xB7, 0xC8, 0xE7, 0xCD, 0x0F, 0x5D, 0xEF, 0x9A,
211 | 0xCD, 0x74, 0x16, 0x82, 0x7D, 0xEA, 0x58, 0xC2, 0xA5, 0x13, 0xA8, 0xA6,
212 | 0x84, 0x8A, 0xE8, 0x93, 0xE3, 0xD2, 0x32, 0x8F, 0x0D, 0x44, 0xAE, 0x58,
213 | 0x15, 0x97, 0x79, 0xC5, 0xD0, 0x84, 0x52, 0x42, 0x64, 0x6E, 0x69, 0x1B,
214 | 0x3A, 0xE9, 0x3C, 0x7D, 0x5C, 0xF2, 0xD0, 0xDA, 0x14, 0xDD, 0xB0, 0xC4,
215 | 0xE5, 0xE2, 0x79, 0x70, 0x1D, 0xE6, 0xE8, 0xA9, 0xB7, 0x86, 0xF6, 0xFA,
216 | 0x7B, 0xB8, 0xF1, 0x1C, 0xC7, 0x43, 0xAB, 0x31, 0xBB, 0xD1, 0x45, 0xDC,
217 | 0xEC, 0xB6, 0x6C, 0x38, 0xF2, 0x83, 0xD2, 0xCA, 0xAF, 0xBF, 0xCC, 0x4D,
218 | 0x8B, 0xF2, 0x34, 0x49, 0xD4, 0x3D, 0xBC, 0x2F, 0xF7, 0x64, 0x9B, 0x78,
219 | 0x8F, 0x91, 0xE8, 0xB2, 0xF7, 0xCB, 0x6A, 0x50, 0x60, 0x33, 0xA1, 0x50,
220 | 0xD3, 0xD4, 0x28, 0xD1, 0x26, 0x75, 0x68, 0xBA, 0x40, 0x49, 0x47, 0xC5,
221 | 0xC7, 0xDF, 0xB5, 0x6B, 0x1F, 0xDF, 0x9B, 0xEF, 0x3B, 0x64, 0xCC, 0x92,
222 | 0xF0, 0xD4, 0x2A, 0xFB, 0x0B, 0x41, 0xF9, 0x4F, 0x1E, 0xC8, 0x13, 0xB0,
223 | 0x74, 0x95, 0xE6, 0x9F, 0x88, 0x0D, 0xED, 0x5F, 0x20, 0x77, 0xE8, 0xCF,
224 | 0x06, 0xDF, 0xCD, 0x09, 0x6F, 0x69, 0x57, 0x9E, 0xB9, 0xDF, 0x29, 0xC4,
225 | 0xD2, 0x5D, 0x0C, 0x46, 0x5B, 0x50, 0x9E, 0x9B, 0xF6, 0xBC, 0x12, 0xC0,
226 | 0xDA, 0xBD, 0x55, 0xD9, 0x31, 0xED, 0x10, 0x73, 0xE2, 0xEB, 0xAA, 0xCD,
227 | 0xF6, 0x1F, 0x76, 0xC9, 0x45, 0x0F, 0xE0, 0x5D, 0x49, 0xE9, 0xA5, 0x66,
228 | 0x1D, 0xDF, 0xC5, 0x45, 0xAB, 0x8F, 0xA3, 0x96, 0x5B, 0x9D, 0x93, 0x18,
229 | 0x83, 0x50, 0x01, 0x94, 0x79, 0x13, 0x7B, 0xD2, 0xE7, 0x07, 0x01, 0xE1,
230 | 0xF8, 0x6E, 0xEF, 0xDA, 0xB3, 0x83, 0x8F, 0xAF, 0x29, 0x70, 0xD3, 0xAF,
231 | 0xE0, 0x03, 0x68, 0x7D, 0x55, 0x50, 0x54, 0x3A, 0x25, 0x99, 0x41, 0xFD,
232 | 0x5F, 0xA2, 0x9F, 0xB4, 0xF4, 0xD3, 0x3A, 0x53, 0xE4, 0x3F, 0x9F, 0xC4,
233 | 0x96, 0xB1, 0x9E, 0x9B, 0x8E, 0x40, 0x15, 0x20, 0x85, 0x64, 0xF4, 0xD6,
234 | 0x79, 0x64, 0xE6, 0xA8, 0xE1, 0x66, 0x88, 0x5B, 0x8D, 0x47, 0x7D, 0xC9,
235 | 0x0A, 0xB3, 0xFC, 0xB7, 0xF0, 0xDF, 0xEF, 0xDD, 0x4C, 0xB9, 0xAF, 0x34,
236 | 0x2F, 0xD9, 0x55, 0xA1, 0xB1, 0x79, 0x39, 0xF5, 0x75, 0xD0, 0xCD, 0xF0,
237 | 0xAE, 0xCC, 0x5A, 0xF2, 0xFD, 0x8A, 0x20, 0xE4, 0x77, 0xBB, 0x11, 0x5B,
238 | 0xE4, 0x37, 0xB3, 0x37, 0xBE, 0xA8, 0x9D, 0xA6, 0x86, 0xEE, 0xA9, 0x28,
239 | 0x0E, 0xB3, 0x84, 0xB2, 0x98, 0x75, 0x52, 0xC3, 0x6E, 0x93, 0x23, 0xF4,
240 | 0x51, 0x1D, 0x2E, 0x7E, 0x3D, 0x61, 0xF2, 0xCA, 0x1E, 0x39, 0x05, 0x75,
241 | 0xD5, 0x3D, 0x2D, 0x25, 0x03, 0x82, 0xD1, 0x00, 0x7E, 0x57, 0xF4, 0x7E,
242 | 0xA9, 0x98, 0x4E, 0x24, 0x40, 0x58, 0x3B, 0xB7, 0xED, 0xC9, 0xF9, 0xB0,
243 | 0x23, 0xBC, 0xD4, 0xA8, 0x43, 0x82, 0x22, 0xB7, 0xD3, 0x18, 0x14, 0x24,
244 | 0x29, 0x44, 0xFD, 0x39, 0x37, 0x9B, 0xAB, 0xE8, 0xDF, 0xEC, 0x74, 0x3A,
245 | 0x9C, 0x49, 0x99, 0xBF, 0x26, 0xEB, 0xC3, 0x70, 0x10, 0x5D, 0xAC, 0xC6,
246 | 0x0A, 0x41, 0x85, 0x70, 0x24, 0xCA, 0xC5, 0x9B, 0x32, 0x4D, 0xCF, 0x37,
247 | 0xF1, 0x7A, 0x40, 0x5A, 0xAF, 0xC2, 0x38, 0x7F, 0x26, 0x57, 0xF9, 0x6C,
248 | 0xE9, 0xCC, 0x34, 0x29, 0x5E, 0x4A, 0x8E, 0x47, 0x71, 0xCD, 0xBC, 0x86,
249 | 0x04, 0x38, 0x67, 0x46, 0xDA, 0xE9, 0xD7, 0x5C, 0x47, 0x0E, 0x1C, 0x60,
250 | 0x5C, 0xC9, 0xF3, 0x32, 0x07, 0xB2, 0xF3, 0x98, 0xF2, 0x09, 0x9F, 0x2A,
251 | 0x9B, 0x3B, 0x8E, 0xA6, 0x44, 0x7F, 0xA6, 0xE0, 0xEB, 0x01, 0x48, 0x8D,
252 | 0x00, 0x09, 0x65, 0x0D, 0x8F, 0x9D, 0xBD, 0x2D, 0x47, 0xFE, 0x13, 0x91,
253 | 0x0E, 0x7B, 0x31, 0x5A, 0x71, 0x8D, 0x5A, 0x3D, 0x45, 0x0E, 0xD6, 0xFB,
254 | 0xDC, 0xEE, 0xF6, 0x86, 0xC6, 0xFE, 0x77, 0x3F, 0xB2, 0x0B, 0x6A, 0xE5,
255 | 0xF7, 0x5E, 0xAB, 0x32, 0x29, 0xC4, 0x62, 0x22, 0xC6, 0x87, 0xD3, 0x7E,
256 | 0x7E, 0xC8, 0x87, 0x00, 0xB7, 0xE4, 0x70, 0x65, 0xE1, 0x80, 0x8D, 0x64,
257 | 0x1A, 0x76, 0xBD, 0xC3, 0x47, 0x0C, 0x3C, 0x5C, 0x7C, 0xB0, 0xC5, 0x9A,
258 | 0x8F, 0x15, 0xCB, 0x6E, 0x23, 0xD3, 0xE2, 0x73, 0x5E, 0x43, 0x95, 0x50,
259 | 0xE4, 0x7E, 0xA9, 0x21, 0x19, 0x95, 0x59, 0x0C, 0x41, 0x64, 0x6C, 0x1F,
260 | 0xD7, 0xEF, 0x52, 0xEE, 0xE2, 0x6E, 0x88, 0xBD, 0x66, 0x3C, 0xEC, 0x66,
261 | 0x0C, 0x82, 0x42, 0x0E, 0xCB, 0x44, 0xF2, 0xB4, 0xFC, 0x2E, 0x6F, 0xA5,
262 | 0x16, 0x2F, 0x30, 0xC0, 0xAA, 0x95, 0xB7, 0xF9, 0x3F, 0x0F, 0xA0, 0x60,
263 | 0xA9, 0xB0, 0x3F, 0xA8, 0x24, 0xF7, 0xB2, 0xE9, 0x4F, 0xB4, 0xC3, 0xA3,
264 | 0x80, 0xCC, 0x51, 0x81, 0x9C, 0xE9, 0xE0, 0x2A, 0x00, 0xFF, 0xB0, 0x0C,
265 | 0x6D, 0x64, 0x9A, 0x2E, 0xD6, 0x2C, 0x1D, 0x99, 0x6B, 0xF1, 0x2B, 0xEF,
266 | 0xD2, 0x0A, 0x61, 0x0A, 0x07, 0xEA, 0x74, 0x16, 0xE7, 0xCF, 0x7F, 0x56,
267 | 0xAF, 0xF5, 0x5E, 0xF0, 0xCB, 0x47, 0xDF, 0xDF, 0x59, 0xBB, 0x3E, 0x6C,
268 | 0xAD, 0x2C, 0x05, 0x2F, 0x04, 0xCA, 0x01, 0x47, 0x4A, 0x16, 0x65, 0x0C,
269 | 0xB3, 0xEC, 0x85, 0xA2, 0x0A, 0xD2, 0x8F, 0x34, 0xFF, 0xF8, 0x15, 0x79,
270 | 0x33, 0x9D, 0x26, 0xDC, 0x72, 0x8F, 0x57, 0x74, 0x80, 0xED, 0x3C, 0x76,
271 | 0x51, 0x59, 0x27, 0x53, 0xBD, 0xEE, 0x51, 0x8C, 0x52, 0x44, 0x8B, 0x6D,
272 | 0x4E, 0xC3, 0x67, 0x52, 0x7B, 0x19, 0xB7, 0xE9, 0xAB, 0x00, 0x28, 0x91,
273 | 0xA4, 0x07, 0xD2, 0x10, 0xF7, 0xEA, 0x38, 0x42, 0x12, 0xBD, 0x71, 0x21,
274 | 0x0B, 0xFF, 0x7E, 0x9C, 0xFF, 0x79, 0x1B, 0x25, 0x53, 0x77, 0xBB, 0x41,
275 | 0x83, 0x65, 0xEA, 0xEC, 0xE5, 0x93, 0x13, 0x03, 0xE9, 0x8F, 0xA5, 0x49,
276 | 0x87, 0x75, 0xBB, 0x83, 0x56, 0x31, 0x2A, 0x4A, 0xA7, 0x8D, 0xAB, 0xA3,
277 | 0xAA, 0x74, 0xF7, 0x38, 0x4E, 0x4D, 0x84, 0xD3, 0x18, 0x14, 0xC8, 0x7C,
278 | 0x9D, 0xA0, 0x4C, 0x89, 0x56, 0xDE, 0x1A, 0x34, 0xA5, 0xD6, 0x74, 0x95,
279 | 0x51, 0x97, 0x29, 0xD2, 0x93, 0x32, 0xD4, 0xDB, 0xE8, 0x53, 0x3D, 0x25,
280 | 0xC4, 0x5C, 0x00, 0x64, 0x6F, 0xE5, 0x3D, 0x81, 0xDA, 0x3C, 0x1C, 0x63,
281 | 0xBD, 0x81, 0x16, 0x93, 0xB8, 0x0A, 0xDD, 0x77, 0x6F, 0xB5, 0xD6, 0x15,
282 | 0xB1, 0x06, 0x31, 0x2A, 0xBF, 0x02, 0x31, 0x60, 0x3D, 0x9A, 0xF2, 0xFE,
283 | 0x38, 0xAA, 0x8B, 0x64, 0x91, 0xE7, 0x0A, 0x47, 0xAB, 0x25, 0xE6, 0x02,
284 | 0x5D, 0x4D, 0x90, 0x04, 0xA4, 0xCE, 0x31, 0x3B, 0x6D, 0x12, 0xA2, 0xA9,
285 | 0x75, 0x45, 0x81, 0x76, 0x11, 0x55, 0x6A, 0x4C, 0x19, 0xC6, 0xD7, 0xE2,
286 | 0xA5, 0x8C, 0x0D, 0x84, 0xF8, 0xE8, 0x15, 0x4D, 0x95, 0x6A, 0x25, 0x59,
287 | 0x56, 0x6A, 0xED, 0xF1, 0xAB, 0xEF, 0x96, 0xB9, 0x13, 0xC9, 0xA9, 0x5D,
288 | 0xD8, 0xBE, 0x52, 0x76, 0xBE, 0xA0, 0xBD, 0xED, 0x08, 0x19, 0xF8, 0x59,
289 | 0xD7, 0x5E, 0xD4, 0xD1, 0x76, 0xF8, 0xB5, 0x00, 0xE4, 0x97, 0x6B, 0x98,
290 | 0x62, 0x98, 0xF9, 0x0B, 0xC6, 0x48, 0xA4, 0xF3, 0x73, 0x25, 0xF9, 0x05,
291 | 0xC4, 0xC7, 0xB2, 0x9A, 0xF2, 0xBB, 0x92, 0xBF, 0x36, 0xE0, 0xE5, 0xA8,
292 | 0x2B, 0xA0, 0x30, 0x30, 0x3B, 0x83, 0x1E, 0xAB, 0x39, 0xD0, 0x90, 0x51,
293 | 0xC2, 0xEE, 0x31, 0x3D, 0x27, 0xF4, 0xAD, 0x4E, 0x17, 0xD2, 0x65, 0xEA,
294 | 0xD3, 0x4D, 0xAC, 0xAF, 0xFE, 0xF6, 0x5E, 0xDF, 0x6C, 0xDD, 0x8C, 0xE8,
295 | 0xDF, 0xD1, 0x55, 0xAB, 0x8B, 0x9E, 0x68, 0x77, 0x81, 0xB0, 0xA6, 0x3B,
296 | 0x08, 0x79, 0xD3, 0x9E, 0x78, 0x36, 0x38, 0x42, 0xA6, 0x21, 0xF2, 0xF9,
297 | 0x5D, 0x35, 0xCF, 0xB6, 0x09, 0xAA, 0x10, 0x1C, 0x86, 0x54, 0x64, 0x38,
298 | 0x0E, 0x32, 0x46, 0xAB, 0xCE, 0xBD, 0x01, 0x6C, 0xBF, 0x77, 0x9B, 0xAA,
299 | 0x85, 0x70, 0x8A, 0xDC, 0xC0, 0x40, 0x3C, 0x84, 0x12, 0x8B, 0x21, 0x35,
300 | 0x35, 0x5A, 0xC1, 0x57, 0xD4, 0x79, 0xA8, 0x37, 0x71, 0x86, 0xFA, 0x41,
301 | 0xF3, 0xE4, 0xBA, 0xE9, 0x23, 0xC9, 0x21, 0x95, 0xB3, 0xBA, 0x32, 0x2A,
302 | 0x6B, 0xB4, 0x15, 0x54, 0xDD, 0x45, 0x72, 0xCB, 0xBD, 0x07, 0x64, 0x6E,
303 | 0x82, 0x77, 0x47, 0x88, 0xA6, 0x4E, 0x42, 0x18, 0x27, 0x44, 0x21, 0x48,
304 | 0xB8, 0xB8, 0x66, 0xD2, 0xD4, 0x96, 0xB7, 0x70, 0x23, 0x82, 0xE4, 0xE4,
305 | 0x45, 0xBF, 0xA5, 0xF6, 0x00, 0xC4, 0x4C, 0xA3, 0x1C, 0xF2, 0x62, 0x0B,
306 | 0xA7, 0x6C, 0xEB, 0xF0, 0xA3, 0x66, 0x85, 0x4F, 0x04, 0x59, 0x3E, 0xD2,
307 | 0xFD, 0xC0, 0xEA, 0x86, 0x7D, 0x0A, 0x08, 0x95, 0x24, 0x6A, 0x92, 0xE2,
308 | 0xA1, 0xC4, 0x83, 0x99, 0x84, 0x8B, 0x39, 0x7E, 0xF1, 0xB2, 0xCC, 0x9F,
309 | 0x6B, 0x69, 0x70, 0xFF, 0xF7, 0x7A, 0xB4, 0xCC, 0x96, 0xBF, 0xD3, 0x61,
310 | 0x85, 0x03, 0x17, 0xF4, 0xEF, 0x6F, 0xC8, 0x0B, 0x90, 0x5C, 0xFB, 0x06,
311 | 0xFE, 0xD9, 0x92, 0xCE, 0xCF, 0xA3, 0x0C, 0x12, 0x59, 0x2F, 0xDE, 0xDC,
312 | 0x38, 0x2B, 0xFD, 0xD6, 0x74, 0xD9, 0x58, 0x42, 0x3C, 0x0A, 0x2C, 0x4B,
313 | 0xEC, 0x67, 0x09, 0x1F, 0x51, 0x7E, 0x98, 0x25, 0xC7, 0xCF, 0x7A, 0x4E,
314 | 0x94, 0x96, 0x7F, 0x1F, 0x30, 0x32, 0xDE, 0x18, 0x5D, 0x09, 0xD9, 0x85,
315 | 0x16, 0x76, 0x73, 0x54, 0x56, 0x69, 0xC2, 0x6E, 0x7B, 0x63, 0xB3, 0x9E,
316 | 0x92, 0x51, 0x8A, 0x90, 0x3E, 0xB4, 0x9F, 0x83, 0x2A, 0x0F, 0xE7, 0xEF,
317 | 0xA5, 0x68, 0xE1, 0x42, 0x22, 0x5C, 0x6E, 0x77, 0xA9, 0xE3, 0x6B, 0x73,
318 | 0xED, 0x66, 0x2D, 0xB7, 0x94, 0x47, 0xF9, 0xA4, 0x55, 0xBD, 0x14, 0xB4,
319 | 0xD3, 0x23, 0xE6, 0x5D, 0xA9, 0xE1, 0xD1, 0x9C, 0x7E, 0x7F, 0x95, 0x42,
320 | 0x93, 0xF5, 0x1C, 0x38, 0x07, 0x7D, 0x8A, 0x67, 0x04, 0xB6, 0x8B, 0x15,
321 | 0xBD, 0x26, 0x49, 0xE6, 0x38, 0x74, 0x04, 0x5A, 0xCB, 0x68, 0x0D, 0x36,
322 | 0x8E, 0x7A, 0x48, 0xAB, 0x0B, 0x9A, 0x76, 0x0D, 0x39, 0xC9, 0x3F, 0xDE,
323 | 0xC0, 0xA9, 0x3E, 0xF3, 0x55, 0x74, 0xB8, 0x28, 0x0C, 0xC0, 0xF6, 0xD5,
324 | 0xFE, 0x8F, 0x3F, 0x85, 0xC3, 0x38, 0xD2, 0xC8, 0x3E, 0x47, 0xB5, 0x3B,
325 | 0x97, 0x90, 0x77, 0xC9, 0x0C, 0x3A, 0xAF, 0xC4, 0xC0, 0x98, 0x90, 0x89,
326 | 0xE4, 0xC0, 0x51, 0x3C, 0x4E, 0x21, 0x02, 0x7E, 0xA4, 0x85, 0x5F, 0xE6,
327 | 0x57, 0xA8, 0xC4, 0xE7, 0x9E, 0x17, 0x96, 0x60, 0x59, 0xB5, 0x9E, 0x9C,
328 | 0x7D, 0x5F, 0x9A, 0x70, 0xD5, 0x6F, 0x8F, 0x54, 0xCB, 0x58, 0xAD, 0xF1,
329 | 0x83, 0xE7, 0xBC, 0xE4, 0x6C, 0x2B, 0xCA, 0x43, 0x8F, 0xDE, 0x43, 0x6F,
330 | 0xF9, 0x02, 0x0A, 0xA6, 0x35, 0x0F, 0xDD, 0xBC, 0xB3, 0x82, 0xF5, 0x58,
331 | 0x0D, 0x3B, 0x63, 0x30, 0x49, 0x7D, 0x7C, 0xF1, 0xF7, 0x09, 0xF8, 0x35,
332 | 0x17, 0x04, 0xE9, 0x9E, 0x4F, 0x67, 0x60, 0x63, 0x7B, 0x5B, 0x37, 0x44,
333 | 0x97, 0x9C, 0x94, 0x72, 0xD2, 0xDC, 0x78, 0xFF, 0x9A, 0xF5, 0xBF, 0x08,
334 | 0x4E, 0x4D, 0xB1, 0x00, 0xC1, 0xF8, 0x7E, 0xF3, 0x10, 0xA0, 0x60, 0x67,
335 | 0xEE, 0x4D, 0xDD, 0x87, 0x8A, 0x20, 0xF5, 0x31, 0xDD, 0x3A, 0x1C, 0x72,
336 | 0x46, 0xAB, 0xB9, 0x6E, 0x41, 0x03, 0x25, 0x29, 0x21, 0xC7, 0xDC, 0x2F,
337 | 0xBB, 0x2E, 0x81, 0x9D, 0xA0, 0x78, 0xA0, 0x60, 0x2D, 0xDC, 0xFC, 0x20,
338 | 0xF9, 0x94, 0xC5, 0xE8, 0x88, 0xE4, 0x56, 0x49, 0x03, 0x38, 0x87, 0x81,
339 | 0x3D, 0x95, 0x1A, 0xF5, 0xE1, 0xC7, 0x43, 0x9B, 0x40, 0x54, 0xD9, 0xDF,
340 | 0x7E, 0xB0, 0xBD, 0x8D, 0x2F, 0xF2, 0xCA, 0x1E, 0xCA, 0xCE, 0x4D, 0xB1,
341 | 0x3D, 0x00, 0xC8, 0xC4, 0x1B, 0x05, 0x26, 0xDC, 0x11, 0x10, 0xDC, 0x8B,
342 | 0xDB, 0x04, 0xF2, 0x6D, 0xCF, 0x91, 0x24, 0xC7, 0x23, 0x46, 0xD7, 0x9F,
343 | 0x7F, 0xC5, 0x63, 0xA6, 0x6D, 0xF9, 0xF6, 0xD0, 0x49, 0xD2, 0xF5, 0x60,
344 | 0x81, 0x6B, 0xB6, 0x10, 0x37, 0x64, 0xB2, 0xEE, 0x2C, 0x33, 0x39, 0xE7,
345 | 0xFD, 0xBD, 0x94, 0xEB, 0xE3, 0xE0, 0xC4, 0xBF, 0xA8, 0xE7, 0x8E, 0x5D,
346 | 0xC3, 0xC5, 0x76, 0xF6, 0x61, 0x70, 0x35, 0x38, 0x68, 0x4D, 0x3E, 0x8D,
347 | 0x0B, 0x28, 0x60, 0xD5, 0x38, 0xB4, 0x85, 0x7A, 0x19, 0xDA, 0xE7, 0x00,
348 | 0x1B, 0x02, 0xE3, 0x44, 0x73, 0xEE, 0x2B, 0xB2, 0x14, 0x11, 0xF1, 0x7A,
349 | 0xD3, 0xAC, 0x4A, 0x9A, 0x7A, 0xA9, 0x5E, 0xBB, 0x0B, 0xAC, 0x93, 0x79,
350 | 0xD7, 0x2E, 0xC1, 0x3D, 0xA8, 0xED, 0xC7, 0x7A, 0x57, 0x8E, 0x52, 0x26,
351 | 0xC9, 0x78, 0x5B, 0xB1, 0x77, 0xB1, 0xE2, 0xEA, 0x3C, 0x7A, 0xBD, 0xA9,
352 | 0x80, 0x21, 0x81, 0x03, 0x1F, 0x45, 0xD1, 0x0E, 0x7D, 0xC5, 0xB1, 0x9A,
353 | 0xC5, 0xC3, 0xAC, 0xA0, 0x3D, 0x53, 0x0B, 0xBF, 0x13, 0x84, 0x7F, 0x01,
354 | 0x2A, 0x45, 0x28, 0x32, 0xE1, 0x48, 0xEB, 0x86, 0xFF, 0xBC, 0x9C, 0x37,
355 | 0x75, 0x0F, 0x20, 0x3B, 0x2B, 0xB6, 0xBB, 0x0C, 0x7F, 0x62, 0x67, 0x74,
356 | 0xE3, 0xB5, 0x82, 0xC7, 0x37, 0xC5, 0x02, 0xD4, 0x42, 0xE8, 0x6B, 0x02,
357 | 0x7A, 0x2D, 0xA3, 0x41, 0xFF, 0xB6, 0x10, 0x59, 0xB4, 0x26, 0x99, 0x90,
358 | 0x1E, 0x81, 0xBD, 0x05, 0xC8, 0x98, 0xFD, 0x92, 0x80, 0xE9, 0x66, 0xCF,
359 | 0x9C, 0xB7, 0x5E, 0x1F, 0xE4, 0x31, 0x2C, 0x4E, 0x0C, 0x7F, 0x9B, 0x5F,
360 | 0x91, 0x2E, 0x97, 0x91, 0x5A, 0x2E, 0x0F, 0xF7, 0x27, 0xB6, 0xB6, 0xD5,
361 | 0x3A, 0x24, 0x1F, 0x3B, 0x7B, 0xCB, 0x04, 0xF4, 0xC3, 0x2E, 0x23, 0xE6,
362 | 0x06, 0x89, 0xDA, 0x22, 0xFF, 0x48, 0xB3, 0x0C, 0x9D, 0x34, 0xA6, 0x0D,
363 | 0x98, 0x8B, 0x85, 0x7C, 0xF1, 0x58, 0xB2, 0xD9, 0xD4, 0x9D, 0x3C, 0x62,
364 | 0x26, 0xC6, 0x93, 0x84, 0x3B, 0x1F, 0x55, 0x91, 0x53, 0x7A, 0xD9, 0x6E,
365 | 0x32, 0x0C, 0xA1, 0xB5, 0x3E, 0x9F, 0xD3, 0x23, 0x04, 0xDA, 0xE7, 0x57,
366 | 0x89, 0x26, 0x0B, 0x8F, 0x29, 0x45, 0xB5, 0xEB, 0x72, 0x55, 0x01, 0x0F,
367 | 0x33, 0xB8, 0x4D, 0x2F, 0x2B, 0x1D, 0x7E, 0x40, 0x1A, 0x5F, 0x81, 0xFD,
368 | 0x64, 0xBB, 0xAC, 0xDE, 0x59, 0x88, 0xC3, 0xE8, 0x0B, 0x11, 0x04, 0xDE,
369 | 0xAB, 0x01, 0xC9, 0xB6, 0x1A, 0xEE, 0x3A, 0x15, 0x20, 0xF7, 0x5A, 0x6B,
370 | 0x96, 0xE7, 0xBF, 0xC3, 0xC7, 0x2B, 0x41, 0x8B, 0x67, 0xE1, 0xCB, 0xE8,
371 | 0xF2, 0x3D, 0xD8, 0x60, 0x81, 0x69, 0x50, 0x35, 0xC7, 0x1F, 0x8F, 0x2B,
372 | 0xCF, 0x29, 0x10, 0x31, 0x6E, 0xAD, 0x6F, 0xF4, 0x6B, 0x05, 0x40, 0x74,
373 | 0x79, 0x97, 0xDF, 0x61, 0xDA, 0x02, 0x3D, 0x69, 0xCC, 0x3C, 0xD9, 0xE7,
374 | 0xD3, 0xD3, 0x1B, 0x86, 0xA0, 0x85, 0x0B, 0xAD, 0x7A, 0x27, 0xCD, 0x8B,
375 | 0xB7, 0xCE, 0x2F, 0xB7, 0x4F, 0xD8, 0xA5, 0x8C, 0x50, 0x71, 0x84, 0xC0,
376 | 0x28, 0x84, 0xD8, 0x1B, 0x0D, 0xCA, 0x73, 0x46, 0xE5, 0xDB, 0x30, 0xB0,
377 | 0xC1, 0x06, 0x1C, 0xB5, 0xFB, 0xAF, 0x2B, 0x64, 0x3B, 0x04, 0xB5, 0xCC,
378 | 0xCB, 0xEB, 0x4A, 0xB8, 0x52, 0x03, 0xD8, 0xBC, 0xD6, 0x20, 0x55, 0x38,
379 | 0xAA, 0x18, 0x45, 0x3B, 0xBE, 0x1B, 0xD9, 0xFE, 0x32, 0x89, 0xF6, 0x95,
380 | 0x0E, 0xAE, 0x10, 0x3A, 0xAF, 0xAE, 0x33, 0xD7, 0xA7, 0x5B, 0xF8, 0xF9,
381 | 0xD1, 0xC3, 0x8A, 0x97, 0x0C, 0x54, 0x64, 0x6A, 0x5C, 0x57, 0xA1, 0xC5,
382 | 0x2B, 0xCD, 0xAD, 0xC2, 0x65, 0x54, 0xD4, 0xC3, 0x05, 0x1F, 0x67, 0x47,
383 | 0xF3, 0xFF, 0x42, 0x85, 0x92, 0x8F, 0x52, 0x0F, 0xCD, 0xB9, 0xA8, 0x3D,
384 | 0x28, 0x69, 0xE9, 0x13, 0x86, 0xCB, 0xE5, 0x0A, 0x23, 0x24, 0x60, 0x55,
385 | 0x22, 0xB0, 0xCF, 0x1A, 0x6B, 0xF1, 0xC9, 0x68, 0xA5, 0x28, 0xBC, 0xBE,
386 | 0x5E, 0xD1, 0xD1, 0xB1, 0x30, 0x41, 0xF5, 0xA9, 0x93, 0x0B, 0xBB, 0x0E,
387 | 0xB9, 0xFD, 0x3F, 0xAF, 0xAD, 0x8A, 0x07, 0x52, 0x60, 0x21, 0xBF, 0xB3,
388 | 0x05, 0x9D, 0x4D, 0xFB, 0xA3, 0x85, 0x27, 0xE3, 0xE5, 0x87, 0x5A, 0xD0,
389 | 0xA5, 0x68, 0x8E, 0x68, 0x76, 0x03, 0x84, 0x38, 0x8D, 0xAC, 0x46, 0x9B,
390 | 0xFD, 0x86, 0xF2, 0x23
391 | ];
392 | }
--------------------------------------------------------------------------------
/GCDTool/GcdHeader.cs:
--------------------------------------------------------------------------------
1 | using GCDTool.IO;
2 | using GCDTool.Wram;
3 | using System.Security.Cryptography;
4 |
5 | namespace GCDTool;
6 |
7 | ///
8 | /// Class representing the header of a GCD rom file.
9 | ///
10 | sealed class GcdHeader
11 | {
12 | ///
13 | /// The game code of the GCD rom.
14 | ///
15 | public uint GameCode { get; set; }
16 |
17 | ///
18 | /// The offset of the ARM 9 binary in the rom.
19 | ///
20 | public uint Arm9RomOffset { get; set; }
21 |
22 | ///
23 | /// The original size of the ARM 9 binary.
24 | ///
25 | public uint Arm9Size { get; set; }
26 |
27 | ///
28 | /// The memory address where the ARM 9 binary will be loaded.
29 | ///
30 | public uint Arm9LoadAddress { get; set; }
31 |
32 | ///
33 | /// The padded size of the ARM 9 binary.
34 | ///
35 | public uint Arm9PaddedSize { get; set; }
36 |
37 | ///
38 | /// The offset of the ARM 7 binary in the rom.
39 | ///
40 | public uint Arm7RomOffset { get; set; }
41 |
42 | ///
43 | /// The original size of the ARM 7 binary.
44 | ///
45 | public uint Arm7Size { get; set; }
46 |
47 | ///
48 | /// The memory address where the ARM 7 binary will be loaded.
49 | ///
50 | public uint Arm7LoadAddress { get; set; }
51 |
52 | ///
53 | /// The padded size of the ARM 7 binary.
54 | ///
55 | public uint Arm7PaddedSize { get; set; }
56 |
57 | ///
58 | /// The regular rom control settings.
59 | ///
60 | public uint RomControl { get; set; }
61 |
62 | ///
63 | /// The secure rom control settings.
64 | ///
65 | public uint RomControlSecure { get; set; }
66 |
67 | ///
68 | /// The secure area delay.
69 | ///
70 | public ushort SecureAreaDelay { get; set; }
71 |
72 | ///
73 | /// The end of the nitro region of the rom.
74 | ///
75 | public ushort NitroRomRegionEnd { get; set; }
76 |
77 | ///
78 | /// The start of the twl region of the rom.
79 | ///
80 | public ushort TwlRomRegionStart { get; set; }
81 |
82 | ///
83 | /// Flags controlling how the GCD firm is loaded.
84 | ///
85 | public GcdHeaderFlags Flags { get; set; }
86 |
87 | ///
88 | /// The RSA signed signature. See for the format before signing.
89 | ///
90 | public byte[] Signature { get; } = new byte[GcdSignature.SIGNATURE_SIGNED_LENGTH];
91 |
92 | ///
93 | /// The initial WRAM configuration used by the GCD firm.
94 | ///
95 | public WramConfig WramConfiguration { get; set; } = new();
96 |
97 | ///
98 | /// Serializes the header data as a byte array.
99 | ///
100 | /// The header data as a byte array.
101 | public byte[] ToByteArray()
102 | {
103 | var header = new byte[0x200];
104 | IOUtil.WriteU32Le(header, 0x0C, GameCode);
105 | IOUtil.WriteU32Le(header, 0x20, Arm9RomOffset);
106 | IOUtil.WriteU32Le(header, 0x24, Arm9Size);
107 | IOUtil.WriteU32Le(header, 0x28, Arm9LoadAddress);
108 | IOUtil.WriteU32Le(header, 0x2C, Arm9PaddedSize);
109 | IOUtil.WriteU32Le(header, 0x30, Arm7RomOffset);
110 | IOUtil.WriteU32Le(header, 0x34, Arm7Size);
111 | IOUtil.WriteU32Le(header, 0x38, Arm7LoadAddress);
112 | IOUtil.WriteU32Le(header, 0x3C, Arm7PaddedSize);
113 | IOUtil.WriteU32Le(header, 0x60, RomControl);
114 | IOUtil.WriteU32Le(header, 0x64, RomControlSecure);
115 | IOUtil.WriteU16Le(header, 0x6E, SecureAreaDelay);
116 | IOUtil.WriteU16Le(header, 0x90, NitroRomRegionEnd);
117 | IOUtil.WriteU16Le(header, 0x92, TwlRomRegionStart);
118 | header[0xFF] = (byte)Flags;
119 | Signature.CopyTo(header, 0x100);
120 | WramConfiguration.Write(header.AsSpan(0x180));
121 | return header;
122 | }
123 |
124 | ///
125 | /// Calculates the SHA-1 hash used for .
126 | ///
127 | /// The byte span to write the SHA-1 hash to.
128 | public void GetSha1Hash(Span destination)
129 | {
130 | var headerData = ToByteArray();
131 | SHA1.HashData([.. headerData.AsSpan(0, 0x100), .. headerData.AsSpan(0x180, 0x80)], destination);
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/GCDTool/GcdHeaderFlags.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool;
2 |
3 | [Flags]
4 | enum GcdHeaderFlags : byte
5 | {
6 | Arm9Compressed = (1 << 0),
7 | Arm7Compressed = (1 << 1),
8 | Arm9Speed134MHz = (1 << 2)
9 | }
--------------------------------------------------------------------------------
/GCDTool/GcdRom.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool;
2 |
3 | ///
4 | /// Class representing a GCD rom file.
5 | ///
6 | sealed class GcdRom
7 | {
8 | private const int BLOWFISH_P_TABLE_OFFSET = 0x1600;
9 | private const int BLOWFISH_S_BOXES_OFFSET = 0x1C00;
10 | private const int TEST_PATTERNS_OFFSET = 0x3000;
11 |
12 | ///
13 | /// The header of the GCD rom.
14 | ///
15 | public GcdHeader Header { get; set; } = new();
16 |
17 | ///
18 | /// The encrypted ARM 9 binary.
19 | ///
20 | public byte[] EncryptedArm9Binary { get; set; } = [];
21 |
22 | ///
23 | /// The encrypted ARM 7 binary.
24 | ///
25 | public byte[] EncryptedArm7Binary { get; set; } = [];
26 |
27 | ///
28 | /// Writes the GCD rom file to the given .
29 | ///
30 | /// The stream to write to.
31 | public void Write(Stream stream)
32 | {
33 | stream.Write(Header.ToByteArray());
34 | WriteBlowfishTable(stream);
35 | WriteTestPatterns(stream);
36 | stream.Position = Header.Arm9RomOffset;
37 | stream.Write(EncryptedArm9Binary);
38 | stream.Position = Header.Arm7RomOffset;
39 | stream.Write(EncryptedArm7Binary);
40 | }
41 |
42 | private void WriteBlowfishTable(Stream stream)
43 | {
44 | var blowfishTable = GcdBlowfish.GetTransformedKeyTable(Header.GameCode);
45 | stream.Position = BLOWFISH_P_TABLE_OFFSET;
46 | stream.Write(blowfishTable, 0, Blowfish.P_TABLE_ENTRY_COUNT * 4);
47 | stream.Position = BLOWFISH_S_BOXES_OFFSET;
48 | stream.Write(blowfishTable, Blowfish.P_TABLE_ENTRY_COUNT * 4, Blowfish.S_BOX_COUNT * Blowfish.S_BOX_ENTRY_COUNT * 4);
49 | }
50 |
51 | private void WriteTestPatterns(Stream stream)
52 | {
53 | stream.Position = TEST_PATTERNS_OFFSET;
54 | stream.Write([0xFF, 0x00, 0xFF, 0x00, 0xAA, 0x55, 0xAA, 0x55]);
55 | for (int i = 8; i < 0x200; i++)
56 | {
57 | stream.WriteByte((byte)(i & 0xFF));
58 | }
59 | for (int i = 0; i < 0x200; i++)
60 | {
61 | stream.WriteByte((byte)(0xFF - (i & 0xFF)));
62 | }
63 | for (int i = 0; i < 0x200; i++)
64 | {
65 | stream.WriteByte(0x00);
66 | }
67 | for (int i = 0; i < 0x200; i++)
68 | {
69 | stream.WriteByte(0xFF);
70 | }
71 | for (int i = 0; i < 0x200; i++)
72 | {
73 | stream.WriteByte(0x0F);
74 | }
75 | for (int i = 0; i < 0x200; i++)
76 | {
77 | stream.WriteByte(0xF0);
78 | }
79 | for (int i = 0; i < 0x200; i++)
80 | {
81 | stream.WriteByte(0x55);
82 | }
83 | for (int i = 0; i < 0x1FF; i++)
84 | {
85 | stream.WriteByte(0xAA);
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/GCDTool/GcdSignature.cs:
--------------------------------------------------------------------------------
1 | using Org.BouncyCastle.Crypto.Encodings;
2 | using Org.BouncyCastle.Crypto.Engines;
3 | using Org.BouncyCastle.Crypto;
4 | using Org.BouncyCastle.OpenSsl;
5 | using System.Security.Cryptography;
6 |
7 | namespace GCDTool;
8 |
9 | ///
10 | /// Class representing a GCD signature.
11 | ///
12 | sealed class GcdSignature
13 | {
14 | ///
15 | /// The length of the signature data before signing.
16 | ///
17 | public const int SIGNATURE_LENGTH = 0x74;
18 |
19 | ///
20 | /// The length of the signature data after signing.
21 | ///
22 | public const int SIGNATURE_SIGNED_LENGTH = 0x80;
23 |
24 | ///
25 | /// AES key Y used for encrypting the ARM 9 and ARM 7 binaries.
26 | ///
27 | public byte[] AesKeyY { get; } = new byte[16];
28 |
29 | ///
30 | /// The SHA-1 hash of the 0x180 bytes consisting of header[0..0x100] and header[0x180..0x200].
31 | ///
32 | public byte[] HeaderSha1 { get; } = new byte[20];
33 |
34 | ///
35 | /// The SHA-1 hash of the original ARM 9 binary (without padding and AES encryption).
36 | ///
37 | public byte[] Arm9Sha1 { get; } = new byte[20];
38 |
39 | ///
40 | /// The SHA-1 hash of the original ARM 7 binary (without padding and AES encryption).
41 | ///
42 | public byte[] Arm7Sha1 { get; } = new byte[20];
43 |
44 | ///
45 | /// Serializes the signature as a byte array.
46 | ///
47 | /// The signature as byte array.
48 | public byte[] ToByteArray()
49 | {
50 | var signature = new byte[SIGNATURE_LENGTH];
51 | AesKeyY.CopyTo(signature, 0x00);
52 | HeaderSha1.CopyTo(signature, 0x10);
53 | Arm9Sha1.CopyTo(signature, 0x24);
54 | Arm7Sha1.CopyTo(signature, 0x38);
55 | SHA1.HashData(signature.AsSpan(0, 0x60), signature.AsSpan(0x60));
56 | return signature;
57 | }
58 |
59 | ///
60 | /// Serializes the signature as a byte array and signs the result
61 | /// using the RSA private key specified by .
62 | ///
63 | /// The path to the .der file containing the private RSA key to use.
64 | /// A byte array containing the signed signature.
65 | public byte[] ToSignedByteArray(string derKeyPath)
66 | {
67 | var rsaCryptoServiceProvider = new RSACryptoServiceProvider();
68 | rsaCryptoServiceProvider.ImportRSAPrivateKey(File.ReadAllBytes(derKeyPath), out _);
69 | string pem = rsaCryptoServiceProvider.ExportRSAPrivateKeyPem();
70 |
71 | var keyPair = (AsymmetricCipherKeyPair)new PemReader(new StringReader(pem)).ReadObject();
72 | var rsa = new Pkcs1Encoding(new RsaEngine());
73 | rsa.Init(true, keyPair.Private);
74 | return rsa.ProcessBlock(ToByteArray(), 0, SIGNATURE_LENGTH);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/GCDTool/IO/EndianBinaryReader.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 | using System.Runtime.InteropServices;
3 | using System.Text;
4 |
5 | namespace GCDTool.IO;
6 |
7 | sealed class EndianBinaryReader : IDisposable
8 | {
9 | private bool _disposed;
10 | private byte[]? _buffer;
11 |
12 | public Stream BaseStream { get; }
13 | public Endianness Endianness { get; }
14 |
15 | public static Endianness SystemEndianness =>
16 | BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian;
17 |
18 | private bool Reverse => SystemEndianness != Endianness;
19 |
20 | public EndianBinaryReader(Stream baseStream, Endianness endianness = Endianness.LittleEndian)
21 | {
22 | if (!baseStream.CanRead)
23 | {
24 | throw new ArgumentException("Stream is not readable.", nameof(baseStream));
25 | }
26 |
27 | BaseStream = baseStream;
28 | Endianness = endianness;
29 | }
30 |
31 | ~EndianBinaryReader()
32 | {
33 | Dispose(false);
34 | }
35 |
36 | [MemberNotNull(nameof(_buffer))]
37 | private void FillBuffer(int bytes, int stride)
38 | {
39 | if (_buffer == null || _buffer.Length < bytes)
40 | {
41 | _buffer = new byte[bytes];
42 | }
43 |
44 | BaseStream.Read(_buffer, 0, bytes);
45 |
46 | if (Reverse && stride > 1)
47 | {
48 | for (int i = 0; i < bytes; i += stride)
49 | {
50 | Array.Reverse(_buffer, i, stride);
51 | }
52 | }
53 | }
54 |
55 | public char ReadChar(Encoding encoding)
56 | {
57 | int size = GetEncodingSize(encoding);
58 | FillBuffer(size, size);
59 | return encoding.GetChars(_buffer, 0, size)[0];
60 | }
61 |
62 | public char[] ReadChars(Encoding encoding, int count)
63 | {
64 | int size = GetEncodingSize(encoding);
65 | FillBuffer(size * count, size);
66 | return encoding.GetChars(_buffer, 0, size * count);
67 | }
68 |
69 | private static int GetEncodingSize(Encoding encoding)
70 | {
71 | if (encoding == Encoding.UTF8 || encoding == Encoding.ASCII)
72 | {
73 | return 1;
74 | }
75 | else if (encoding == Encoding.Unicode || encoding == Encoding.BigEndianUnicode)
76 | {
77 | return 2;
78 | }
79 | else
80 | {
81 | return 1;
82 | }
83 | }
84 |
85 | public string ReadStringNT(Encoding encoding)
86 | {
87 | string text = string.Empty;
88 | do
89 | {
90 | text += ReadChar(encoding);
91 | } while (!text.EndsWith("\0", StringComparison.Ordinal));
92 |
93 | return text.Remove(text.Length - 1);
94 | }
95 |
96 | public string ReadString(Encoding encoding, int count)
97 | {
98 | return new string(ReadChars(encoding, count));
99 | }
100 |
101 | public unsafe T Read() where T : unmanaged
102 | {
103 | int size = sizeof(T);
104 | FillBuffer(size, size);
105 | return MemoryMarshal.Read(_buffer);
106 | }
107 |
108 | public unsafe T[] Read(int count) where T : unmanaged
109 | {
110 | int size = sizeof(T);
111 | var result = new T[count];
112 | var byteResult = MemoryMarshal.Cast(result);
113 | BaseStream.Read(byteResult);
114 |
115 | if (Reverse && size > 1)
116 | {
117 | for (int i = 0; i < size * count; i += size)
118 | {
119 | byteResult.Slice(i, size).Reverse();
120 | }
121 | }
122 |
123 | return result;
124 | }
125 |
126 | public void Close()
127 | {
128 | Dispose();
129 | }
130 |
131 | public void Dispose()
132 | {
133 | Dispose(true);
134 | GC.SuppressFinalize(this);
135 | }
136 |
137 | private void Dispose(bool disposing)
138 | {
139 | if (_disposed)
140 | {
141 | return;
142 | }
143 |
144 | if (disposing)
145 | {
146 | BaseStream?.Close();
147 | }
148 |
149 | _buffer = null;
150 | _disposed = true;
151 | }
152 | }
--------------------------------------------------------------------------------
/GCDTool/IO/Endianness.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool.IO;
2 |
3 | enum Endianness
4 | {
5 | BigEndian,
6 | LittleEndian
7 | }
--------------------------------------------------------------------------------
/GCDTool/IO/IOUtil.cs:
--------------------------------------------------------------------------------
1 | using System.Buffers.Binary;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | namespace GCDTool.IO;
6 |
7 | static class IOUtil
8 | {
9 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
10 | public static short ReadS16Le(byte[] data, int offset)
11 | => BinaryPrimitives.ReadInt16LittleEndian(data.AsSpan(offset));
12 |
13 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
14 | public static short ReadS16Le(ReadOnlySpan span)
15 | => BinaryPrimitives.ReadInt16LittleEndian(span);
16 |
17 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
18 | public static short[] ReadS16Le(byte[] data, int offset, int count)
19 | => ReadS16Le(data.AsSpan(offset), count);
20 |
21 | public static short[] ReadS16Le(ReadOnlySpan data, int count)
22 | {
23 | var res = MemoryMarshal.Cast(data.Slice(0, count * 2)).ToArray();
24 | if (!BitConverter.IsLittleEndian)
25 | {
26 | for (int i = 0; i < count; i++)
27 | res[i] = BinaryPrimitives.ReverseEndianness(res[i]);
28 | }
29 |
30 | return res;
31 | }
32 |
33 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
34 | public static void WriteS16Le(byte[] data, int offset, short value)
35 | => BinaryPrimitives.WriteInt16LittleEndian(data.AsSpan(offset), value);
36 |
37 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
38 | public static void WriteS16Le(Span span, short value)
39 | => BinaryPrimitives.WriteInt16LittleEndian(span, value);
40 |
41 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
42 | public static void WriteS16Le(byte[] data, int offset, ReadOnlySpan values)
43 | => WriteS16Le(data.AsSpan(offset), values);
44 |
45 | public static void WriteS16Le(Span data, ReadOnlySpan values)
46 | {
47 | var dst = MemoryMarshal.Cast(data);
48 | values.CopyTo(dst);
49 | if (!BitConverter.IsLittleEndian)
50 | {
51 | for (int i = 0; i < values.Length; i++)
52 | dst[i] = BinaryPrimitives.ReverseEndianness(dst[i]);
53 | }
54 | }
55 |
56 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
57 | public static short ReadS16Be(byte[] data, int offset)
58 | => BinaryPrimitives.ReadInt16BigEndian(data.AsSpan(offset));
59 |
60 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
61 | public static short ReadS16Be(ReadOnlySpan span)
62 | => BinaryPrimitives.ReadInt16BigEndian(span);
63 |
64 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
65 | public static ushort[] ReadU16Le(byte[] data, int offset, int count)
66 | => ReadU16Le(data.AsSpan(offset), count);
67 |
68 | public static ushort[] ReadU16Le(ReadOnlySpan data, int count)
69 | {
70 | var res = MemoryMarshal.Cast(data.Slice(0, count * 2)).ToArray();
71 | if (!BitConverter.IsLittleEndian)
72 | {
73 | for (int i = 0; i < count; i++)
74 | res[i] = BinaryPrimitives.ReverseEndianness(res[i]);
75 | }
76 |
77 | return res;
78 | }
79 |
80 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
81 | public static ushort ReadU16Le(byte[] data, int offset)
82 | => BinaryPrimitives.ReadUInt16LittleEndian(data.AsSpan(offset));
83 |
84 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
85 | public static ushort ReadU16Le(ReadOnlySpan span)
86 | => BinaryPrimitives.ReadUInt16LittleEndian(span);
87 |
88 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
89 | public static void WriteU16Le(byte[] data, int offset, ushort value)
90 | => BinaryPrimitives.WriteUInt16LittleEndian(data.AsSpan(offset), value);
91 |
92 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
93 | public static void WriteU16Le(Span span, ushort value)
94 | => BinaryPrimitives.WriteUInt16LittleEndian(span, value);
95 |
96 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
97 | public static void WriteU16Le(byte[] data, int offset, ReadOnlySpan values)
98 | => WriteU16Le(data.AsSpan(offset), values);
99 |
100 | public static void WriteU16Le(Span data, ReadOnlySpan values)
101 | {
102 | var dst = MemoryMarshal.Cast(data);
103 | values.CopyTo(dst);
104 | if (!BitConverter.IsLittleEndian)
105 | {
106 | for (int i = 0; i < values.Length; i++)
107 | dst[i] = BinaryPrimitives.ReverseEndianness(dst[i]);
108 | }
109 | }
110 |
111 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
112 | public static ushort ReadU16Be(byte[] data, int offset)
113 | => BinaryPrimitives.ReadUInt16BigEndian(data.AsSpan(offset));
114 |
115 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
116 | public static ushort ReadU16Be(ReadOnlySpan span)
117 | => BinaryPrimitives.ReadUInt16BigEndian(span);
118 |
119 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
120 | public static void WriteU16Be(byte[] data, int offset, ushort value)
121 | => BinaryPrimitives.WriteUInt16BigEndian(data.AsSpan(offset), value);
122 |
123 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
124 | public static void WriteU16Be(Span span, ushort value)
125 | => BinaryPrimitives.WriteUInt16BigEndian(span, value);
126 |
127 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
128 | public static uint ReadU24Le(byte[] data, int offset)
129 | => (uint)(data[offset] | data[offset + 1] << 8 | data[offset + 2] << 16);
130 |
131 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
132 | public static uint ReadU24Le(ReadOnlySpan span)
133 | => (uint)(span[0] | span[1] << 8 | span[2] << 16);
134 |
135 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
136 | public static uint ReadU32Le(byte[] data, int offset)
137 | => BinaryPrimitives.ReadUInt32LittleEndian(data.AsSpan(offset));
138 |
139 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
140 | public static uint ReadU32Le(ReadOnlySpan span)
141 | => BinaryPrimitives.ReadUInt32LittleEndian(span);
142 |
143 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
144 | public static uint[] ReadU32Le(byte[] data, int offset, int count)
145 | => ReadU32Le(data.AsSpan(offset), count);
146 |
147 | public static uint[] ReadU32Le(ReadOnlySpan data, int count)
148 | {
149 | var res = MemoryMarshal.Cast(data.Slice(0, count * 4)).ToArray();
150 | if (!BitConverter.IsLittleEndian)
151 | {
152 | for (int i = 0; i < count; i++)
153 | res[i] = BinaryPrimitives.ReverseEndianness(res[i]);
154 | }
155 |
156 | return res;
157 | }
158 |
159 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
160 | public static uint ReadU32Be(byte[] data, int offset)
161 | => BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(offset));
162 |
163 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
164 | public static uint ReadU32Be(ReadOnlySpan span)
165 | => BinaryPrimitives.ReadUInt32BigEndian(span);
166 |
167 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
168 | public static void WriteU32Le(byte[] data, int offset, uint value)
169 | => BinaryPrimitives.WriteUInt32LittleEndian(data.AsSpan(offset), value);
170 |
171 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
172 | public static void WriteU32Le(Span span, uint value)
173 | => BinaryPrimitives.WriteUInt32LittleEndian(span, value);
174 |
175 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
176 | public static void WriteU32Le(byte[] data, int offset, ReadOnlySpan values)
177 | => WriteU32Le(data.AsSpan(offset), values);
178 |
179 | public static void WriteU32Le(Span data, ReadOnlySpan values)
180 | {
181 | var dst = MemoryMarshal.Cast(data);
182 | values.CopyTo(dst);
183 | if (!BitConverter.IsLittleEndian)
184 | {
185 | for (int i = 0; i < values.Length; i++)
186 | dst[i] = BinaryPrimitives.ReverseEndianness(dst[i]);
187 | }
188 | }
189 |
190 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
191 | public static ulong ReadU64Le(byte[] data, int offset)
192 | => BinaryPrimitives.ReadUInt64LittleEndian(data.AsSpan(offset));
193 |
194 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
195 | public static ulong ReadU64Le(ReadOnlySpan span)
196 | => BinaryPrimitives.ReadUInt64LittleEndian(span);
197 |
198 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
199 | public static ulong ReadU64Be(byte[] data, int offset)
200 | => BinaryPrimitives.ReadUInt64BigEndian(data.AsSpan(offset));
201 |
202 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
203 | public static ulong ReadU64Be(ReadOnlySpan span)
204 | => BinaryPrimitives.ReadUInt64BigEndian(span);
205 |
206 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
207 | public static void WriteU64Le(byte[] data, int offset, ulong value)
208 | => BinaryPrimitives.WriteUInt64LittleEndian(data.AsSpan(offset), value);
209 |
210 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
211 | public static void WriteU64Le(Span span, ulong value)
212 | => BinaryPrimitives.WriteUInt64LittleEndian(span, value);
213 |
214 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
215 | public static void WriteU64Be(byte[] data, int offset, ulong value)
216 | => BinaryPrimitives.WriteUInt64BigEndian(data.AsSpan(offset), value);
217 |
218 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
219 | public static void WriteU64Be(Span span, ulong value)
220 | => BinaryPrimitives.WriteUInt64BigEndian(span, value);
221 | }
--------------------------------------------------------------------------------
/GCDTool/Program.cs:
--------------------------------------------------------------------------------
1 | using CommandLine;
2 | using CommandLine.Text;
3 | using GCDTool.Elf;
4 | using GCDTool.IO;
5 | using GCDTool.Wram;
6 | using Org.BouncyCastle.Crypto.Parameters;
7 | using Org.BouncyCastle.Security;
8 | using System.Security.Cryptography;
9 | using System.Text;
10 |
11 | namespace GCDTool;
12 |
13 | static class Program
14 | {
15 | private const string GCDTOOL_HEADING = "== GCDTool by Gericom ==";
16 | private const string AES_CIPHER_NAME = "AES/CTR/NoPadding";
17 | private const string ALGORITHM_AES = "AES";
18 | private const int AES_BLOCK_SIZE = 16;
19 | private const uint ROM_CONTROL = 0x00416657;
20 | private const uint ROM_CONTROL_SECURE = 0x081808F8;
21 | private const ushort SECURE_AREA_DELAY = 3454;
22 |
23 | private static ReadOnlySpan AesKeyX
24 | => [0x4E, 0x69, 0x6E, 0x74, 0x65, 0x6E, 0x64, 0x6F, 0x20, 0x44, 0x53, 0x00, 0x01, 0x23, 0x21, 0x00];
25 |
26 | private static void Main(string[] args)
27 | {
28 | var parser = new Parser(with => with.HelpWriter = null);
29 | var parserResult = parser.ParseArguments(args);
30 | parserResult
31 | .WithParsed(RunOptions)
32 | .WithNotParsed(_ =>
33 | {
34 | Console.WriteLine(HelpText.AutoBuild(parserResult, h =>
35 | {
36 | h.AutoVersion = false;
37 | h.Copyright = string.Empty;
38 | h.Heading = GCDTOOL_HEADING;
39 | return HelpText.DefaultParsingErrorsHandler(parserResult, h);
40 | }, e => e));
41 | });
42 | }
43 |
44 | private static void RunOptions(CommandLineOptions options)
45 | {
46 | if (options.GameCode.Length != 4)
47 | {
48 | Console.WriteLine("Invalid game code specified. Should be 4 characters.");
49 | return;
50 | }
51 |
52 | uint gameCode = IOUtil.ReadU32Le(Encoding.ASCII.GetBytes(options.GameCode));
53 |
54 | // This can be any key, used for encryption of the arm7 and arm9 binaries.
55 | // To have reproducable results, we'll simply use an all-zeros key.
56 | byte[] aesKeyY = new byte[16];
57 |
58 | var gcdRom = new GcdRom
59 | {
60 | Header = new GcdHeader
61 | {
62 | GameCode = gameCode,
63 |
64 | RomControl = ROM_CONTROL,
65 | RomControlSecure = ROM_CONTROL_SECURE,
66 | SecureAreaDelay = SECURE_AREA_DELAY,
67 |
68 | // it appears to be unnecessary to specify the rom area
69 | NitroRomRegionEnd = 0,
70 | TwlRomRegionStart = 0,
71 |
72 | Flags = options.Arm9Speed67MHz ? 0 : GcdHeaderFlags.Arm9Speed134MHz
73 | },
74 | };
75 |
76 | var signature = new GcdSignature();
77 | aesKeyY.CopyTo(signature.AesKeyY, 0);
78 |
79 | if (!TryLoadArm9(options.Arm9Path, gcdRom, signature) ||
80 | !TryLoadArm7(options.Arm7Path, gcdRom, signature) ||
81 | !TryLoadWramConfig(options.WramConfigJsonPath, gcdRom))
82 | {
83 | return;
84 | }
85 |
86 | gcdRom.Header.GetSha1Hash(signature.HeaderSha1);
87 | var signedSignature = signature.ToSignedByteArray(options.GcdKeyPath);
88 | signedSignature.CopyTo(gcdRom.Header.Signature, 0);
89 |
90 | using (var stream = File.Create(options.OutputPath))
91 | {
92 | gcdRom.Write(stream);
93 | }
94 | }
95 |
96 | private static bool TryLoadArm9(string arm9ElfPath, GcdRom gcdRom, GcdSignature signature)
97 | {
98 | var arm9Data = ParseElf(arm9ElfPath, out uint arm9LoadAddress);
99 | if (arm9LoadAddress < 0x03000000)
100 | {
101 | Console.WriteLine("Error: ARM9 binary cannot be loaded to main memory. Use TWL wram instead.");
102 | return false;
103 | }
104 | gcdRom.Header.Arm9LoadAddress = arm9LoadAddress;
105 | gcdRom.Header.Arm9Size = (uint)arm9Data.Length;
106 | SHA1.HashData(arm9Data, signature.Arm9Sha1);
107 |
108 | if ((arm9Data.Length % 512) != 0)
109 | {
110 | arm9Data = [.. arm9Data, .. new byte[512 - (arm9Data.Length % 512)]];
111 | }
112 | gcdRom.Header.Arm9PaddedSize = (uint)arm9Data.Length;
113 | EncryptAes(arm9Data, signature.AesKeyY);
114 | gcdRom.EncryptedArm9Binary = arm9Data;
115 | gcdRom.Header.Arm9RomOffset = 0x4000;
116 | return true;
117 | }
118 |
119 | private static bool TryLoadArm7(string arm7ElfPath, GcdRom gcdRom, GcdSignature signature)
120 | {
121 | var arm7Data = ParseElf(arm7ElfPath, out uint arm7LoadAddress);
122 | if (arm7LoadAddress < 0x03000000)
123 | {
124 | Console.WriteLine("Error: ARM7 binary cannot be loaded to main memory. Use TWL wram instead.");
125 | return false;
126 | }
127 | gcdRom.Header.Arm7LoadAddress = arm7LoadAddress;
128 | gcdRom.Header.Arm7Size = (uint)arm7Data.Length;
129 | SHA1.HashData(arm7Data, signature.Arm7Sha1);
130 |
131 | if ((arm7Data.Length % 512) != 0)
132 | {
133 | arm7Data = [.. arm7Data, .. new byte[512 - (arm7Data.Length % 512)]];
134 | }
135 | gcdRom.Header.Arm7PaddedSize = (uint)arm7Data.Length;
136 | EncryptAes(arm7Data, signature.AesKeyY);
137 | gcdRom.EncryptedArm7Binary = arm7Data;
138 | gcdRom.Header.Arm7RomOffset = (uint)(0x4000 + gcdRom.EncryptedArm9Binary.Length);
139 | return true;
140 | }
141 |
142 | private static byte[] ParseElf(string elfPath, out uint loadAddress)
143 | {
144 | loadAddress = 0;
145 | var elf = new ElfFile(File.ReadAllBytes(elfPath));
146 | var programData = new MemoryStream();
147 | bool loadAddressSet = false;
148 | foreach (var programHeader in elf.ProgramHeaderTable)
149 | {
150 | if (programHeader.SegmentType == ProgramHeaderTableEntry.ElfSegmentType.Load &&
151 | programHeader.SegmentData is not null &&
152 | programHeader.SegmentData.Length != 0)
153 | {
154 | if (!loadAddressSet)
155 | {
156 | loadAddress = programHeader.PhysicalAddress;
157 | loadAddressSet = true;
158 | }
159 | programData.Write(programHeader.SegmentData);
160 | }
161 | }
162 |
163 | return programData.ToArray();
164 | }
165 |
166 | private static void ByteSwapAesBlocks(Span data)
167 | {
168 | for (int i = 0; i < data.Length; i += AES_BLOCK_SIZE)
169 | {
170 | data.Slice(i, AES_BLOCK_SIZE).Reverse();
171 | }
172 | }
173 |
174 | private static byte[] CreateAesInitializationVector(uint dataLength)
175 | {
176 | var iv = new byte[16];
177 | IOUtil.WriteU32Le(iv, 0, dataLength);
178 | IOUtil.WriteU32Le(iv, 4, (uint)-dataLength);
179 | IOUtil.WriteU32Le(iv, 8, ~dataLength);
180 | return iv;
181 | }
182 |
183 | private static void EncryptAes(Span data, ReadOnlySpan keyY)
184 | {
185 | var scrambledKey = AesKeyScrambler.Scramble(AesKeyX, keyY);
186 | Array.Reverse(scrambledKey);
187 | ByteSwapAesBlocks(data);
188 | var iv = CreateAesInitializationVector((uint)data.Length);
189 | Array.Reverse(iv);
190 | var cipher = CipherUtilities.GetCipher(AES_CIPHER_NAME);
191 | cipher.Init(true, new ParametersWithIV(
192 | ParameterUtilities.CreateKeyParameter(ALGORITHM_AES, scrambledKey), iv));
193 | cipher.ProcessBytes(data, data);
194 | cipher.DoFinal(data);
195 | ByteSwapAesBlocks(data);
196 | }
197 |
198 | private static bool TryLoadWramConfig(string wramConfigJsonPath, GcdRom gcdRom)
199 | {
200 | try
201 | {
202 | string wramConfigJson = File.ReadAllText(wramConfigJsonPath);
203 | gcdRom.Header.WramConfiguration = new WramConfigJsonReader().ReadWramConfigFromJson(wramConfigJson);
204 | return true;
205 | }
206 | catch (Exception exception)
207 | {
208 | Console.WriteLine("Failed to parse wram configuration JSON: " + exception.Message);
209 | return false;
210 | }
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/GCDTool/Wram/NtrWramMaster.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool.Wram;
2 |
3 | ///
4 | /// The master of a NTR wram block.
5 | ///
6 | enum NtrWramMaster
7 | {
8 | Arm9 = 0,
9 | Arm7 = 1
10 | }
--------------------------------------------------------------------------------
/GCDTool/Wram/WramABlockMapping.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool.Wram;
2 |
3 | ///
4 | /// Specifies the mapping of TWL wram A block.
5 | ///
6 | readonly record struct WramABlockMapping(WramAMaster Master, WramASlot Slot, bool Enable)
7 | {
8 | public static explicit operator byte(WramABlockMapping wramABlockMapping)
9 | {
10 | return (byte)(((byte)wramABlockMapping.Master & 1)
11 | | (((byte)wramABlockMapping.Slot & 3) << 2)
12 | | (wramABlockMapping.Enable ? 0x80 : 0));
13 | }
14 | }
--------------------------------------------------------------------------------
/GCDTool/Wram/WramAMappedSlots.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool.Wram;
2 |
3 | ///
4 | /// Specifies which TWL wram A slots are mapped in memory.
5 | ///
6 | enum WramAMappedSlots
7 | {
8 | ///
9 | /// Only slot 0 mapped. 64 kB mirrors.
10 | ///
11 | Slot0 = 0,
12 |
13 | ///
14 | /// Slot 0 and 1 mapped. 128 kB mirrors.
15 | ///
16 | Slot01 = 2,
17 |
18 | ///
19 | /// All slots mapped. 256 kB mirrors.
20 | ///
21 | All = 3
22 | }
23 |
--------------------------------------------------------------------------------
/GCDTool/Wram/WramAMapping.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool.Wram;
2 |
3 | ///
4 | /// Specifies the mapping of TWL wram A in memory.
5 | ///
6 | readonly record struct WramAMapping
7 | {
8 | private const uint WRAM_START_ADDRESS = 0x03000000u;
9 | private const uint WRAM_END_ADDRESS = 0x04000000u;
10 |
11 | private readonly uint _startOffset;
12 | private readonly uint _endOffset;
13 |
14 | ///
15 | /// The WRAM A slots that are mapped and mirrored in memory.
16 | ///
17 | public WramAMappedSlots MappedSlots { get; }
18 |
19 | ///
20 | /// The start address of the WRAM A area in memory.
21 | ///
22 | public uint StartAddress => WRAM_START_ADDRESS + (_startOffset << 16);
23 |
24 | ///
25 | /// The end address of the WRAM A area in memory.
26 | ///
27 | public uint EndAddress => WRAM_START_ADDRESS + (_endOffset << 16);
28 |
29 | public WramAMapping(uint startAddress, uint length, WramAMappedSlots mappedSlots)
30 | {
31 | if (startAddress < WRAM_START_ADDRESS)
32 | {
33 | throw new ArgumentException(
34 | $"Start address (0x{startAddress:X8}) must be at least (0x{WRAM_START_ADDRESS:X8}).", nameof(startAddress));
35 | }
36 | if ((startAddress & 0xFFFF) != 0)
37 | {
38 | throw new ArgumentException("Start address must be a multiple of 64 kB.", nameof(startAddress));
39 | }
40 | if ((length & 0xFFFF) != 0)
41 | {
42 | throw new ArgumentException("Length must be a multiple of 64 kB.", nameof(length));
43 | }
44 | uint endAddress = startAddress + length;
45 | if (endAddress > WRAM_END_ADDRESS)
46 | {
47 | throw new ArgumentException(
48 | $"Length is too large. Resulting end address (0x{endAddress:X8}) must be at most (0x{WRAM_END_ADDRESS:X8}).",
49 | nameof(length));
50 | }
51 |
52 | _startOffset = (startAddress - WRAM_START_ADDRESS) >> 16;
53 | _endOffset = (endAddress - WRAM_START_ADDRESS) >> 16;
54 | MappedSlots = mappedSlots;
55 | }
56 |
57 | public static explicit operator uint(WramAMapping wramAMapping)
58 | {
59 | return (wramAMapping._startOffset << 4)
60 | | ((uint)wramAMapping.MappedSlots << 12)
61 | | (wramAMapping._endOffset << 20);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/GCDTool/Wram/WramAMaster.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool.Wram;
2 |
3 | ///
4 | /// The master of a TWL wram A block.
5 | ///
6 | enum WramAMaster
7 | {
8 | Arm9 = 0,
9 | Arm7 = 1
10 | }
--------------------------------------------------------------------------------
/GCDTool/Wram/WramASlot.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool.Wram;
2 |
3 | ///
4 | /// Specifies a slot of TWL wram A.
5 | ///
6 | enum WramASlot
7 | {
8 | Slot0 = 0,
9 | Slot1 = 1,
10 | Slot2 = 2,
11 | Slot3 = 3
12 | }
--------------------------------------------------------------------------------
/GCDTool/Wram/WramBBlockMapping.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool.Wram;
2 |
3 | ///
4 | /// Specifies the mapping of TWL wram B block.
5 | ///
6 | readonly record struct WramBBlockMapping(WramBMaster Master, WramBCSlot Slot, bool Enable)
7 | {
8 | public static explicit operator byte(WramBBlockMapping wramBBlockMapping)
9 | {
10 | return (byte)(((byte)wramBBlockMapping.Master & 3)
11 | | (((byte)wramBBlockMapping.Slot & 7) << 2)
12 | | (wramBBlockMapping.Enable ? 0x80 : 0));
13 | }
14 | }
--------------------------------------------------------------------------------
/GCDTool/Wram/WramBCMappedSlots.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool.Wram;
2 |
3 | ///
4 | /// Specifies which TWL wram B or C slots are mapped in memory.
5 | ///
6 | enum WramBCMappedSlots
7 | {
8 | ///
9 | /// Only slot 0 mapped. 32 kB mirrors.
10 | ///
11 | Slot0 = 0,
12 |
13 | ///
14 | /// Only slot 0 and 1 mapped. 64 kB mirrors.
15 | ///
16 | Slot01 = 1,
17 |
18 | ///
19 | /// Only slot 0, 1, 2 and 3 mapped. 128 kB mirrors.
20 | ///
21 | Slot0123 = 2,
22 |
23 | ///
24 | /// All slots mapped. 256 kB mirrors.
25 | ///
26 | All = 3
27 | }
--------------------------------------------------------------------------------
/GCDTool/Wram/WramBCMapping.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool.Wram;
2 |
3 | ///
4 | /// Specifies the mapping of TWL wram B or C in memory.
5 | ///
6 | readonly record struct WramBCMapping
7 | {
8 | private const uint WRAM_START_ADDRESS = 0x03000000u;
9 | private const uint WRAM_END_ADDRESS = 0x04000000u;
10 |
11 | private readonly uint _startOffset;
12 | private readonly uint _endOffset;
13 |
14 | ///
15 | /// The WRAM A slots that are mapped and mirrored in memory.
16 | ///
17 | public WramBCMappedSlots MappedSlots { get; }
18 |
19 | ///
20 | /// The start address of the WRAM B or C area in memory.
21 | ///
22 | public uint StartAddress => WRAM_START_ADDRESS + (_startOffset << 15);
23 |
24 | ///
25 | /// The end address of the WRAM B or C area in memory.
26 | ///
27 | public uint EndAddress => WRAM_START_ADDRESS + (_endOffset << 15);
28 |
29 | public WramBCMapping(uint startAddress, uint length, WramBCMappedSlots mappedSlots)
30 | {
31 | if (startAddress < WRAM_START_ADDRESS)
32 | {
33 | throw new ArgumentException(
34 | $"Start address (0x{startAddress:X8}) must be at least (0x{WRAM_START_ADDRESS:X8}).", nameof(startAddress));
35 | }
36 | if ((startAddress & 0x7FFF) != 0)
37 | {
38 | throw new ArgumentException("Start address must be a multiple of 32 kB.", nameof(startAddress));
39 | }
40 | if ((length & 0x7FFF) != 0)
41 | {
42 | throw new ArgumentException("Length must be a multiple of 32 kB.", nameof(length));
43 | }
44 | uint endAddress = startAddress + length;
45 | if (endAddress > WRAM_END_ADDRESS)
46 | {
47 | throw new ArgumentException(
48 | $"Length is too large. Resulting end address (0x{endAddress:X8}) must be at most (0x{WRAM_END_ADDRESS:X8}).",
49 | nameof(length));
50 | }
51 |
52 | _startOffset = (startAddress - WRAM_START_ADDRESS) >> 15;
53 | _endOffset = (endAddress - WRAM_START_ADDRESS) >> 15;
54 | MappedSlots = mappedSlots;
55 | }
56 |
57 | public static explicit operator uint(WramBCMapping wramBCMapping)
58 | {
59 | return (wramBCMapping._startOffset << 3)
60 | | ((uint)wramBCMapping.MappedSlots << 12)
61 | | (wramBCMapping._endOffset << 19);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/GCDTool/Wram/WramBCSlot.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool.Wram;
2 |
3 | ///
4 | /// Specifies a slot of TWL wram B or C.
5 | ///
6 | enum WramBCSlot
7 | {
8 | Slot0 = 0,
9 | Slot1 = 1,
10 | Slot2 = 2,
11 | Slot3 = 3,
12 | Slot4 = 4,
13 | Slot5 = 5,
14 | Slot6 = 6,
15 | Slot7 = 7
16 | }
--------------------------------------------------------------------------------
/GCDTool/Wram/WramBMaster.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool.Wram;
2 |
3 | ///
4 | /// The master of a TWL wram B block.
5 | ///
6 | enum WramBMaster
7 | {
8 | Arm9 = 0,
9 | Arm7 = 1,
10 | DspCode = 2
11 | }
--------------------------------------------------------------------------------
/GCDTool/Wram/WramCBlockMapping.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool.Wram;
2 |
3 | ///
4 | /// Specifies the mapping of TWL wram C block.
5 | ///
6 | readonly record struct WramCBlockMapping(WramCMaster Master, WramBCSlot Slot, bool Enable)
7 | {
8 | public static explicit operator byte(WramCBlockMapping wramCBlockMapping)
9 | {
10 | return (byte)(((byte)wramCBlockMapping.Master & 3)
11 | | (((byte)wramCBlockMapping.Slot & 7) << 2)
12 | | (wramCBlockMapping.Enable ? 0x80 : 0));
13 | }
14 | }
--------------------------------------------------------------------------------
/GCDTool/Wram/WramCMaster.cs:
--------------------------------------------------------------------------------
1 | namespace GCDTool.Wram;
2 |
3 | ///
4 | /// The master of a TWL wram C block.
5 | ///
6 | enum WramCMaster
7 | {
8 | Arm9 = 0,
9 | Arm7 = 1,
10 | DspData = 2
11 | }
--------------------------------------------------------------------------------
/GCDTool/Wram/WramConfig.cs:
--------------------------------------------------------------------------------
1 | using GCDTool.IO;
2 |
3 | namespace GCDTool.Wram;
4 |
5 | ///
6 | /// Class representing the initial wram configuration.
7 | ///
8 | sealed class WramConfig
9 | {
10 | ///
11 | /// The block mapping of TWL wram A.
12 | ///
13 | public WramABlockMapping[] TwlWramABlockMapping { get; } = new WramABlockMapping[4];
14 |
15 | ///
16 | /// The block mapping of TWL wram B.
17 | ///
18 | public WramBBlockMapping[] TwlWramBBlockMapping { get; } = new WramBBlockMapping[8];
19 |
20 | ///
21 | /// The block mapping of TWL wram C.
22 | ///
23 | public WramCBlockMapping[] TwlWramCBlockMapping { get; } = new WramCBlockMapping[8];
24 |
25 | ///
26 | /// The mapping of TWL wram A on the ARM9.
27 | ///
28 | public WramAMapping Arm9TwlWramAMapping { get; set; }
29 |
30 | ///
31 | /// The mapping of TWL wram B on the ARM9.
32 | ///
33 | public WramBCMapping Arm9TwlWramBMapping { get; set; }
34 |
35 | ///
36 | /// The mapping of TWL wram C on the ARM9.
37 | ///
38 | public WramBCMapping Arm9TwlWramCMapping { get; set; }
39 |
40 | ///
41 | /// The mapping of TWL wram A on the ARM7.
42 | ///
43 | public WramAMapping Arm7TwlWramAMapping { get; set; }
44 |
45 | ///
46 | /// The mapping of TWL wram B on the ARM7.
47 | ///
48 | public WramBCMapping Arm7TwlWramBMapping { get; set; }
49 |
50 | ///
51 | /// The mapping of TWL wram C on the ARM7.
52 | ///
53 | public WramBCMapping Arm7TwlWramCMapping { get; set; }
54 |
55 | ///
56 | /// For each TWL wram A block when locked, or otherwise.
57 | ///
58 | public bool[] TwlWramABlockLocked { get; } = new bool[4];
59 |
60 | ///
61 | /// For each TWL wram B block when locked, or otherwise.
62 | ///
63 | public bool[] TwlWramBBlockLocked { get; } = new bool[8];
64 |
65 | ///
66 | /// For each TWL wram C block when locked, or otherwise.
67 | ///
68 | public bool[] TwlWramCBlockLocked { get; } = new bool[8];
69 |
70 | ///
71 | /// The mapping of nitro wram.
72 | ///
73 | public NtrWramMaster[] NtrWramMapping { get; } = new NtrWramMaster[2];
74 |
75 | ///
76 | /// The mapping of vram C.
77 | ///
78 | public byte VramCMapping { get; set; }
79 |
80 | ///
81 | /// The mapping of vram D.
82 | ///
83 | public byte VramDMapping { get; set; }
84 |
85 | ///
86 | /// Writes the wram configuration to the given span.
87 | ///
88 | ///
89 | public void Write(Span destination)
90 | {
91 | for (int i = 0; i < 4; i++)
92 | {
93 | destination[i] = (byte)TwlWramABlockMapping[i];
94 | }
95 | for (int i = 0; i < 8; i++)
96 | {
97 | destination[4 + i] = (byte)TwlWramBBlockMapping[i];
98 | destination[12 + i] = (byte)TwlWramCBlockMapping[i];
99 | }
100 | IOUtil.WriteU32Le(destination[0x14..], (uint)Arm9TwlWramAMapping);
101 | IOUtil.WriteU32Le(destination[0x18..], (uint)Arm9TwlWramBMapping);
102 | IOUtil.WriteU32Le(destination[0x1C..], (uint)Arm9TwlWramCMapping);
103 | IOUtil.WriteU32Le(destination[0x20..], (uint)Arm7TwlWramAMapping);
104 | IOUtil.WriteU32Le(destination[0x24..], (uint)Arm7TwlWramBMapping);
105 | IOUtil.WriteU32Le(destination[0x28..], (uint)Arm7TwlWramCMapping);
106 | destination[0x2C] = CreateLockMask(TwlWramABlockLocked);
107 | destination[0x2D] = CreateLockMask(TwlWramBBlockLocked);
108 | destination[0x2E] = CreateLockMask(TwlWramCBlockLocked);
109 | destination[0x2F] = (byte)(((byte)NtrWramMapping[0] & 1) | (((byte)NtrWramMapping[1] & 1) << 1)
110 | | (VramCMapping & 7) << 2 | (VramDMapping & 7) << 5);
111 | }
112 |
113 | private byte CreateLockMask(ReadOnlySpan locks)
114 | {
115 | byte wramLocks = 0;
116 | for (int i = 0; i < locks.Length; i++)
117 | {
118 | if (locks[i])
119 | {
120 | wramLocks |= (byte)(1 << i);
121 | }
122 | }
123 |
124 | return wramLocks;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/GCDTool/Wram/WramConfigJsonReader.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Text.Json;
3 | using System.Text.Json.Serialization;
4 |
5 | namespace GCDTool.Wram;
6 |
7 | ///
8 | /// Class for reading from a JSON file.
9 | ///
10 | sealed class WramConfigJsonReader
11 | {
12 | private const int WRAM_A_BLOCK_COUNT = 4;
13 | private const int WRAM_BC_BLOCK_COUNT = 8;
14 |
15 | private const string MASTER_ARM9 = "arm9";
16 | private const string MASTER_ARM7 = "arm7";
17 | private const string MASTER_DSP = "dsp";
18 |
19 | private const string SLOT_0 = "0";
20 | private const string SLOT_01 = "01";
21 | private const string SLOT_0123 = "0123";
22 | private const string SLOT_01234567 = "01234567";
23 | private const string SLOT_ALL = "all";
24 |
25 | ///
26 | /// Reads a from the given .
27 | ///
28 | /// The JSON string containing the WRAM configuration.
29 | /// The constructed .
30 | public WramConfig ReadWramConfigFromJson(string jsonString)
31 | {
32 | var wramConfig = new WramConfig();
33 | var json = JsonDocument.Parse(jsonString).RootElement;
34 | ParseWramAConfig(wramConfig, json.GetProperty("wramA"));
35 | ParseWramBConfig(wramConfig, json.GetProperty("wramB"));
36 | ParseWramCConfig(wramConfig, json.GetProperty("wramC"));
37 |
38 | var ntrWram = json.GetProperty("ntrWram");
39 | if (ntrWram.GetArrayLength() != 2)
40 | {
41 | throw new InvalidDataException("'ntrWram' array should have exactly 2 items.");
42 | }
43 |
44 | wramConfig.NtrWramMapping[0] = ParseNtrWramMapping(ntrWram[0].GetString());
45 | wramConfig.NtrWramMapping[1] = ParseNtrWramMapping(ntrWram[1].GetString());
46 | wramConfig.VramCMapping = json.GetProperty("vramC").GetByte();
47 | wramConfig.VramDMapping = json.GetProperty("vramD").GetByte();
48 | return wramConfig;
49 | }
50 |
51 | private void ParseWramAConfig(WramConfig wramConfig, JsonElement wramAJson)
52 | {
53 | wramConfig.Arm9TwlWramAMapping = wramAJson.TryGetProperty("arm9", out var arm9)
54 | ? ParseWramAMapping(arm9)
55 | : new WramAMapping(0x03000000, 0, WramAMappedSlots.Slot0); // unmapped
56 |
57 | wramConfig.Arm7TwlWramAMapping = wramAJson.TryGetProperty("arm7", out var arm7)
58 | ? ParseWramAMapping(arm7)
59 | : new WramAMapping(0x03000000, 0, WramAMappedSlots.Slot0); // unmapped
60 |
61 | var blocks = wramAJson.GetProperty("blocks");
62 | if (blocks.GetArrayLength() != WRAM_A_BLOCK_COUNT)
63 | {
64 | throw new InvalidDataException($"Wram A 'blocks' array should have exactly {WRAM_A_BLOCK_COUNT} items.");
65 | }
66 | for (int i = 0; i < WRAM_A_BLOCK_COUNT; i++)
67 | {
68 | wramConfig.TwlWramABlockMapping[i] = ParseWramABlockMapping(
69 | blocks[i], out wramConfig.TwlWramABlockLocked[i]);
70 | }
71 | }
72 |
73 | private void ParseWramBConfig(WramConfig wramConfig, JsonElement wramBJson)
74 | {
75 | wramConfig.Arm9TwlWramBMapping = wramBJson.TryGetProperty("arm9", out var arm9)
76 | ? ParseWramBCMapping(arm9)
77 | : new WramBCMapping(0x03000000, 0, WramBCMappedSlots.Slot0); // unmapped
78 |
79 | wramConfig.Arm7TwlWramBMapping = wramBJson.TryGetProperty("arm7", out var arm7)
80 | ? ParseWramBCMapping(arm7)
81 | : new WramBCMapping(0x03000000, 0, WramBCMappedSlots.Slot0); // unmapped
82 |
83 | var blocks = wramBJson.GetProperty("blocks");
84 | if (blocks.GetArrayLength() != WRAM_BC_BLOCK_COUNT)
85 | {
86 | throw new InvalidDataException($"Wram B 'blocks' array should have exactly {WRAM_BC_BLOCK_COUNT} items.");
87 | }
88 | for (int i = 0; i < WRAM_BC_BLOCK_COUNT; i++)
89 | {
90 | wramConfig.TwlWramBBlockMapping[i] = ParseWramBBlockMapping(
91 | blocks[i], out wramConfig.TwlWramBBlockLocked[i]);
92 | }
93 | }
94 |
95 | private void ParseWramCConfig(WramConfig wramConfig, JsonElement wramCJson)
96 | {
97 | wramConfig.Arm9TwlWramCMapping = wramCJson.TryGetProperty("arm9", out var arm9)
98 | ? ParseWramBCMapping(arm9)
99 | : new WramBCMapping(0x03000000, 0, WramBCMappedSlots.Slot0); // unmapped
100 |
101 | wramConfig.Arm7TwlWramCMapping = wramCJson.TryGetProperty("arm7", out var arm7)
102 | ? ParseWramBCMapping(arm7)
103 | : new WramBCMapping(0x03000000, 0, WramBCMappedSlots.Slot0); // unmapped
104 |
105 | var blocks = wramCJson.GetProperty("blocks");
106 | if (blocks.GetArrayLength() != WRAM_BC_BLOCK_COUNT)
107 | {
108 | throw new InvalidDataException($"Wram C 'blocks' array should have exactly {WRAM_BC_BLOCK_COUNT} items.");
109 | }
110 | for (int i = 0; i < WRAM_BC_BLOCK_COUNT; i++)
111 | {
112 | wramConfig.TwlWramCBlockMapping[i] = ParseWramCBlockMapping(
113 | blocks[i], out wramConfig.TwlWramCBlockLocked[i]);
114 | }
115 | }
116 |
117 | private WramAMapping ParseWramAMapping(JsonElement mappingJson)
118 | {
119 | var wramMapping = mappingJson.Deserialize()
120 | ?? throw new InvalidDataException("Could not parse wram A mapping.");
121 | return new(
122 | ParseHexString(wramMapping.Start),
123 | ParseHexString(wramMapping.Length),
124 | wramMapping.Slots switch
125 | {
126 | SLOT_0 => WramAMappedSlots.Slot0,
127 | SLOT_01 => WramAMappedSlots.Slot01,
128 | SLOT_0123 or SLOT_ALL => WramAMappedSlots.All,
129 | _ => throw new InvalidDataException("Invalid value specified for wram A 'slots'.")
130 | });
131 | }
132 |
133 | private WramBCMapping ParseWramBCMapping(JsonElement mappingJson)
134 | {
135 | var wramMapping = mappingJson.Deserialize()
136 | ?? throw new InvalidDataException("Could not parse wram B or C mapping.");
137 | return new(
138 | ParseHexString(wramMapping.Start),
139 | ParseHexString(wramMapping.Length),
140 | wramMapping.Slots switch
141 | {
142 | SLOT_0 => WramBCMappedSlots.Slot0,
143 | SLOT_01 => WramBCMappedSlots.Slot01,
144 | SLOT_0123 => WramBCMappedSlots.Slot0123,
145 | SLOT_01234567 or SLOT_ALL => WramBCMappedSlots.All,
146 | _ => throw new InvalidDataException("Invalid value specified for wram B or C 'slots'.")
147 | });
148 | }
149 |
150 | private WramABlockMapping ParseWramABlockMapping(JsonElement blockMappingJson, out bool locked)
151 | {
152 | var blockMapping = blockMappingJson.Deserialize()
153 | ?? throw new InvalidDataException("Could not parse wram A block mapping.");
154 | if (blockMapping.Slot >= WRAM_A_BLOCK_COUNT)
155 | {
156 | throw new InvalidDataException("Invalid slot specified for wram A block mapping.");
157 | }
158 | locked = blockMapping.Locked;
159 | return new(
160 | blockMapping.Master switch
161 | {
162 | MASTER_ARM7 => WramAMaster.Arm7,
163 | MASTER_ARM9 => WramAMaster.Arm9,
164 | _ => throw new InvalidDataException("Invalid master specified for wram A block mapping.")
165 | },
166 | (WramASlot)blockMapping.Slot,
167 | blockMapping.Enabled);
168 | }
169 |
170 | private WramBBlockMapping ParseWramBBlockMapping(JsonElement blockMappingJson, out bool locked)
171 | {
172 | var blockMapping = blockMappingJson.Deserialize()
173 | ?? throw new InvalidDataException("Could not parse wram B block mapping.");
174 | if (blockMapping.Slot >= WRAM_BC_BLOCK_COUNT)
175 | {
176 | throw new InvalidDataException("Invalid slot specified for wram B block mapping.");
177 | }
178 | locked = blockMapping.Locked;
179 | return new(
180 | blockMapping.Master switch
181 | {
182 | MASTER_ARM9 => WramBMaster.Arm9,
183 | MASTER_ARM7 => WramBMaster.Arm7,
184 | MASTER_DSP => WramBMaster.DspCode,
185 | _ => throw new InvalidDataException("Invalid master specified for wram B block mapping.")
186 | },
187 | (WramBCSlot)blockMapping.Slot,
188 | blockMapping.Enabled);
189 | }
190 |
191 | private WramCBlockMapping ParseWramCBlockMapping(JsonElement blockMappingJson, out bool locked)
192 | {
193 | var blockMapping = blockMappingJson.Deserialize()
194 | ?? throw new InvalidDataException("Could not parse wram C block mapping.");
195 | if (blockMapping.Slot >= WRAM_BC_BLOCK_COUNT)
196 | {
197 | throw new InvalidDataException("Invalid slot specified for wram C block mapping.");
198 | }
199 | locked = blockMapping.Locked;
200 | return new(
201 | blockMapping.Master switch
202 | {
203 | MASTER_ARM9 => WramCMaster.Arm9,
204 | MASTER_ARM7 => WramCMaster.Arm7,
205 | MASTER_DSP => WramCMaster.DspData,
206 | _ => throw new InvalidDataException("Invalid master specified for wram C block mapping.")
207 | },
208 | (WramBCSlot)blockMapping.Slot,
209 | blockMapping.Enabled);
210 | }
211 |
212 | private NtrWramMaster ParseNtrWramMapping(string? mapping) => mapping switch
213 | {
214 | MASTER_ARM9 => NtrWramMaster.Arm9,
215 | MASTER_ARM7 => NtrWramMaster.Arm7,
216 | _ => throw new InvalidDataException("Invalid master specified for ntr wram mapping.")
217 | };
218 |
219 | private uint ParseHexString(string hexString)
220 | {
221 | if (new UInt32Converter().ConvertFromString(hexString) is not uint value)
222 | {
223 | throw new InvalidDataException("Invalid start address or length specified.");
224 | }
225 | return value;
226 | }
227 |
228 | private sealed class JsonWramMapping
229 | {
230 | [JsonPropertyName("start")]
231 | public string Start { get; set; } = "0x03000000";
232 |
233 | [JsonPropertyName("length")]
234 | public string Length { get; set; } = "0";
235 |
236 | [JsonPropertyName("slots")]
237 | public string Slots { get; set; } = SLOT_0;
238 | }
239 |
240 | private sealed class JsonBlockMapping
241 | {
242 | [JsonPropertyName("master")]
243 | public string Master { get; set; } = MASTER_ARM9;
244 |
245 | [JsonPropertyName("slot")]
246 | public uint Slot { get; set; } = 0;
247 |
248 | [JsonPropertyName("enabled")]
249 | public bool Enabled { get; set; } = true;
250 |
251 | [JsonPropertyName("locked")]
252 | public bool Locked { get; set; } = false;
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # GCDTool
2 | Tool to create a GCD rom for DSi ntrboot.
3 |
4 | ## Usage
5 | `GCDTool --arm9 arm9.elf --arm7 arm7.elf --wram wramConfig.json --rsakey gcdKey.der output.gcd`
6 |
7 | ### Arguments
8 | #### Mandatory
9 | - `--arm9` specifies the arm9 elf file;
10 | - `--arm7` specifies the arm7 elf file;
11 | - `--wram` specifies the wram configuration json file (see below);
12 | - `--rsakey` specifies the der file with the RSA private key to use.
13 |
14 | 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.
15 |
16 | #### Optional
17 | - `--gamecode` specifies the 4 character game code to use (default: ####);
18 | - `--arm9-67-mhz` runs the arm9 at 67 MHz instead of 134 MHz.
19 |
20 | ### Wram configuration JSON file
21 | Specifies the initial wram configuration to use.
22 |
23 | #### Example
24 | ```json
25 | {
26 | "wramA": {
27 | "arm7": { "start": "0x03800000", "length": "0x40000", "slots": "all" },
28 | "blocks": [
29 | { "master": "arm7", "slot": 0 },
30 | { "master": "arm7", "slot": 1 },
31 | { "master": "arm7", "slot": 2 },
32 | { "master": "arm7", "slot": 3 }
33 | ]
34 | },
35 | "wramB": {
36 | "arm9": { "start": "0x03800000", "length": "0x40000", "slots": "all" },
37 | "blocks": [
38 | { "master": "arm9", "slot": 0 },
39 | { "master": "arm9", "slot": 1 },
40 | { "master": "arm9", "slot": 2 },
41 | { "master": "arm9", "slot": 3 },
42 | { "master": "arm9", "slot": 4 },
43 | { "master": "arm9", "slot": 5 },
44 | { "master": "arm9", "slot": 6 },
45 | { "master": "arm9", "slot": 7 }
46 | ]
47 | },
48 | "wramC": {
49 | "arm9": { "start": "0x03840000", "length": "0x40000", "slots": "all" },
50 | "blocks": [
51 | { "master": "arm9", "slot": 0 },
52 | { "master": "arm9", "slot": 1 },
53 | { "master": "arm9", "slot": 2 },
54 | { "master": "arm9", "slot": 3 },
55 | { "master": "arm9", "slot": 4 },
56 | { "master": "arm9", "slot": 5 },
57 | { "master": "arm9", "slot": 6 },
58 | { "master": "arm9", "slot": 7 }
59 | ]
60 | },
61 | "ntrWram" : [ "arm7", "arm7" ],
62 | "vramC": 7,
63 | "vramD": 7
64 | }
65 | ```
66 |
67 | #### TWL wram mapping
68 | 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).
69 |
70 | #### TWL wram block mapping
71 | For each block of TWL wram the following properties are available:
72 | - `"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;
73 | - `"slot"` specifies the slot the block is mapped to;
74 | - `"enabled"` specifies whether the block is enabled. This defaults to `true` and can be omitted;
75 | - `"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.
76 |
77 | When `"enabled"` is set to `false`, `"master"` and `"slot"` can be omitted.
78 |
79 | #### NTR wram mapping
80 | Specifies for the two NTR wram blocks whether they are mapped to `"arm9"` or `"arm7"`.
81 |
82 | #### Vram mapping
83 | 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.
84 |
85 | ## Booting the GCD rom
86 | 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`).
87 |
88 | 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.
89 |
--------------------------------------------------------------------------------