gameboy wormboy manboy gameworm gameman wormgame mangame manworm

Dependencies:   mbed SDFileSystem2

Revision:
17:c9afe1a7b423
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mem.cpp	Sun Jan 13 19:00:10 2019 +0000
@@ -0,0 +1,721 @@
+#define NDEBUG
+#include <assert.h>
+#include <stdlib.h>
+#include <mbed.h>
+
+#include "mem.h"
+#include "platform.h"
+
+#define DANGER_MODE
+
+MemState globalMemState;
+
+void CartInfo::print() {
+  printf("Gameboy Cartridge\n"
+         "\ttitle: %s\n"
+         "\tisColor: 0x%x\n"
+         "\tSGB: 0x%x\n"
+         "\tcartType: 0x%x\n"
+         "\tromSize: 0x%x\n"
+         "\tramSize: 0x%x\n"
+         "\tnotJapan: 0x%x\n",
+         title, isColor, SGB, (u8)cartType, (u8)romSize, (u8)ramSize, notJapan);
+}
+
+// number of banks for given cartridge types
+static const u8 romSizeIDToNBanks[7] = {2,4,8,16,32,64,128};
+static const u8 ramSizeIDToNBanks[5] = {0,1,1,4,4};
+static u8* fileData = nullptr;
+
+void saveGame() {
+  FileLoadData fld;
+  fld.size = 0x2000 * globalMemState.nRamBanks;
+  fld.data = globalMemState.mappedRamAllocation;
+  saveFile("savegame.gam", fld);
+}
+
+void loadGame() {
+  FileLoadData fld = loadFile("savegame.gam");
+  memcpy(globalMemState.mappedRamAllocation, fld.data, 0x2000 * globalMemState.nRamBanks);
+}
+
+
+
+
+void initMem(FileLoadData file) {
+  CartInfo* cartInfo = (CartInfo*)(file.data + CART_INFO_ADDR);
+  cartInfo->print();
+  fileData = file.data;
+  printf("ROM size: 0x%x bytes\n", file.size);
+
+
+  globalMemState.inBios = true;       // start in BIOS mode
+  globalMemState.rom0 = file.data;    // ROM-bank 0 is the bottom of the cartridge
+
+  // initialize everything to zero
+  globalMemState.mappedRom = nullptr;
+  globalMemState.nRamBanks = 0;
+  globalMemState.nRomBanks = 0;
+  globalMemState.vram = nullptr;
+  globalMemState.mappedRam = nullptr;
+  globalMemState.disabledMappedRam = nullptr;
+  globalMemState.mappedRamAllocation = nullptr;
+  globalMemState.internalRam = nullptr;
+  globalMemState.upperRam = nullptr;
+
+  switch(cartInfo->cartType) {
+    case ROM_ONLY:
+      globalMemState.mappedRom = file.data + 0x4000; // maps in upper ROM by default
+      break;
+
+    case ROM_MBC1:
+      globalMemState.mappedRom = file.data + 0x4000; // maps in upper ROM by default
+      globalMemState.mbcType = 1;
+      if((u8)cartInfo->romSize < 7) {
+        globalMemState.nRomBanks = romSizeIDToNBanks[(u8)cartInfo->romSize]; // has ROM banks
+      } else {
+        printf("unknown number of rom banks\n");
+        assert(false);
+      }
+      break;
+
+    case ROM_MBC1_RAM:
+      globalMemState.mappedRom = file.data + 0x4000; // maps in upper ROM by default
+      globalMemState.mbcType = 1;
+      if((u8)cartInfo->romSize < 7) {
+        globalMemState.nRomBanks = romSizeIDToNBanks[(u8)cartInfo->romSize]; // has ROM banks
+      } else {
+        printf("unknown number of rom banks\n");
+        assert(false);
+      }
+
+      if((u8)cartInfo->ramSize < 5) {
+        globalMemState.nRamBanks = ramSizeIDToNBanks[(u8)cartInfo->ramSize]; // has RAM banks
+      } else {
+        printf("unknown number of ram banks\n");
+        assert(false);
+      }
+      break;
+
+    case ROM_MBC3_RAM_BATT:
+    case ROM_MBC3_TIMER_RAM_BATT:
+      globalMemState.mappedRom = file.data + 0x4000; // maps in upper ROM by default
+      globalMemState.mbcType = 3;
+      if((u8)cartInfo->romSize < 7) {
+        globalMemState.nRomBanks = romSizeIDToNBanks[(u8)cartInfo->romSize]; // has ROM banks
+      } else {
+        printf("unknown number of rom banks\n");
+        assert(false);
+      }
+
+      if((u8)cartInfo->ramSize < 5) {
+        globalMemState.nRamBanks = ramSizeIDToNBanks[(u8)cartInfo->ramSize]; // has RAM banks
+      } else {
+        printf("unknown number of ram banks\n");
+      }
+      break;
+    default:
+      printf("unknown cart type 0x%x\n", (u8)cartInfo->cartType);
+      assert(false);
+      break;
+  }
+
+  printf("mbc%d\n", globalMemState.mbcType);
+  printf("rom-banks: %d\n", globalMemState.nRomBanks);
+  printf("ram-banks: %d\n", globalMemState.nRamBanks);
+
+  // allocate cartridge RAM (8 KB * # of banks)
+  if(globalMemState.nRamBanks) {
+    globalMemState.mappedRamAllocation = (u8*)malloc(0x2000 * globalMemState.nRamBanks);
+    memset((void*)globalMemState.mappedRamAllocation, 0, 0x2000 * globalMemState.nRamBanks);
+    globalMemState.disabledMappedRam = globalMemState.mappedRamAllocation;
+  } else {
+
+  }
+
+  // allocate memories:
+  // internal RAM
+  globalMemState.internalRam = (u8*)badalloc_check(0x2000, "internal_ram");
+  globalMemState.vram =        (u8*)badalloc_check(0x2000, "vram");
+  globalMemState.upperRam    = (u8*)badalloc_check(0x80,   "upper-regs");
+  globalMemState.ioRegs      = (u8*)badalloc_check(0x80,   "io-regs");
+  
+  printf("[initRam] ioRegs: 0x%x\n", globalMemState.ioRegs);
+
+  // clear RAMs
+  memset(globalMemState.internalRam, 0, 0x2000);
+  memset(globalMemState.vram, 0, 0x2000);
+  memset(globalMemState.upperRam, 0, 0x80);
+  memset(globalMemState.ioRegs, 0, 0x80);
+
+  // setup i/o regs and friends
+  u8* io = globalMemState.ioRegs;
+  io[IO_TIMA] = 0; // reset TIMER COUNT to 0
+  io[IO_TMA] = 0;  // TIMER RELOAD
+  io[IO_TAC] = 0;  // TIMER STOP
+  io[IO_NR10] = 0x80;
+  io[IO_NR11] = 0xbf;
+  io[IO_NR12] = 0xf3;
+  io[IO_NR14] = 0xbf;
+  io[IO_NR21] = 0x3f;
+  io[IO_NR22] = 0x00;
+  io[IO_NR24] = 0xbf;
+  io[IO_NR30] = 0x7f;
+  io[IO_NR31] = 0xff;
+  io[IO_NR32] = 0x9f;
+  io[IO_NR34] = 0xbf;
+  io[IO_NR41] = 0xff;
+  io[IO_NR42] = 0x00;
+  io[IO_NR43] = 0x00;
+  io[IO_NR44] = 0xbf;
+  io[IO_NR50] = 0x77;
+  io[IO_NR51] = 0xf3;
+  io[IO_NR52] = 0xf1;
+  io[IO_LCDC] = 0x91;
+  io[IO_SCROLLY] = 0x00;
+  io[IO_SCROLLX] = 0x00;
+  io[IO_LYC] = 0x00;
+  io[IO_BGP] = 0xfc;
+  io[IO_OBP0] = 0xff;
+  io[IO_OBP1] = 0xff;
+  io[IO_WINY] = 0x00;
+  io[IO_WINX] = 0x00;
+
+  // turn off interrupts
+  globalMemState.upperRam[0x7f] = 0;
+}
+
+
+// boot ROM
+static const u8 bios[256] = {0x31, 0xFE, 0xFF, // LD, SP, $fffe   0
+                       0xAF,             // XOR A           3
+                       0x21, 0xFF, 0x9F, // LD HL, $9fff    4
+                       0x32,             // LD (HL--), A    7
+                       0xCB, 0x7C,       // BIT 7, H        8
+                       0x20, 0xFB,       // JR NZ 7         a
+                       0x21, 0x26, 0xFF, // LD HL, $ff26    c
+                       0x0E, 0x11,       // LD c,$11        f
+                       0x3E, 0x80,       // LD a,$80        11
+                       0x32,             // LD (HL--), A    13
+                       0xE2,             // LD($FF00+C),A   14
+                       0x0C,             // INC C           15
+                       0x3E, 0xF3,       // LD A, $f3       16
+                       0xE2,             // LD (HL--), A    18
+                       0x32,             // LD($FF00+C),A   19
+                       0x3E, 0x77,       // LD A,$77        1a
+                       0x77, 0x3E, 0xFC, 0xE0,
+                       0x47, 0x11, 0x04, 0x01, 0x21, 0x10, 0x80, 0x1A, 0xCD, 0x95, 0x00, 0xCD, 0x96, 0x00, 0x13, 0x7B,
+                       0xFE, 0x34, 0x20, 0xF3, 0x11, 0xD8, 0x00, 0x06, 0x08, 0x1A, 0x13, 0x22, 0x23, 0x05, 0x20, 0xF9,
+                       0x3E, 0x19, 0xEA, 0x10, 0x99, 0x21, 0x2F, 0x99, 0x0E, 0x0C, 0x3D, 0x28, 0x08, 0x32, 0x0D, 0x20,
+                       0xF9, 0x2E, 0x0F, 0x18, 0xF3, 0x67, 0x3E, 0x64, 0x57, 0xE0, 0x42, 0x3E, 0x91, 0xE0, 0x40, 0x04,
+                       0x1E, 0x02, 0x0E, 0x0C, 0xF0, 0x44, 0xFE, 0x90, 0x20, 0xFA, 0x0D, 0x20, 0xF7, 0x1D, 0x20, 0xF2,
+                       0x0E, 0x13, 0x24, 0x7C, 0x1E, 0x83, 0xFE, 0x62, 0x28, 0x06, 0x1E, 0xC1, 0xFE, 0x64, 0x20, 0x06,
+                       0x7B, 0xE2, 0x0C, 0x3E, 0x87, 0xF2, 0xF0, 0x42, 0x90, 0xE0, 0x42, 0x15, 0x20, 0xD2, 0x05, 0x20,
+                       0x4F, 0x16, 0x20, 0x18, 0xCB, 0x4F, 0x06, 0x04, 0xC5, 0xCB, 0x11, 0x17, 0xC1, 0xCB, 0x11, 0x17,
+                       0x05, 0x20, 0xF5, 0x22, 0x23, 0x22, 0x23, 0xC9, 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B,
+                       0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E,
+                       0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC,
+                       0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E, 0x3c, 0x42, 0xB9, 0xA5, 0xB9, 0xA5, 0x42, 0x4C,
+                       0x21, 0x04, 0x01, 0x11, 0xA8, 0x00, 0x1A, 0x13, 0xBE, 0x20, 0xFE, 0x23, 0x7D, 0xFE, 0x34, 0x20,
+                       0xF5, 0x06, 0x19, 0x78, 0x86, 0x23, 0x05, 0x20, 0xFB, 0x86, 0x20, 0xFE, 0x3E, 0x01, 0xE0, 0x50};
+
+// handler for MBC0 switch
+void mbc0Handler(u16 addr, u8 value) {
+
+  // it looks like tetris tries to select ROM 1 for banked ROM, so we need to allow this:
+  if(addr >= 0x2000 && addr < 0x3fff) {
+    if(value == 0 || value == 1) {
+      // nothing to do!
+    } else {
+      assert(false);
+    }
+  } else {
+    assert(false);
+  }
+
+}
+
+// handler for MBC1 switch (doesn't handle everything yet...)
+void mbc1Handler(u16 addr, u8 value) {
+  if(addr >= 0x2000 && addr < 0x3fff) {
+    // ROM bank switch
+    if(value >= globalMemState.nRomBanks) {
+      printf("\trequested rom bank %d when there are only %d banks!\n", value, globalMemState.nRomBanks);
+      assert(false);
+    }
+
+    if(value == 0) value = 1;
+    if(value == 0x21) value = 0x20;
+    if(value == 0x41) value = 0x40;
+    globalMemState.mappedRom = fileData + 0x4000 * value;
+  } else if(addr >= 0 && addr < 0x1fff) {
+    // enable RAM
+    if(value == 0) {
+      globalMemState.disabledMappedRam = globalMemState.mappedRam;
+      globalMemState.mappedRam = nullptr;
+    } else if(value == 0xa) {
+      globalMemState.mappedRam = globalMemState.disabledMappedRam;
+    } else {
+      assert(false);
+    }
+  } else {
+    assert(false);
+  }
+
+}
+
+// handler for MBC2 switch (doesn't handle anything yet...)
+void mbc2Handler(u16 addr, u8 value) {
+  assert(false);
+}
+
+// handler for MBC3 switch (doesn't handle anything yet...)
+void mbc3Handler(u16 addr, u8 value) {
+
+  if(addr >= 0x2000 && addr < 0x3fff) {
+    // ROM bank switch
+    if(value >= globalMemState.nRomBanks) {
+      printf("\trequested rom bank %d when there are only %d banks!\n", value, globalMemState.nRomBanks);
+      assert(false);
+    }
+
+    if(value == 0) value = 1;
+    globalMemState.mappedRom = fileData + 0x4000 * value;
+  } else if(addr >= 0 && addr < 0x1fff) {
+    // RAM enable/disable
+    if(value == 0) {
+      globalMemState.disabledMappedRam = globalMemState.mappedRam;
+      globalMemState.mappedRam = nullptr;
+    } else if(value == 0xa) {
+      globalMemState.mappedRam = globalMemState.disabledMappedRam;
+    } else {
+      //assert(false);
+    }
+  } else if(addr >= 0x4000 && addr < 0x5fff) {
+    // RAM bank switch
+    if(value < globalMemState.nRamBanks) {
+      globalMemState.mappedRam = globalMemState.mappedRamAllocation + 0x2000 * value;
+    } else {
+      //assert(false);
+    }
+  } else if(addr == 0x6000) {
+    // ?? RTC latch nonsense
+  } else {
+    assert(false);
+  }
+}
+
+// handler for all MBC switches
+void mbcHandler(u16 addr, u8 value) {
+  switch(globalMemState.mbcType) {
+    case 0:
+      mbc0Handler(addr, value);
+      break;
+    case 1:
+      mbc1Handler(addr, value);
+      break;
+    case 2:
+      mbc2Handler(addr, value);
+      break;
+    case 3:
+      mbc3Handler(addr, value);
+      break;
+    default:
+      assert(false);
+      break;
+  }
+}
+
+// read a u16 from game memory
+u16 readU16(u16 addr) {
+  return (u16)readByte(addr) + ((u16)(readByte(addr+(u16)1)) << 8);
+}
+
+// write a u16 to game memory
+void writeU16(u16 mem, u16 addr) {
+  writeByte((u8)(mem & 0xff), addr);
+  writeByte((u8)(mem >> 8),  addr + (u16)1);
+}
+
+// read byte from memory
+u8 readByte(u16 addr) {
+  switch(addr & 0xf000) {
+    case 0x0000: // either BIOS or ROM 0:
+      if(globalMemState.inBios) {
+        if(addr < 0x100) {
+          return bios[addr];
+        } else if(addr == 0x100) {
+          printf("EXIT BIOS ERROR\n");
+          assert(false);
+        } else {
+          return globalMemState.rom0[addr]; // todo <- change this for stm32
+        }
+      } else {
+        return globalMemState.rom0[addr];   // todo <- change this for stm32
+      }
+
+    case 0x1000: // ROM 0
+    case 0x2000: // ROM 0
+    case 0x3000: // ROM 0
+      return globalMemState.rom0[addr];    // todo <- change this for stm32
+
+    case 0x4000: // banked ROM
+    case 0x5000:
+    case 0x6000:
+    case 0x7000:
+      return globalMemState.mappedRom[addr & 0x3fff]; // todo <- change this for stm32
+
+    case 0x8000: // VRAM
+    case 0x9000:
+      return globalMemState.vram[addr & 0x1fff];
+
+    case 0xa000: // mapped RAM
+    case 0xb000:
+      if(!globalMemState.mappedRam) {
+#ifndef DANGER_MODE
+        assert(false);
+#endif
+        return 0xff;
+      }
+      return globalMemState.mappedRam[addr & 0x1fff];
+
+    case 0xc000: // internal RAM
+    case 0xd000:
+      return globalMemState.internalRam[addr & 0x1fff];
+
+    case 0xe000: // interal RAM copy
+      return globalMemState.internalRam[addr & 0x1fff];
+
+    case 0xf000: // either internal RAM copy or I/O or top-ram
+      switch(addr & 0x0f00) {
+        case 0x000:
+        case 0x100:
+        case 0x200:
+        case 0x300:
+        case 0x400:
+        case 0x500:
+        case 0x600:
+        case 0x700:
+        case 0x800:
+        case 0x900:
+        case 0xa00:
+        case 0xb00:
+        case 0xc00:
+        case 0xd00:
+        case 0xe00:
+          return globalMemState.internalRam[addr & 0x1fff];
+
+
+        case 0xf00:
+          if(addr >= 0xff80) {
+            return globalMemState.upperRam[addr & 0x7f];
+          } else {
+            u8 lowAddr = (u8)(addr & 0xff);
+            switch(lowAddr) {
+              case IO_LY:
+              case IO_SCROLLX:
+              case IO_SCROLLY:
+              case IO_NR10: // nyi
+              case IO_NR11: // nyi
+              case IO_NR12: // nyi
+              case IO_NR13: // nyi
+              case IO_NR14: // nyi
+              case IO_NR21: // nyi
+              case IO_NR22: // nyi
+              case IO_NR23: // nyi
+              case IO_NR24: // nyi
+              case IO_NR30: // nyi
+              case IO_NR31: // nyi
+              case IO_NR32: // nyi
+              case IO_NR33: // nyi
+              case IO_NR34: // nyi
+              case IO_NR41: // nyi
+              case IO_NR42: // nyi
+              case IO_NR43: // nyi
+              case IO_NR44: // nyi
+              case IO_NR50: // nyi
+              case IO_NR51: // nyi
+              case IO_NR52: // nyi
+              case IO_STAT: // nyi
+              case IO_WAVE_PATTERN: // nyi
+              case IO_LCDC: // nyi
+              case IO_BGP:
+              case IO_OBP0:
+              case IO_OBP1:
+              case IO_SERIAL_SB:
+              case IO_SERIAL_SC:
+              case IO_DIV:
+              case IO_TIMA:
+              case IO_TMA:
+              case IO_TAC:
+              case IO_WINY:
+              case IO_WINX:
+                return globalMemState.ioRegs[lowAddr];
+                break;
+
+              case IO_IF:
+                printf("read if: 0x%x\n", globalMemState.ioRegs[lowAddr]);
+                printf("timer value: 0x%x\n", globalMemState.ioRegs[IO_TIMA]);
+                return globalMemState.ioRegs[lowAddr];
+                break;
+
+
+              case IO_P1:
+              {
+                u8 regP1 = globalMemState.ioRegs[IO_P1];
+                //printf("ireg: 0x%x\n", regP1);
+                u8 joypad_data = 0;
+                if(regP1 & 0x10) {
+                  if(keyboard.a) joypad_data += 0x1;
+                  if(keyboard.b) joypad_data += 0x2;
+                  if(keyboard.select) joypad_data += 0x4;
+                  if(keyboard.start) joypad_data += 0x8;
+                }
+                if(regP1 & 0x20) {
+                  if(keyboard.r) joypad_data += 0x1;
+                  if(keyboard.l) joypad_data += 0x2;
+                  if(keyboard.u) joypad_data += 0x4;
+                  if(keyboard.d) joypad_data += 0x8;
+                }
+
+                regP1 = (regP1 & 0xf0);
+                joypad_data = ~joypad_data;
+                joypad_data = regP1 + (joypad_data & 0xf);
+                //globalMemState.ioRegs[IO_P1] = joypad_data;
+                //printf("jpd: 0x%x\n", joypad_data);
+                return joypad_data;
+              }
+
+                break;
+
+              case IO_GBCSPEED:
+                return 0xff;
+
+              case IO_LYC:
+              case IO_DMA:
+
+                printf("unhandled I/O read @ 0x%x\n", addr);
+#ifndef DANGER_MODE
+                assert(false);
+#endif
+                break;
+              default:
+                printf("unknown I/O read @ 0x%x\n", addr);
+#ifndef DANGER_MODE
+                assert(false);
+#endif
+                break;
+            }
+
+          }
+        default:
+#ifndef DANGER_MODE
+          assert(false);
+#endif
+          break;
+
+      }
+      break;
+
+    default:
+#ifndef DANGER_MODE
+      assert(false);
+#endif
+      break;
+  }
+  assert(false);
+  return 0xff;
+}
+
+
+void writeByte(u8 byte, u16 addr) {
+  switch(addr & 0xf000) {
+    case 0x0000: // ROM 0, but possibly the BIOS area
+      if(globalMemState.inBios) {
+        printf("ERROR: tried to write into ROM0 or BIOS (@ 0x%04x) during BIOS!\n", addr);
+#ifndef DANGER_MODE
+        assert(false);
+#endif
+      } else {
+        mbcHandler(addr, byte);
+      }
+      break;
+
+
+    case 0x1000: // ROM 0
+    case 0x2000: // ROM 0
+    case 0x3000: // ROM 0
+    case 0x4000: // ROM 1
+    case 0x5000: // ROM 1
+    case 0x6000: // ROM 1
+    case 0x7000: // ROM 1
+      mbcHandler(addr, byte);
+      break;
+
+    case 0x8000: // VRAM
+    case 0x9000:
+      globalMemState.vram[addr & 0x1fff] = byte;
+      break;
+
+    case 0xa000: // mapped RAM
+    case 0xb000:
+      if(!globalMemState.mappedRam) {
+        //printf("write to unmapped ram @ 0x%x value 0x%x\n", addr, byte);
+//#ifndef DANGER_MODE
+//        assert(false);
+//#endif
+        break;
+      }
+      globalMemState.mappedRam[addr & 0x1fff] = byte;
+      break;
+
+    case 0xc000: // internal RAM
+    case 0xd000:
+      globalMemState.internalRam[addr & 0x1fff] = byte;
+      break;
+
+    case 0xe000: // interal RAM copy
+      globalMemState.internalRam[addr & 0x1fff] = byte;
+      break;
+
+    case 0xf000: // either internal RAM copy or I/O or top-ram
+      switch(addr & 0x0f00) {
+        case 0x000:
+        case 0x100:
+        case 0x200:
+        case 0x300:
+        case 0x400:
+        case 0x500:
+        case 0x600:
+        case 0x700:
+        case 0x800:
+        case 0x900:
+        case 0xa00:
+        case 0xb00:
+        case 0xc00:
+        case 0xd00:
+        case 0xe00:
+          globalMemState.internalRam[addr & 0x1fff] = byte;
+          break;
+
+
+        case 0xf00:
+          if(addr >= 0xff80) {
+            globalMemState.upperRam[addr & 0x7f] = byte;
+            break;
+          } else {
+            u16 maskedAddress = addr & 0x7f;
+            globalMemState.ioRegs[maskedAddress] = byte;
+            u8 lowAddr = (u8)(addr & 0xff);
+            switch(lowAddr) {
+
+              case IO_NR10:
+              case IO_NR11:
+              case IO_NR12:
+              case IO_NR13:
+              case IO_NR14:
+              case IO_NR21:
+              case IO_NR22:
+              case IO_NR23:
+              case IO_NR24:
+              case IO_NR30:
+              case IO_NR31:
+              case IO_NR32:
+              case IO_NR33:
+              case IO_NR34:
+              case IO_NR41:
+              case IO_NR42:
+              case IO_NR43:
+              case IO_NR44:
+              case IO_NR50:
+              case IO_NR51:
+              case IO_NR52:
+              case IO_WAVE_PATTERN:
+              case 0x31:
+              case 0x32:
+              case 0x33:
+              case 0x34:
+              case 0x35:
+              case 0x36:
+              case 0x37:
+              case 0x38:
+              case 0x39:
+              case 0x3a:
+              case 0x3b:
+              case 0x3c:
+              case 0x3d:
+              case 0x3e:
+              case 0x3f:
+              case IO_BGP:
+              case IO_SCROLLX:
+              case IO_SCROLLY:
+              case IO_LCDC:
+              case IO_STAT:
+              case IO_OBP0:
+              case IO_OBP1:
+              case IO_P1:
+              case IO_IF:
+              case IO_TAC:
+              case IO_TIMA:
+              case IO_TMA:
+              case IO_SERIAL_SB:
+              case IO_SERIAL_SC:
+              case IO_WINY:
+              case IO_WINX:
+              case IO_LYC:
+                break;
+
+              case IO_EXIT_BIOS:
+                if(globalMemState.inBios) {
+                  printf("EXIT BIOS by write 0x%x to 0x%x", byte, addr);
+                  globalMemState.inBios = false;
+                  break;
+                } else {
+                  printf("tried to write to 0xff50 when not in bios?\n");
+                  break;
+                }
+                break;
+
+              case IO_DMA:
+              {
+                u16 dmaAddr = ((u16)byte) << 8;
+                for(u16 i = 0; i < 160; i++) {
+                  writeByte(readByte(dmaAddr + i), (u16)0xfe00 + i);
+                }
+                break;
+              }
+
+              case 0x7f:
+                printf("OOPS\n");
+                break;
+
+
+              case IO_LY:
+                printf("unhandled I/O write @ 0x%x\n", addr);
+#ifndef DANGER_MODE
+                assert(false);
+#endif
+                break;
+              default:
+                printf("unknown I/O write @ 0x%x\n", addr);
+#ifndef DANGER_MODE
+                assert(false);
+#endif
+                break;
+            }
+            break;
+          }
+        default:
+#ifndef DANGER_MODE
+          assert(false);
+#endif
+          break;
+      }
+      break;
+    default:
+#ifndef DANGER_MODE
+      assert(false);
+#endif
+      break;
+  }
+}
\ No newline at end of file