1 module nes.mapper1;
2 
3 import std.base64;
4 import std.conv;
5 import std.format;
6 import std.stdio;
7 
8 import nes.cartridge;
9 import nes.mapper;
10 import nes.memory;
11 
12 class Mapper1 : Mapper {
13     this(Cartridge cartridge) {
14         this.cart = cartridge;
15         this.shiftRegister = 0x10;
16         this.prgOffsets[1] = this.prgBankOffset(-1);
17     }
18 
19     void step() {
20     }
21 
22     ubyte read(ushort address) {
23         if (address < 0x2000) {
24             auto bank = address / 0x1000;
25             auto offset = address % 0x1000;
26             return this.cart.chr[this.chrOffsets[bank] + cast(int)offset];
27         }
28         else if (address >= 0x8000) {
29             address = cast(ushort)(address - 0x8000);
30             auto bank = address / 0x4000;
31             auto offset = address % 0x4000;
32             return this.cart.prg[this.prgOffsets[bank] + cast(int)offset];
33         }
34         else if (address >= 0x6000) {
35             return this.cart.sram[cast(int)address - 0x6000];
36         }
37         else {
38             throw new MapperException(format("unhandled mapper1 read at address: 0x%04X", address));
39         }
40     }
41 
42     void write(ushort address, ubyte value) {
43         if (address < 0x2000) {
44             auto bank = address / 0x1000;
45             auto offset = address % 0x1000;
46             this.cart.chr[this.chrOffsets[bank] + cast(int)offset] = value;
47         }
48         else if (address >= 0x8000) {
49             this.loadRegister(address, value);
50         }
51         else if (address >= 0x6000) {
52             this.cart.sram[cast(int)address - 0x6000] = value;
53         }
54         else {
55             throw new MapperException(format("unhandled mapper1 write at address: 0x%04X", address));
56         }
57     }
58 
59     void loadRegister(ushort address, ubyte value) {
60         if ((value & 0x80) == 0x80) {
61             this.shiftRegister = 0x10;
62             this.writeControl(this.control | 0x0C);
63         } else {
64             auto complete = (this.shiftRegister & 1) == 1;
65             this.shiftRegister >>= 1;
66             this.shiftRegister |= (value & 1) << 4;
67             if (complete) {
68                 this.writeRegister(address, this.shiftRegister);
69                 this.shiftRegister = 0x10;
70             }
71         }
72     }
73 
74     void writeRegister(ushort address, ubyte value) {
75         if (address <= 0x9FFF) {
76             this.writeControl(value);
77         }
78         else if (address <= 0xBFFF) {
79             this.writeCHRBank0(value);
80         }
81         else if (address <= 0xDFFF) {
82             this.writeCHRBank1(value);
83         }
84         else if (address <= 0xFFFF) {
85             this.writePRGBank(value);
86         }
87     }
88 
89     // Control (internal, $8000-$9FFF)
90     void writeControl(ubyte value) {
91         this.control = value;
92         this.chrMode = (value >> 4) & 1;
93         this.prgMode = (value >> 2) & 3;
94         auto mirror = value & 3;
95 
96         switch (mirror) {
97             case 0:
98                 this.cart.mirror = MirrorSingle0;
99                 break;
100             case 1:
101                 this.cart.mirror = MirrorSingle1;
102                 break;
103             case 2:
104                 this.cart.mirror = MirrorVertical;
105                 break;
106             case 3:
107                 this.cart.mirror = MirrorHorizontal;
108                 break;
109             default:
110                 break;
111         }
112 
113         this.updateOffsets();
114     }
115 
116     // CHR bank 0 (internal, $A000-$BFFF)
117     void writeCHRBank0(ubyte value) {
118         this.chrBank0 = value;
119         this.updateOffsets();
120     }
121 
122     // CHR bank 1 (internal, $C000-$DFFF)
123     void writeCHRBank1(ubyte value) {
124         this.chrBank1 = value;
125         this.updateOffsets();
126     }
127 
128     // PRG bank (internal, $E000-$FFFF)
129     void writePRGBank(ubyte value) {
130         this.prgBank = value & 0x0F;
131         this.updateOffsets();
132     }
133 
134     int prgBankOffset(int index) {
135         if (index >= 0x80) {
136             index -= 0x100;
137         }
138         index %= this.cart.prg.length / 0x4000;
139         auto offset = index * 0x4000;
140         if (offset < 0) {
141             offset += this.cart.prg.length;
142         }
143         return offset;
144     }
145 
146     int chrBankOffset(int index) {
147         if (index >= 0x80) {
148             index -= 0x100;
149         }
150         index %= this.cart.chr.length / 0x1000;
151         auto offset = index * 0x1000;
152         if (offset < 0) {
153             offset += this.cart.chr.length;
154         }
155         return offset;
156     }
157 
158     // PRG ROM bank mode (0, 1: switch 32 KB at $8000, ignoring low bit of bank number;
159     //                    2: fix first bank at $8000 and switch 16 KB bank at $C000;
160     //                    3: fix last bank at $C000 and switch 16 KB bank at $8000)
161     // CHR ROM bank mode (0: switch 8 KB at a time; 1: switch two separate 4 KB banks)
162     void updateOffsets() {
163         switch (this.prgMode) {
164             case 0, 1:
165                 this.prgOffsets[0] = this.prgBankOffset(cast(int)(this.prgBank & 0xFE));
166                 this.prgOffsets[1] = this.prgBankOffset(cast(int)(this.prgBank | 0x01));
167                 break;
168             case 2:
169                 this.prgOffsets[0] = 0;
170                 this.prgOffsets[1] = this.prgBankOffset(cast(int)this.prgBank);
171                 break;
172             case 3:
173                 this.prgOffsets[0] = this.prgBankOffset(cast(int)this.prgBank);
174                 this.prgOffsets[1] = this.prgBankOffset(-1);
175                 break;
176             default:
177                 break;
178         }
179 
180         switch (this.chrMode) {
181             case 0:
182                 this.chrOffsets[0] = this.chrBankOffset(cast(int)(this.chrBank0 & 0xFE));
183                 this.chrOffsets[1] = this.chrBankOffset(cast(int)(this.chrBank0 | 0x01));
184                 break;
185             case 1:
186                 this.chrOffsets[0] = this.chrBankOffset(cast(int)this.chrBank0);
187                 this.chrOffsets[1] = this.chrBankOffset(cast(int)this.chrBank1);
188                 break;
189             default:
190                 break;
191         }
192     }
193 
194     void save(string[string] state) {
195         state["mapper1.shiftRegister"] = to!string(this.shiftRegister);
196         state["mapper1.control"] = to!string(this.control);
197         state["mapper1.prgMode"] = to!string(this.prgMode);
198         state["mapper1.chrMode"] = to!string(this.chrMode);
199         state["mapper1.prgBank"] = to!string(this.prgBank);
200         state["mapper1.chrBank0"] = to!string(this.chrBank0);
201         state["mapper1.chrBank1"] = to!string(this.chrBank1);
202         state["mapper1.prgOffsets"] = to!string(this.prgOffsets);
203         state["mapper1.chrOffsets"] = to!string(this.chrOffsets);
204     }
205 
206     void load(string[string] state) {
207         this.shiftRegister = to!ubyte(state["mapper1.shiftRegister"]);
208         this.control = to!ubyte(state["mapper1.control"]);
209         this.prgMode = to!ubyte(state["mapper1.prgMode"]);
210         this.chrMode = to!ubyte(state["mapper1.chrMode"]);
211         this.prgBank = to!ubyte(state["mapper1.prgBank"]);
212         this.chrBank0 = to!ubyte(state["mapper1.chrBank0"]);
213         this.chrBank1 = to!ubyte(state["mapper1.chrBank1"]);
214         this.prgOffsets = to!(int[2])(state["mapper1.prgOffsets"]);
215         this.chrOffsets = to!(int[2])(state["mapper1.chrOffsets"]);
216     }
217 
218     private:
219         Cartridge cart;
220         ubyte     shiftRegister;
221         ubyte     control;
222         ubyte     prgMode;
223         ubyte     chrMode;
224         ubyte     prgBank;
225         ubyte     chrBank0;
226         ubyte     chrBank1;
227         int[2]    prgOffsets;
228         int[2]    chrOffsets;
229 }