gameboy wormboy manboy gameworm gameman wormgame mangame manworm
Dependencies: mbed SDFileSystem2
Diff: mem.cpp
- 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