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 }