1 module gbaid.gba.memory; 2 3 import core.time : TickDuration; 4 5 import std.meta : Alias; 6 import std.traits : ImmutableOf; 7 import std.format : format; 8 9 import gbaid.util; 10 11 import gbaid.gba.io; 12 import gbaid.gba.gpio; 13 import gbaid.gba.rtc; 14 import gbaid.gba.interrupt; 15 16 public import gbaid.gba.rtc : RTC_SIZE; 17 18 public alias Ram(uint byteSize) = Memory!(byteSize, false); 19 public alias Rom(uint byteSize) = Memory!(byteSize, true); 20 21 public alias Bios = Rom!BIOS_SIZE; 22 public alias BoardWram = Ram!BOARD_WRAM_SIZE; 23 public alias ChipWram = Ram!CHIP_WRAM_SIZE; 24 public alias Palette = Ram!PALETTE_SIZE; 25 public alias Vram = Ram!VRAM_SIZE; 26 public alias Oam = Ram!OAM_SIZE; 27 public alias GameRom = Rom!MAX_ROM_SIZE; 28 public alias Sram = Ram!SRAM_SIZE; 29 public alias Flash512K = Flash!FLASH_512K_SIZE; 30 public alias Flash1M = Flash!FLASH_1M_SIZE; 31 32 public enum MainSaveKind { 33 SRAM, FLASH_512K, FLASH_1M, NONE 34 } 35 36 public enum uint BIOS_SIZE = 16 * BYTES_PER_KIB; 37 public enum uint BOARD_WRAM_SIZE = 256 * BYTES_PER_KIB; 38 public enum uint CHIP_WRAM_SIZE = 32 * BYTES_PER_KIB; 39 public enum uint IO_REGISTERS_SIZE = 1 * BYTES_PER_KIB; 40 public enum uint PALETTE_SIZE = 1 * BYTES_PER_KIB; 41 public enum uint VRAM_SIZE = 96 * BYTES_PER_KIB; 42 public enum uint OAM_SIZE = 1 * BYTES_PER_KIB; 43 public enum uint MAX_ROM_SIZE = 32 * BYTES_PER_MIB; 44 public enum uint SRAM_SIZE = 32 * BYTES_PER_KIB; 45 public enum uint FLASH_512K_SIZE = 64 * BYTES_PER_KIB; 46 public enum uint FLASH_1M_SIZE = 128 * BYTES_PER_KIB; 47 public enum uint EEPROM_SIZE = 8 * BYTES_PER_KIB; 48 49 public enum uint BIOS_START = 0x00000000; 50 public enum uint BIOS_MASK = 0x3FFF; 51 public enum uint BOARD_WRAM_MASK = 0x3FFFF; 52 public enum uint CHIP_WRAM_MASK = 0x7FFF; 53 public enum uint IO_REGISTERS_END = 0x040003FE; 54 public enum uint IO_REGISTERS_MASK = 0x3FF; 55 public enum uint PALETTE_MASK = 0x3FF; 56 public enum uint VRAM_MASK = 0x1FFFF; 57 public enum uint VRAM_LOWER_MASK = 0xFFFF; 58 public enum uint VRAM_HIGH_MASK = 0x17FFF; 59 public enum uint OAM_MASK = 0x3FF; 60 public enum uint GAME_PAK_START = 0x08000000; 61 public enum uint ROM_MASK = 0x1FFFFFF; 62 public enum uint SRAM_MASK = 0x7FFF; 63 public enum uint FLASH_MASK = 0xFFFF; 64 public enum uint EEPROM_MASK_NARROW = 0xFFFF00; 65 public enum uint EEPROM_MASK_WIDE = 0x0; 66 67 public struct Memory(uint byteSize, bool readOnly) { 68 private Mod!(void[byteSize]) memory; 69 70 static if (readOnly) { 71 @disable public this(); 72 } 73 74 public this(void[] memory) { 75 if (memory.length > byteSize) { 76 throw new Exception(format("Expected a memory size of %dB, but got %dB", byteSize, memory.length)); 77 } 78 this.memory[0 .. memory.length] = memory[]; 79 } 80 81 public Mod!T get(T)(uint address) if (IsInt8to32Type!T) { 82 return *cast(Mod!T*) (memory.ptr + (address & IntAlignMask!T)); 83 } 84 85 static if (!readOnly) { 86 public void set(T)(uint address, T v) if (IsInt8to32Type!T) { 87 *cast(Mod!T*) (memory.ptr + (address & IntAlignMask!T)) = v; 88 } 89 } 90 91 public Mod!(T[]) getArray(T)(uint address = 0x0, uint size = byteSize) if (IsInt8to32Type!T) { 92 address &= IntAlignMask!T; 93 return cast(Mod!(T[])) (memory[address .. address + size]); 94 } 95 96 public Mod!(T*) getPointer(T)(uint address) if (IsInt8to32Type!T) { 97 return cast(Mod!T*) (memory.ptr + (address & IntAlignMask!T)); 98 } 99 100 private template Mod(T) { 101 static if (readOnly) { 102 private alias Mod = ImmutableOf!T; 103 } else { 104 private alias Mod = T; 105 } 106 } 107 } 108 109 public struct Flash(uint byteSize) if (byteSize == 64 * BYTES_PER_KIB || byteSize == 128 * BYTES_PER_KIB) { 110 private alias DeviceID = Alias!(byteSize == 64 * BYTES_PER_KIB ? PANASONIC_64K_ID : SANYO_128K_ID); 111 private static enum uint PANASONIC_64K_ID = 0x1B32; 112 private static enum uint SANYO_128K_ID = 0x1362; 113 private static enum uint DEVICE_ID_ADDRESS = 0x1; 114 private static enum uint FIRST_CMD_ADDRESS = 0x5555; 115 private static enum uint SECOND_CMD_ADDRESS = 0x2AAA; 116 private static enum uint FIRST_CMD_START_BYTE = 0xAA; 117 private static enum uint SECOND_CMD_START_BYTE = 0x55; 118 private static enum uint ID_MODE_START_CMD_BYTE = 0x90; 119 private static enum uint ID_MODE_STOP_CMD_BYTE = 0xF0; 120 private static enum uint ERASE_CMD_BYTE = 0x80; 121 private static enum uint ERASE_ALL_CMD_BYTE = 0x10; 122 private static enum uint ERASE_SECTOR_CMD_BYTE = 0x30; 123 private static enum uint WRITE_BYTE_CMD_BYTE = 0xA0; 124 private static enum uint SWITCH_BANK_CMD_BYTE = 0xB0; 125 private void[byteSize] memory; 126 private Mode mode = Mode.NORMAL; 127 private uint cmdStage = 0; 128 private uint eraseSectorTarget; 129 private uint sectorOffset = 0; 130 131 public this(void[] memory) { 132 if (memory.length == 0) { 133 this.erase(0x0, byteSize); 134 } else if (memory.length <= byteSize) { 135 this.memory[0 .. memory.length] = memory[]; 136 } else { 137 throw new Exception(format("Expected a memory size of 0 or %dB, but got %dB", byteSize, memory.length)); 138 } 139 } 140 141 public T get(T)(uint address) if (is(T == byte) || is(T == ubyte)) { 142 if (mode == Mode.ID && address <= DEVICE_ID_ADDRESS) { 143 return cast(T) (DeviceID >> ((address & 0b1) << 3)); 144 } 145 return *cast(T*) (memory.ptr + address + sectorOffset); 146 } 147 148 public void set(T)(uint address, T value) if (is(T == byte) || is(T == ubyte)) { 149 uint intValue = value & 0xFF; 150 // Handle commands completions 151 switch (mode) { 152 case Mode.ERASE_ALL: 153 if (address == 0x0 && intValue == 0xFF) { 154 mode = Mode.NORMAL; 155 } 156 break; 157 case Mode.ERASE_SECTOR: 158 if (address == eraseSectorTarget && intValue == 0xFF) { 159 mode = Mode.NORMAL; 160 } 161 break; 162 case Mode.WRITE_BYTE: 163 *cast(T*) (memory.ptr + address + sectorOffset) = value; 164 mode = Mode.NORMAL; 165 break; 166 case Mode.SWITCH_BANK: 167 sectorOffset = (value & 0b1) << 16; 168 mode = Mode.NORMAL; 169 break; 170 default: 171 } 172 // Handle command initialization and execution 173 if (address == FIRST_CMD_ADDRESS && intValue == FIRST_CMD_START_BYTE) { 174 cmdStage = 1; 175 } else if (cmdStage == 1) { 176 if (address == SECOND_CMD_ADDRESS && intValue == SECOND_CMD_START_BYTE) { 177 cmdStage = 2; 178 } else { 179 cmdStage = 0; 180 } 181 } else if (cmdStage == 2) { 182 cmdStage = 0; 183 // execute 184 if (address == FIRST_CMD_ADDRESS) { 185 switch (intValue) { 186 case ID_MODE_START_CMD_BYTE: 187 mode = Mode.ID; 188 break; 189 case ID_MODE_STOP_CMD_BYTE: 190 mode = Mode.NORMAL; 191 break; 192 case ERASE_CMD_BYTE: 193 mode = Mode.ERASE; 194 break; 195 case ERASE_ALL_CMD_BYTE: 196 if (mode == Mode.ERASE) { 197 mode = Mode.ERASE_ALL; 198 erase(0x0, byteSize); 199 } 200 break; 201 case WRITE_BYTE_CMD_BYTE: 202 mode = Mode.WRITE_BYTE; 203 break; 204 case SWITCH_BANK_CMD_BYTE: 205 if (DeviceID == SANYO_128K_ID) { 206 mode = Mode.SWITCH_BANK; 207 } 208 break; 209 default: 210 } 211 } else if (!(address & 0xFF0FFF) && intValue == ERASE_SECTOR_CMD_BYTE && mode == Mode.ERASE) { 212 mode = Mode.ERASE_SECTOR; 213 eraseSectorTarget = address; 214 erase(address + sectorOffset, 4 * BYTES_PER_KIB); 215 } 216 } 217 } 218 219 public T[] getArray(T)(uint address = 0x0, uint size = byteSize) if (IsInt8to32Type!T) { 220 return cast(T[]) (memory[address .. address + size]); 221 } 222 223 private void erase(uint address, uint size) { 224 auto byteMemory = cast(byte*) (memory.ptr + address); 225 byteMemory[0 .. size] = cast(byte) 0xFF; 226 } 227 228 private static enum Mode { 229 NORMAL, 230 ID, 231 ERASE, 232 ERASE_ALL, 233 ERASE_SECTOR, 234 WRITE_BYTE, 235 SWITCH_BANK 236 } 237 } 238 239 public struct Eeprom { 240 private void[EEPROM_SIZE] memory; 241 private Mode mode = Mode.NORMAL; 242 private int targetAddress = 0; 243 private int currentAddressBit = 0; 244 private int currentReadBit = 0; 245 private int[3] writeBuffer; 246 247 public this(void[] memory) { 248 auto byteSize = this.memory.length; 249 if (memory.length == 0) { 250 (cast(byte[]) memory)[0 .. $] = cast(byte) 0xFF; 251 } else if (memory.length <= byteSize) { 252 this.memory[0 .. memory.length] = memory[]; 253 } else { 254 throw new Exception(format("Expected a memory size of 0 or %dB, but got %dB", byteSize, memory.length)); 255 } 256 } 257 258 public T get(T)(uint address) if (is(T == short) || is (T == ushort)) { 259 if (mode == Mode.WRITE) { 260 // get write address and offset in write buffer 261 int actualAddress = void; 262 int bitOffset = void; 263 if (currentAddressBit > 73) { 264 actualAddress = targetAddress >>> 18; 265 bitOffset = 14; 266 } else { 267 actualAddress = targetAddress >>> 26; 268 bitOffset = 6; 269 } 270 actualAddress <<= 3; 271 // get data to write from buffer 272 long toWrite = 0; 273 foreach (int i; 0 .. 64) { 274 toWrite |= writeBuffer[i + bitOffset >> 5].getBit(i + bitOffset & 31).ucast() << 63 - i; 275 } 276 // write data 277 auto intMemory = cast(int*) (memory.ptr + actualAddress); 278 *intMemory = cast(int) toWrite; 279 *(intMemory + 1) = cast(int) (toWrite >>> 32); 280 // end write mode 281 mode = Mode.NORMAL; 282 targetAddress = 0; 283 currentAddressBit = 0; 284 } else if (mode == Mode.READ) { 285 // get data 286 T data = void; 287 if (currentReadBit < 4) { 288 // first 4 bits are 0 289 data = 0; 290 } else { 291 // get read address depending on amount of bits received 292 int actualAddress = void; 293 if (currentAddressBit > 9) { 294 actualAddress = targetAddress >>> 18; 295 } else { 296 actualAddress = targetAddress >>> 26; 297 } 298 actualAddress <<= 3; 299 actualAddress += 7 - (currentReadBit - 4 >> 3); 300 // get the data bit 301 auto byteMemory = cast(byte*) (memory.ptr + actualAddress); 302 data = cast(T) (*byteMemory).getBit(7 - (currentReadBit - 4 & 7)); 303 } 304 // end read mode on last bit 305 if (currentReadBit == 67) { 306 mode = Mode.NORMAL; 307 targetAddress = 0; 308 currentAddressBit = 0; 309 currentReadBit = 0; 310 } else { 311 // increment current read bit and save address 312 currentReadBit++; 313 } 314 return data; 315 } 316 // return ready 317 return 1; 318 } 319 320 public void set(T)(uint address, T value) if (is(T == short) || is (T == ushort)) { 321 // get relevant bit 322 int bit = value & 0b1; 323 // if in write mode, buffer the bit 324 if (mode == Mode.WRITE) { 325 writeBuffer[currentAddressBit - 2 >> 5].setBit(currentAddressBit - 2 & 31, bit); 326 } 327 // then process as command or address bit 328 if (currentAddressBit == 0) { 329 // check for first command bit 330 if (bit == 0b1) { 331 // wait for second bit 332 currentAddressBit++; 333 } 334 } else if (currentAddressBit == 1) { 335 // second command bit, set mode to the command 336 mode = cast(Mode) bit; 337 currentAddressBit++; 338 } else { 339 // set address if we have a command 340 if (currentAddressBit < 16) { 341 // max address size if 14 (+2 including command bits) 342 targetAddress.setBit(33 - currentAddressBit, bit); 343 } 344 currentAddressBit++; 345 } 346 } 347 348 public T[] getArray(T)(uint address = 0x0, uint size = EEPROM_SIZE) if (IsInt8to32Type!T) { 349 return cast(T[]) (memory[address .. address + size]); 350 } 351 352 private static enum Mode { 353 NORMAL = 2, 354 READ = 1, 355 WRITE = 0 356 } 357 } 358 359 public struct GamePakData { 360 public void[] rom; 361 public void[] mainSave; 362 public MainSaveKind mainSaveKind; 363 public void[] eeprom; 364 public bool eepromEnabled; 365 public void[] rtc; 366 public bool rtcEnabled; 367 } 368 369 private union SaveMemory { 370 private Sram* sram; 371 private Flash512K* flash512k; 372 private Flash1M* flash1m; 373 } 374 375 public struct GamePak { 376 private GameRom rom; 377 private GpioPort gpio; 378 private MainSaveKind saveKind; 379 private SaveMemory save; 380 private Eeprom* eeprom = null; 381 private Rtc* rtc = null; 382 private int delegate(uint) _unusedMemory = null; 383 private uint eepromMask; 384 private uint actualRomByteSize; 385 386 @disable public this(); 387 388 public this(GamePakData data) { 389 rom = GameRom(data.rom); 390 actualRomByteSize = (cast(int) data.rom.length).nextPowerOf2(); 391 eepromMask = actualRomByteSize <= 16 * BYTES_PER_MIB ? EEPROM_MASK_WIDE : EEPROM_MASK_NARROW; 392 gpio.valueAtCa = rom.get!short(0xCA); 393 394 saveKind = data.mainSaveKind; 395 final switch (saveKind) with (MainSaveKind) { 396 case SRAM: 397 save.sram = new Sram(data.mainSave); 398 break; 399 case FLASH_512K: 400 save.flash512k = new Flash512K(data.mainSave); 401 break; 402 case FLASH_1M: 403 save.flash1m = new Flash1M(data.mainSave); 404 break; 405 case NONE: 406 break; 407 } 408 if (data.eepromEnabled) { 409 eeprom = new Eeprom(data.eeprom); 410 } 411 if (data.rtcEnabled) { 412 rtc = new Rtc(data.rtc); 413 gpio.chip = rtc.chip; 414 gpio.enabled = true; 415 } 416 } 417 418 @property public GamePakData saveData() { 419 GamePakData data; 420 data.mainSaveKind = saveKind; 421 final switch (saveKind) with (MainSaveKind) { 422 case SRAM: 423 data.mainSave = save.sram.getArray!ubyte(); 424 break; 425 case FLASH_512K: 426 data.mainSave = save.flash512k.getArray!ubyte(); 427 break; 428 case FLASH_1M: 429 data.mainSave = save.flash1m.getArray!ubyte(); 430 break; 431 case NONE: 432 data.mainSave = null; 433 break; 434 } 435 if (eeprom !is null) { 436 data.eeprom = eeprom.getArray!ubyte(); 437 data.eepromEnabled = true; 438 } else { 439 data.eepromEnabled = false; 440 } 441 if (rtc !is null) { 442 data.rtc = rtc.dataArray; 443 data.rtcEnabled = true; 444 } else { 445 data.rtcEnabled = false; 446 } 447 return data; 448 } 449 450 @property public void interruptHandler(InterruptHandler interruptHandler) { 451 if (rtc !is null) { 452 rtc.interruptHandler = interruptHandler; 453 } 454 } 455 456 @property public void unusedMemory(int delegate(uint) unusedMemory) { 457 assert (unusedMemory !is null); 458 _unusedMemory = unusedMemory; 459 } 460 461 public T get(T)(uint address) if (IsInt8to32Type!T) { 462 auto highAddress = address >>> 24; 463 switch (highAddress) { 464 case 0x0: .. case 0x4: 465 address &= actualRomByteSize - 1; 466 if (address >= GPIO_ROM_START_ADDRESS && address < GPIO_ROM_END_ADDRESS && gpio.enabled) { 467 return gpio.get!T(address); 468 } 469 return rom.get!T(address); 470 case 0x5: 471 auto lowAddress = address & 0xFFFFFF; 472 if (eeprom !is null && (lowAddress & eepromMask) == eepromMask) { 473 static if (is(T == short) || is(T == ushort)) { 474 return eeprom.get!T(lowAddress & ~eepromMask); 475 } else { 476 return cast(T) _unusedMemory(address & IntAlignMask!T); 477 } 478 } 479 goto case 0x4; 480 case 0x6: 481 final switch (saveKind) with (MainSaveKind) { 482 case SRAM: 483 address &= SRAM_MASK; 484 return save.sram.get!T(address); 485 case FLASH_512K: 486 static if (is(T == byte) || is(T == ubyte)) { 487 address &= FLASH_MASK; 488 return save.flash512k.get!T(address); 489 } else { 490 return cast(T) _unusedMemory(address & IntAlignMask!T); 491 } 492 case FLASH_1M: 493 static if (is(T == byte) || is(T == ubyte)) { 494 address &= FLASH_MASK; 495 return save.flash1m.get!T(address); 496 } else { 497 return cast(T) _unusedMemory(address & IntAlignMask!T); 498 } 499 case NONE: 500 return cast(T) _unusedMemory(address & IntAlignMask!T); 501 } 502 default: 503 return cast(T) _unusedMemory(address & IntAlignMask!T); 504 } 505 } 506 507 public void set(T)(uint address, T value) if (IsInt8to32Type!T) { 508 auto highAddress = address >>> 24; 509 switch (highAddress) { 510 case 0x0: .. case 0x4: 511 address &= actualRomByteSize - 1; 512 if (address >= GPIO_ROM_START_ADDRESS && address < GPIO_ROM_END_ADDRESS && gpio.enabled) { 513 gpio.set!T(address, value); 514 } 515 return; 516 case 0x5: 517 auto lowAddress = address & 0xFFFFFF; 518 if (eeprom !is null && (lowAddress & eepromMask) == eepromMask) { 519 static if (is(T == short) || is(T == ushort)) { 520 eeprom.set!T(lowAddress & ~eepromMask, value); 521 } 522 } 523 return; 524 case 0x6: 525 final switch (saveKind) with (MainSaveKind) { 526 case SRAM: 527 address &= SRAM_MASK; 528 save.sram.set!T(address, value); 529 return; 530 case FLASH_512K: 531 static if (is(T == byte) || is(T == ubyte)) { 532 address &= FLASH_MASK; 533 save.flash512k.set!T(address, value); 534 } 535 return; 536 case FLASH_1M: 537 static if (is(T == byte) || is(T == ubyte)) { 538 address &= FLASH_MASK; 539 save.flash1m.set!T(address, value); 540 } 541 return; 542 case NONE: 543 return; 544 } 545 default: 546 return; 547 } 548 } 549 } 550 551 public struct MemoryBus { 552 private Bios _bios; 553 private BoardWram _boardWRAM; 554 private ChipWram _chipWRAM; 555 private IoRegisters _ioRegisters; 556 private Palette _palette; 557 private Vram _vram; 558 private Oam _oam; 559 private GamePak _gamePak; 560 private int delegate(uint) _unusedMemory; 561 private bool delegate(uint) _biosReadGuard; 562 private int delegate(uint) _biosReadFallback = null; 563 564 @disable public this(); 565 566 public this(void[] bios, GamePakData gamePakData) { 567 _bios = Bios(bios); 568 _gamePak = GamePak(gamePakData); 569 _unusedMemory = &zeroUnusedMemory; 570 _biosReadGuard = &noBiosReadGuard; 571 } 572 573 @property public Bios* bios() { 574 return &_bios; 575 } 576 577 @property public BoardWram* boardWRAM() { 578 return &_boardWRAM; 579 } 580 581 @property public ChipWram* chipWRAM() { 582 return &_chipWRAM; 583 } 584 585 @property public IoRegisters* ioRegisters() { 586 return &_ioRegisters; 587 } 588 589 @property public Palette* palette() { 590 return &_palette; 591 } 592 593 @property public Vram* vram() { 594 return &_vram; 595 } 596 597 @property public Oam* oam() { 598 return &_oam; 599 } 600 601 @property public GamePak* gamePak() { 602 return &_gamePak; 603 } 604 605 @property public void unusedMemory(int delegate(uint) unusedMemory) { 606 assert (unusedMemory !is null); 607 _unusedMemory = unusedMemory; 608 gamePak.unusedMemory = _unusedMemory; 609 } 610 611 @property public void biosReadGuard(bool delegate(uint) biosReadGuard) { 612 _biosReadGuard = biosReadGuard; 613 } 614 615 @property public void biosReadFallback(int delegate(uint) biosReadFallback) { 616 _biosReadFallback = biosReadFallback; 617 } 618 619 private int zeroUnusedMemory(uint address) { 620 return 0; 621 } 622 623 private bool noBiosReadGuard(uint address) { 624 return true; 625 } 626 627 public T get(T)(uint address) if (IsInt8to32Type!T) { 628 auto highAddress = address >>> 24; 629 switch (highAddress) { 630 case 0x0: 631 auto lowAddress = address & 0xFFFFFF; 632 if (lowAddress & ~BIOS_MASK) { 633 return cast(T) _unusedMemory(address & IntAlignMask!T); 634 } 635 auto alignedAddress = address & IntAlignMask!T; 636 if (!_biosReadGuard(alignedAddress)) { 637 return cast(T) _biosReadFallback(alignedAddress); 638 } 639 return _bios.get!T(address & BIOS_MASK); 640 case 0x1: 641 return cast(T) _unusedMemory(address & IntAlignMask!T); 642 case 0x2: 643 return _boardWRAM.get!T(address & BOARD_WRAM_MASK); 644 case 0x3: 645 return _chipWRAM.get!T(address & CHIP_WRAM_MASK); 646 case 0x4: 647 if (address > IO_REGISTERS_END) { 648 return cast(T) _unusedMemory(address & IntAlignMask!T); 649 } 650 return _ioRegisters.get!T(address & IO_REGISTERS_MASK); 651 case 0x5: 652 return _palette.get!T(address & PALETTE_MASK); 653 case 0x6: 654 address &= VRAM_MASK; 655 if (address & ~VRAM_LOWER_MASK) { 656 address &= VRAM_HIGH_MASK; 657 } 658 return _vram.get!T(address); 659 case 0x7: 660 return _oam.get!T(address & OAM_MASK); 661 case 0x8: .. case 0xE: 662 return _gamePak.get!T(address - GAME_PAK_START); 663 default: 664 return cast(T) _unusedMemory(address & IntAlignMask!T); 665 } 666 } 667 668 public void set(T)(uint address, T value) if (IsInt8to32Type!T) { 669 auto highAddress = address >>> 24; 670 switch (highAddress) { 671 case 0x2: 672 _boardWRAM.set!T(address & BOARD_WRAM_MASK, value); 673 return; 674 case 0x3: 675 _chipWRAM.set!T(address & CHIP_WRAM_MASK, value); 676 return; 677 case 0x4: 678 if (address <= IO_REGISTERS_END) { 679 _ioRegisters.set!T(address & IO_REGISTERS_MASK, value); 680 } 681 return; 682 case 0x5: 683 static if (is(T == byte) || is(T == ubyte)) { 684 _palette.set!short(address & PALETTE_MASK, value << 8 | value & 0xFF); 685 } else { 686 _palette.set!T(address & PALETTE_MASK, value); 687 } 688 return; 689 case 0x6: 690 address &= VRAM_MASK; 691 if (address & ~VRAM_LOWER_MASK) { 692 address &= VRAM_HIGH_MASK; 693 } 694 static if (is(T == byte) || is(T == ubyte)) { 695 if (address < 0x10000 || (ioRegisters.getUnMonitored!short(0x0) & 0b111) > 2 && address < 0x14000) { 696 _vram.set!short(address, value << 8 | value & 0xFF); 697 } 698 } else { 699 _vram.set!T(address, value); 700 } 701 return; 702 case 0x7: 703 static if (!is(T == byte) && !is(T == ubyte)) { 704 _oam.set!T(address & OAM_MASK, value); 705 } 706 return; 707 case 0x8: .. case 0xE: 708 _gamePak.set!T(address - GAME_PAK_START, value); 709 return; 710 default: 711 return; 712 } 713 } 714 } 715 716 public int rotateRead(int address, int value) { 717 return value.rotateRight((address & 3) << 3); 718 } 719 720 public int rotateRead(int address, short value) { 721 return rotateRight(value & 0xFFFF, (address & 1) << 3); 722 } 723 724 public int rotateReadSigned(int address, short value) { 725 return value >> ((address & 1) << 3); 726 } 727 728 unittest { 729 auto ram = Ram!1024(); 730 731 static assert(is(typeof(ram.get!ushort(2)) == ushort)); 732 static assert(is(typeof(ram.getPointer!ushort(3)) == ushort*)); 733 static assert(is(typeof(ram.getArray!ushort(5, 2)) == ushort[])); 734 735 ram.set!ushort(2, 34); 736 assert(*ram.getPointer!ushort(2) == 34); 737 assert(ram.getArray!ushort(2, 8) == [34, 0, 0, 0]); 738 } 739 740 unittest { 741 int[] data = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]; 742 auto rom = Rom!40(data); 743 744 static assert(!__traits(compiles, Rom!40())); 745 static assert(!__traits(compiles, rom.set!int(8, 34))); 746 static assert(is(typeof(rom.get!int(4)) == immutable int)); 747 static assert(is(typeof(rom.getPointer!int(8)) == immutable int*)); 748 static assert(is(typeof(rom.getArray!int(24, 12)) == immutable int[])); 749 750 assert(rom.get!int(4) == 8); 751 assert(*rom.getPointer!int(8) == 7); 752 assert(rom.getArray!int(24, 12) == [3, 2, 1]); 753 }