Skip to content

Commit f75bc21

Browse files
committed
Add NES cart extraction
1 parent 8844632 commit f75bc21

3 files changed

Lines changed: 301 additions & 0 deletions

File tree

ExtractionTool/Features/MainFeature.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,11 @@ private void ExtractFile(string file)
230230
nex.Extract(OutputPath, Debug);
231231
break;
232232

233+
// NES Cart
234+
case NESCart nes:
235+
nes.Extract(OutputPath, Debug);
236+
break;
237+
233238
// PAK
234239
case PAK pak:
235240
pak.Extract(OutputPath, Debug);

README.MD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ Options:
6868
| Microsoft LZ-compressed files | KWAJ, QBasic, and SZDD variants |
6969
| MoPaQ game data archive (MPQ) | Windows only |
7070
| New Exectuable | Embedded archives and executables in the overlay and Wise installer |
71+
| Nintendo Entertainment System (NES) Cart Image | Header, Trainer data, PRG-ROM, CHR-ROM, Unheadered ROM, and PlayChoice-10 INST-ROM |
7172
| NovaLogic Game Archive Format (PFF) | |
7273
| PKZIP and derived files (ZIP, etc.) | .NET Framework 4.6.2 and greater |
7374
| Portable Executable | Embedded archives and executables in the resources and overlay, CExe-packed data, SFX archives (7-zip, PKZIP, and RAR), and Wise installer |
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
using System;
2+
using System.IO;
3+
using SabreTools.Data.Models.NES;
4+
using SabreTools.IO.Extensions;
5+
6+
namespace SabreTools.Serialization.Wrappers
7+
{
8+
public partial class NESCart : IExtractable
9+
{
10+
/// <inheritdoc/>
11+
public bool Extract(string outputDirectory, bool includeDebug)
12+
{
13+
// Get the base path
14+
string baseFilename = Filename is null
15+
? Guid.NewGuid().ToString()
16+
: Path.GetFileNameWithoutExtension(Filename);
17+
string basePath = Path.Combine(outputDirectory, baseFilename);
18+
19+
// Check if any data was extracted successfully
20+
bool success = false;
21+
22+
// Header data
23+
if (Header is not null)
24+
{
25+
string headerPath = $"{basePath}.hdr";
26+
if (includeDebug) Console.WriteLine($"Attempting to extract header data to {headerPath}");
27+
28+
// Try to write the data
29+
try
30+
{
31+
// Open the output file for writing
32+
using var fs = File.Open(headerPath, FileMode.Create, FileAccess.Write, FileShare.None);
33+
34+
// Bytes 0-3
35+
fs.Write(Header.IdentificationString);
36+
fs.Flush();
37+
38+
// Byte 4
39+
fs.Write(Header.PrgRomSize);
40+
fs.Flush();
41+
42+
// Byte 5
43+
fs.Write(Header.ChrRomSize);
44+
fs.Flush();
45+
46+
// Byte 6
47+
byte byte6 = 0;
48+
byte6 |= (byte)NametableArrangement;
49+
if (BatteryBackedPrgRam)
50+
byte6 |= 0b00000010;
51+
if (TrainerPresent)
52+
byte6 |= 0b00000100;
53+
if (AlternativeNametableLayout)
54+
byte6 |= 0b00001000;
55+
byte6 |= (byte)(Header.MapperLowerNibble << 4);
56+
fs.Write(byte6);
57+
fs.Flush();
58+
59+
// Byte 7
60+
byte byte7 = 0;
61+
byte7 |= (byte)ConsoleType;
62+
if (NES20)
63+
byte7 |= 0b00001000;
64+
byte7 |= (byte)(Header.MapperUpperNibble << 4);
65+
fs.Write(byte7);
66+
fs.Flush();
67+
68+
if (Header is Header1 header1)
69+
{
70+
// Byte 8
71+
fs.Write(header1.PrgRamSize);
72+
fs.Flush();
73+
74+
// Byte 9
75+
fs.Write((byte)header1.TVSystem);
76+
fs.Flush();
77+
78+
// Byte 10
79+
byte byte10 = 0;
80+
byte10 |= (byte)TVSystemExtended;
81+
byte10 |= (byte)(header1.Byte10ReservedBits23 << 2);
82+
if (PrgRamPresent)
83+
byte10 |= 0b00010000;
84+
if (HasBusConflicts)
85+
byte10 |= 0b00100000;
86+
byte10 |= (byte)(header1.Byte10ReservedBits67 << 6);
87+
fs.Write(byte10);
88+
fs.Flush();
89+
90+
// Bytes 11-15
91+
fs.Write(header1.Padding);
92+
fs.Flush();
93+
}
94+
else if (Header is Header2 header2)
95+
{
96+
// Byte 8
97+
byte byte8 = 0;
98+
byte8 |= header2.MapperMSB;
99+
byte8 |= (byte)(header2.Submapper << 4);
100+
fs.Write(byte8);
101+
fs.Flush();
102+
103+
// Byte 9
104+
byte byte9 = 0;
105+
byte9 |= header2.PrgRomSizeMSB;
106+
byte9 |= (byte)(header2.ChrRomSizeMSB << 4);
107+
fs.Write(byte9);
108+
fs.Flush();
109+
110+
// Byte 10
111+
byte byte10 = 0;
112+
byte10 |= header2.PrgRamShiftCount;
113+
byte10 |= (byte)(header2.PrgNvramEepromShiftCount << 4);
114+
fs.Write(byte10);
115+
fs.Flush();
116+
117+
// Byte 11
118+
byte byte11 = 0;
119+
byte11 |= header2.ChrRamShiftCount;
120+
byte11 |= (byte)(header2.ChrNvramShiftCount << 4);
121+
fs.Write(byte11);
122+
fs.Flush();
123+
124+
// Byte 12
125+
fs.Write((byte)header2.CPUPPUTiming);
126+
fs.Flush();
127+
128+
// Byte 13
129+
if (ConsoleType == ConsoleType.VSUnisystem)
130+
{
131+
byte byte13 = 0;
132+
byte13 |= (byte)header2.VsSystemType;
133+
byte13 |= (byte)((byte)header2.VsHardwareType << 4);
134+
fs.Write(byte13);
135+
fs.Flush();
136+
}
137+
else if (ConsoleType == ConsoleType.ExtendedConsoleType)
138+
{
139+
byte byte13 = 0;
140+
byte13 |= (byte)header2.ExtendedConsoleType;
141+
byte13 |= (byte)(header2.Byte13ReservedBits47 << 4);
142+
fs.Write(byte13);
143+
fs.Flush();
144+
}
145+
else
146+
{
147+
fs.Write(header2.Reserved13);
148+
fs.Flush();
149+
}
150+
151+
// Byte 14
152+
fs.Write(header2.MiscellaneousROMs);
153+
fs.Flush();
154+
155+
// Byte 15
156+
fs.Write((byte)DefaultExpansionDevice);
157+
fs.Flush();
158+
}
159+
else
160+
{
161+
if (includeDebug) Console.Error.WriteLine("Unknown header type, incomplete header data output");
162+
}
163+
164+
// Header extracted
165+
success = true;
166+
}
167+
catch (Exception ex)
168+
{
169+
if (includeDebug) Console.Error.WriteLine(ex);
170+
}
171+
}
172+
173+
// Trainer data
174+
if (Trainer.Length > 0)
175+
{
176+
string trainerPath = $"{basePath}.trn";
177+
if (includeDebug) Console.WriteLine($"Attempting to extract trainer data to {trainerPath}");
178+
179+
// Try to write the data
180+
try
181+
{
182+
// Open the output file for writing
183+
using var fs = File.Open(trainerPath, FileMode.Create, FileAccess.Write, FileShare.None);
184+
fs.Write(Trainer, 0, Trainer.Length);
185+
fs.Flush();
186+
187+
// Trainer extracted
188+
success = true;
189+
}
190+
catch (Exception ex)
191+
{
192+
if (includeDebug) Console.Error.WriteLine(ex);
193+
}
194+
}
195+
196+
// PRG-ROM data
197+
if (PrgRomData.Length > 0)
198+
{
199+
string prgRomPath = $"{basePath}.prg";
200+
if (includeDebug) Console.WriteLine($"Attempting to extract PRG-ROM data to {prgRomPath}");
201+
202+
// Try to write the data
203+
try
204+
{
205+
// Open the output file for writing
206+
using var fs = File.Open(prgRomPath, FileMode.Create, FileAccess.Write, FileShare.None);
207+
fs.Write(PrgRomData, 0, PrgRomData.Length);
208+
fs.Flush();
209+
210+
// PRG-ROM extracted
211+
success = true;
212+
}
213+
catch (Exception ex)
214+
{
215+
if (includeDebug) Console.Error.WriteLine(ex);
216+
}
217+
}
218+
219+
// CHR-ROM data
220+
if (ChrRomData.Length > 0)
221+
{
222+
string chrRomPath = $"{basePath}.chr";
223+
if (includeDebug) Console.WriteLine($"Attempting to extract CHR-ROM data to {chrRomPath}");
224+
225+
// Try to write the data
226+
try
227+
{
228+
// Open the output file for writing
229+
using var fs = File.Open(chrRomPath, FileMode.Create, FileAccess.Write, FileShare.None);
230+
fs.Write(ChrRomData, 0, ChrRomData.Length);
231+
fs.Flush();
232+
233+
// CHR-ROM extracted
234+
success = true;
235+
}
236+
catch (Exception ex)
237+
{
238+
if (includeDebug) Console.Error.WriteLine(ex);
239+
}
240+
}
241+
242+
// Headerless ROM data
243+
if (PrgRomData.Length > 0 && ChrRomData.Length > 0)
244+
{
245+
string unheaderedPath = $"{basePath}.unh";
246+
if (includeDebug) Console.WriteLine($"Attempting to extract unheadered ROM to {unheaderedPath}");
247+
248+
// Try to write the data
249+
try
250+
{
251+
// Open the output file for writing
252+
using var fs = File.Open(unheaderedPath, FileMode.Create, FileAccess.Write, FileShare.None);
253+
fs.Write(PrgRomData, 0, PrgRomData.Length);
254+
fs.Flush();
255+
fs.Write(ChrRomData, 0, ChrRomData.Length);
256+
fs.Flush();
257+
258+
// Unheadered ROM extracted
259+
success = true;
260+
}
261+
catch (Exception ex)
262+
{
263+
if (includeDebug) Console.Error.WriteLine(ex);
264+
}
265+
}
266+
267+
// INST-ROM data
268+
if (PlayChoiceInstRom.Length > 0)
269+
{
270+
string instRomPath = $"{basePath}.inst";
271+
if (includeDebug) Console.WriteLine($"Attempting to extract PlayChoice-10 INST-ROM data to {instRomPath}");
272+
273+
// Try to write the data
274+
try
275+
{
276+
// Open the output file for writing
277+
using var fs = File.Open(instRomPath, FileMode.Create, FileAccess.Write, FileShare.None);
278+
fs.Write(PlayChoiceInstRom, 0, PlayChoiceInstRom.Length);
279+
fs.Flush();
280+
281+
// PC10 INST-ROM extracted
282+
success = true;
283+
}
284+
catch (Exception ex)
285+
{
286+
if (includeDebug) Console.Error.WriteLine(ex);
287+
}
288+
}
289+
290+
// TODO: Add PlayChoice-10 PROM data extraction
291+
292+
return success;
293+
}
294+
}
295+
}

0 commit comments

Comments
 (0)