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 }