1 module nes.mapper4;
2 
3 import std.conv;
4 import std.format;
5 
6 import nes.cartridge;
7 import nes.console;
8 import nes.mapper;
9 import nes.memory;
10 
11 class Mapper4 : Mapper {
12     this(Console console, Cartridge cartridge) {
13         this.console = console;
14         this.cart = cartridge;
15 
16         this.prgOffsets[0] = this.prgBankOffset(0);
17         this.prgOffsets[1] = this.prgBankOffset(1);
18         this.prgOffsets[2] = this.prgBankOffset(-2);
19         this.prgOffsets[3] = this.prgBankOffset(-1);
20     }
21 
22     void step() {
23         auto ppu = this.console.ppu;
24         if (ppu.cycle != 280) { // TODO: this *should* be 260
25             return;
26         }
27         if (ppu.scanLine > 239 && ppu.scanLine < 261) {
28             return;
29         }
30         if (ppu.flagShowBackground == 0 && ppu.flagShowSprites == 0) {
31             return;
32         }
33         this.handleScanLine();
34     }
35 
36     void handleScanLine() {
37         if (this.counter == 0) {
38             this.counter = this.reload;
39         } else {
40             this.counter--;
41             if (this.counter == 0 && this.irqEnable) {
42                 this.console.cpu.triggerIRQ();
43             }
44         }
45     }
46 
47     ubyte read(ushort address) {
48         if (address < 0x2000) {
49             auto bank = address / 0x0400;
50             auto offset = address % 0x0400;
51             return this.cart.chr[this.chrOffsets[bank] + cast(int)offset];
52         }
53         else if (address >= 0x8000) {
54             address = cast(ushort)(address - 0x8000);
55             auto bank = address / 0x2000;
56             auto offset = address % 0x2000;
57             return this.cart.prg[this.prgOffsets[bank] + cast(int)offset];
58         }
59         else if (address >= 0x6000) {
60             auto index = cast(int)address - 0x6000;
61             return this.cart.sram[index];
62         }
63         else {
64             throw new MapperException(format("unhandled mapper4 read at address: 0x%04X", address));
65         }
66     }
67 
68     void write(ushort address, ubyte value) {
69         if (address < 0x2000) {
70             auto bank = address / 0x0400;
71             auto offset = address % 0x0400;
72             this.cart.chr[this.chrOffsets[bank] + cast(int)offset] = value;
73         }
74         else if (address >= 0x8000) {
75             this.writeRegister(address, value);
76         }
77         else if (address >= 0x6000) {
78             auto index = cast(int)address - 0x6000;
79             this.cart.sram[index] = value;
80         }
81         else {
82             throw new MapperException(format("unhandled mapper4 write at address: 0x%04X", address));
83         }
84     }
85 
86     void save(string[string] state) {
87         state["mapper4.register"] = to!string(this.register);
88         state["mapper4.registers"] = to!string(this.registers);
89         state["mapper4.prgMode"] = to!string(this.prgMode);
90         state["mapper4.chrMode"] = to!string(this.chrMode);
91         state["mapper4.prgOffsets"] = to!string(this.prgOffsets);
92         state["mapper4.chrOffsets"] = to!string(this.chrOffsets);
93         state["mapper4.reload"] = to!string(this.reload);
94         state["mapper4.counter"] = to!string(this.counter);
95         state["mapper4.irqEnable"] = to!string(this.irqEnable);
96     }
97 
98     void load(string[string] state) {
99         this.register = to!ubyte(state["mapper4.register"]);
100         this.registers = to!(ubyte[8])(state["mapper4.registers"]);
101         this.prgMode = to!ubyte(state["mapper4.prgMode"]);
102         this.chrMode = to!ubyte(state["mapper4.chrMode"]);
103         this.prgOffsets = to!(int[4])(state["mapper4.prgOffsets"]);
104         this.chrOffsets = to!(int[8])(state["mapper4.chrOffsets"]);
105         this.reload = to!ubyte(state["mapper4.reload"]);
106         this.counter = to!ubyte(state["mapper4.counter"]);
107         this.irqEnable = to!bool(state["mapper4.irqEnable"]);
108     }
109 
110     private:
111         Cartridge cart;
112         Console   console;
113         ubyte     register;
114         ubyte[8]  registers;
115         ubyte     prgMode;
116         ubyte     chrMode;
117         int[4]    prgOffsets;
118         int[8]    chrOffsets;
119         ubyte     reload;
120         ubyte     counter;
121         bool      irqEnable;
122 
123         void writeRegister(ushort address, ubyte value) {
124             if (address <= 0x9FFF && (address % 2) == 0)
125                 this.writeBankSelect(value);
126             else if (address <= 0x9FFF && (address % 2) == 1)
127                 this.writeBankData(value);
128             else if (address <= 0xBFFF && (address % 2) == 0)
129                 this.writeMirror(value);
130             else if (address <= 0xBFFF && (address % 2) == 1)
131                 this.writeProtect(value);
132             else if (address <= 0xDFFF && (address % 2) == 0)
133                 this.writeIRQLatch(value);
134             else if (address <= 0xDFFF && (address % 2) == 1)
135                 this.writeIRQReload(value);
136             else if (address <= 0xFFFF && (address % 2) == 0)
137                 this.writeIRQDisable(value);
138             else if (address <= 0xFFFF && (address % 2) == 1)
139                 this.writeIRQEnable(value);
140         }
141 
142         void writeBankSelect(ubyte value) {
143             this.prgMode = (value >> 6) & 1;
144             this.chrMode = (value >> 7) & 1;
145             this.register = value & 7;
146             this.updateOffsets();
147         }
148 
149         void writeBankData(ubyte value) {
150             this.registers[this.register] = value;
151             this.updateOffsets();
152         }
153 
154         void writeMirror(ubyte value) {
155             switch (value & 1) {
156                 case 0:
157                     this.cart.mirror = MirrorVertical;
158                     break;
159                 case 1:
160                     this.cart.mirror = MirrorHorizontal;
161                     break;
162                 default:
163                     break;
164             }
165         }
166 
167         void writeProtect(ubyte value) {
168         }
169 
170         void writeIRQLatch(ubyte value) {
171             this.reload = value;
172         }
173 
174         void writeIRQReload(ubyte value) {
175             this.counter = 0;
176         }
177 
178         void writeIRQDisable(ubyte value) {
179             this.irqEnable = false;
180         }
181 
182         void writeIRQEnable(ubyte value) {
183             this.irqEnable = true;
184         }
185 
186         int prgBankOffset(int index) {
187             if (index >= 0x80) {
188                 index -= 0x100;
189             }
190             index %= cast(int)(this.cart.prg.length / 0x2000);
191             int offset = index * 0x2000;
192             if (offset < 0) {
193                 offset += this.cart.prg.length;
194             }
195             return offset;
196         }
197 
198         int chrBankOffset(int index) {
199             if (index >= 0x80) {
200                 index -= 0x100;
201             }
202             index %= cast(int)(this.cart.chr.length / 0x0400);
203             int offset = index * 0x0400;
204             if (offset < 0) {
205                 offset += this.cart.chr.length;
206             }
207             return offset;
208         }
209 
210         void updateOffsets() {
211             switch (this.prgMode) {
212                 case 0:
213                     this.prgOffsets[0] = this.prgBankOffset(cast(int)this.registers[6]);
214                     this.prgOffsets[1] = this.prgBankOffset(cast(int)this.registers[7]);
215                     this.prgOffsets[2] = this.prgBankOffset(-2);
216                     this.prgOffsets[3] = this.prgBankOffset(-1);
217                     break;
218                 case 1:
219                     this.prgOffsets[0] = this.prgBankOffset(-2);
220                     this.prgOffsets[1] = this.prgBankOffset(cast(int)this.registers[7]);
221                     this.prgOffsets[2] = this.prgBankOffset(cast(int)this.registers[6]);
222                     this.prgOffsets[3] = this.prgBankOffset(-1);
223                     break;
224                 default:
225                     break;
226             }
227             switch (this.chrMode) {
228                 case 0:
229                     this.chrOffsets[0] = this.chrBankOffset(cast(int)(this.registers[0] & 0xFE));
230                     this.chrOffsets[1] = this.chrBankOffset(cast(int)(this.registers[0] | 0x01));
231                     this.chrOffsets[2] = this.chrBankOffset(cast(int)(this.registers[1] & 0xFE));
232                     this.chrOffsets[3] = this.chrBankOffset(cast(int)(this.registers[1] | 0x01));
233                     this.chrOffsets[4] = this.chrBankOffset(cast(int)this.registers[2]);
234                     this.chrOffsets[5] = this.chrBankOffset(cast(int)this.registers[3]);
235                     this.chrOffsets[6] = this.chrBankOffset(cast(int)this.registers[4]);
236                     this.chrOffsets[7] = this.chrBankOffset(cast(int)this.registers[5]);
237                     break;
238                 case 1:
239                     this.chrOffsets[0] = this.chrBankOffset(cast(int)this.registers[2]);
240                     this.chrOffsets[1] = this.chrBankOffset(cast(int)this.registers[3]);
241                     this.chrOffsets[2] = this.chrBankOffset(cast(int)this.registers[4]);
242                     this.chrOffsets[3] = this.chrBankOffset(cast(int)this.registers[5]);
243                     this.chrOffsets[4] = this.chrBankOffset(cast(int)(this.registers[0] & 0xFE));
244                     this.chrOffsets[5] = this.chrBankOffset(cast(int)(this.registers[0] | 0x01));
245                     this.chrOffsets[6] = this.chrBankOffset(cast(int)(this.registers[1] & 0xFE));
246                     this.chrOffsets[7] = this.chrBankOffset(cast(int)(this.registers[1] | 0x01));
247                     break;
248                 default:
249                     break;
250             }
251         }
252 }