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 }