gameboy wormboy manboy gameworm gameman wormgame mangame manworm

Dependencies:   mbed SDFileSystem2

mem.cpp

Committer:
dicarloj
Date:
2019-01-13
Revision:
17:c9afe1a7b423

File content as of revision 17:c9afe1a7b423:

#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;
  }
}