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 }