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