1 module nes.ines;
2 
3 import std.stdio;
4 
5 import nes.cartridge;
6 
7 enum iNESFileMagic = 0x1a53454e;
8 
9 align(1) struct iNESFileHeader {
10     uint     magic;    // iNES magic number
11     ubyte    numPRG;   // number of PRG-ROM banks (16KB each)
12     ubyte    numCHR;   // number of CHR-ROM banks (8KB each)
13     ubyte    control1; // control bits
14     ubyte    control2; // control bits
15     ubyte    numRAM;   // PRG-RAM size (x 8KB)
16     ubyte[7] padding;  // unused padding
17 }
18 
19 class NesFileException : Exception
20 {
21     import std.exception : basicExceptionCtors;
22     
23     mixin basicExceptionCtors;
24 }
25 
26 // LoadNESFile reads an iNES file (.nes) and returns a Cartridge on success.
27 // http://wiki.nesdev.com/w/index.php/INES
28 // http://nesdev.com/NESDoc.pdf (page 28)
29 Cartridge LoadNESFile(string path) {
30     // open file
31     auto file = File(path);
32 
33     // read file header
34     iNESFileHeader[1] headers;
35     file.rawRead(headers);
36 
37     // verify header magic number
38     if (headers[0].magic != iNESFileMagic) {
39         throw new NesFileException("invalid .nes file");
40     }
41 
42     // mapper type
43     auto mapper1 = cast(ubyte)(headers[0].control1 >> 4);
44     auto mapper2 = cast(ubyte)(headers[0].control2 >> 4);
45     auto mapper = cast(ubyte)(mapper1 | mapper2 << 4);
46 
47     // mirroring type
48     auto mirror1 = cast(ubyte)(headers[0].control1 & 1);
49     auto mirror2 = cast(ubyte)((headers[0].control1 >> 3) & 1);
50     auto mirror = cast(ubyte)(mirror1 | mirror2 << 1);
51 
52     // battery-backed RAM
53     auto battery = cast(ubyte)((headers[0].control1 >> 1) & 1);
54 
55     // read trainer if present (unused)
56     if ((headers[0].control1 & 4) == 4) {
57         auto trainer = new ubyte[512];
58         file.rawRead(trainer);
59     }
60 
61     // read prg-rom bank(s)
62     auto prg = new ubyte[cast(int)headers[0].numPRG * 16384];
63     file.rawRead(prg);
64 
65     // read chr-rom bank(s)
66     bool chrRAM;
67     ubyte[] chr;
68 
69     if (headers[0].numCHR > 0) {
70         chr = new ubyte[cast(int)headers[0].numCHR * 8192];
71         file.rawRead(chr);
72     } else {
73         // provide chr-ram if not in file
74         chr = new ubyte[8192];
75         chrRAM = true;
76     }
77 
78     // success
79     return new Cartridge(prg, chr, mapper, mirror, battery, chrRAM);
80 }