/* Copyright (c) 2013-2016 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "memory.h" #include "core/interface.h" #include "gb/gb.h" #include "gb/io.h" #include "util/memory.h" #include mLOG_DEFINE_CATEGORY(GB_MBC); mLOG_DEFINE_CATEGORY(GB_MEM); static void _GBMBCNone(struct GBMemory* memory, uint16_t address, uint8_t value) { UNUSED(memory); UNUSED(address); UNUSED(value); mLOG(GB_MBC, GAME_ERROR, "Wrote to invalid MBC"); } static void _GBMBC1(struct GBMemory*, uint16_t address, uint8_t value); static void _GBMBC2(struct GBMemory*, uint16_t address, uint8_t value); static void _GBMBC3(struct GBMemory*, uint16_t address, uint8_t value); static void _GBMBC4(struct GBMemory*, uint16_t address, uint8_t value); static void _GBMBC5(struct GBMemory*, uint16_t address, uint8_t value); static void _GBMBC7(struct GBMemory*, uint16_t address, uint8_t value); static void GBSetActiveRegion(struct LR35902Core* cpu, uint16_t address) { // TODO } static void _GBMemoryDMAService(struct GB* gb); void GBMemoryInit(struct GB* gb) { struct LR35902Core* cpu = gb->cpu; cpu->memory.cpuLoad8 = GBLoad8; cpu->memory.load8 = GBLoad8; cpu->memory.store8 = GBStore8; cpu->memory.setActiveRegion = GBSetActiveRegion; gb->memory.wram = 0; gb->memory.wramBank = 0; gb->memory.rom = 0; gb->memory.romBank = 0; gb->memory.romSize = 0; gb->memory.mbcType = GB_MBC_NONE; gb->memory.mbc = 0; gb->memory.dmaNext = INT_MAX; gb->memory.dmaRemaining = 0; memset(gb->memory.hram, 0, sizeof(gb->memory.hram)); gb->memory.sramAccess = false; gb->memory.rtcAccess = false; gb->memory.rtcLatched = 0; gb->memory.rtc = NULL; GBIOInit(gb); } void GBMemoryDeinit(struct GB* gb) { mappedMemoryFree(gb->memory.wram, GB_SIZE_WORKING_RAM); if (gb->memory.rom) { mappedMemoryFree(gb->memory.rom, gb->memory.romSize); } } void GBMemoryReset(struct GB* gb) { if (gb->memory.wram) { mappedMemoryFree(gb->memory.wram, GB_SIZE_WORKING_RAM); } gb->memory.wram = anonymousMemoryMap(GB_SIZE_WORKING_RAM); gb->memory.wramBank = &gb->memory.wram[GB_SIZE_WORKING_RAM_BANK0]; gb->memory.romBank = &gb->memory.rom[GB_SIZE_CART_BANK0]; gb->memory.currentBank = 1; gb->memory.sramCurrentBank = 0; gb->memory.sramBank = gb->memory.sram; memset(&gb->video.oam, 0, sizeof(gb->video.oam)); const struct GBCartridge* cart = &gb->memory.rom[0x100]; switch (cart->type) { case 0: case 8: case 9: gb->memory.mbc = _GBMBCNone; gb->memory.mbcType = GB_MBC_NONE; break; case 1: case 2: case 3: gb->memory.mbc = _GBMBC1; gb->memory.mbcType = GB_MBC1; break; case 5: case 6: gb->memory.mbc = _GBMBC2; gb->memory.mbcType = GB_MBC2; break; case 0x0F: case 0x10: case 0x11: case 0x12: case 0x13: gb->memory.mbc = _GBMBC3; gb->memory.mbcType = GB_MBC3; break; case 0x15: case 0x16: case 0x17: gb->memory.mbc = _GBMBC4; gb->memory.mbcType = GB_MBC4; break; default: mLOG(GB_MBC, WARN, "Unknown MBC type: %02X", cart->type); case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: gb->memory.mbc = _GBMBC5; gb->memory.mbcType = GB_MBC5; break; case 0x22: gb->memory.mbc = _GBMBC7; gb->memory.mbcType = GB_MBC7; break; } if (!gb->memory.wram) { GBMemoryDeinit(gb); } } uint8_t GBLoad8(struct LR35902Core* cpu, uint16_t address) { struct GB* gb = (struct GB*) cpu->master; struct GBMemory* memory = &gb->memory; switch (address >> 12) { case GB_REGION_CART_BANK0: case GB_REGION_CART_BANK0 + 1: case GB_REGION_CART_BANK0 + 2: case GB_REGION_CART_BANK0 + 3: return memory->rom[address & (GB_SIZE_CART_BANK0 - 1)]; case GB_REGION_CART_BANK1: case GB_REGION_CART_BANK1 + 1: case GB_REGION_CART_BANK1 + 2: case GB_REGION_CART_BANK1 + 3: return memory->romBank[address & (GB_SIZE_CART_BANK0 - 1)]; case GB_REGION_VRAM: case GB_REGION_VRAM + 1: return gb->video.vram[address & (GB_SIZE_VRAM - 1)]; case GB_REGION_EXTERNAL_RAM: case GB_REGION_EXTERNAL_RAM + 1: if (memory->rtcAccess) { return gb->memory.rtcRegs[memory->activeRtcReg]; } else if (memory->sramAccess) { return gb->memory.sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)]; } return 0xFF; case GB_REGION_WORKING_RAM_BANK0: case GB_REGION_WORKING_RAM_BANK0 + 2: return memory->wram[address & (GB_SIZE_WORKING_RAM_BANK0 - 1)]; case GB_REGION_WORKING_RAM_BANK1: return memory->wramBank[address & (GB_SIZE_WORKING_RAM_BANK0 - 1)]; default: if (address < GB_BASE_OAM) { return memory->wramBank[address & (GB_SIZE_WORKING_RAM_BANK0 - 1)]; } if (address < GB_BASE_UNUSABLE) { if (gb->video.mode < 2) { return gb->video.oam.raw[address & 0xFF]; } return 0xFF; } if (address < GB_BASE_IO) { mLOG(GB_MEM, GAME_ERROR, "Attempt to read from unusable memory: %04X", address); return 0xFF; } if (address < GB_BASE_HRAM) { return GBIORead(gb, address & (GB_SIZE_IO - 1)); } if (address < GB_BASE_IE) { return memory->hram[address & GB_SIZE_HRAM]; } return GBIORead(gb, REG_IE); } } void GBStore8(struct LR35902Core* cpu, uint16_t address, int8_t value) { struct GB* gb = (struct GB*) cpu->master; struct GBMemory* memory = &gb->memory; switch (address >> 12) { case GB_REGION_CART_BANK0: case GB_REGION_CART_BANK0 + 1: case GB_REGION_CART_BANK0 + 2: case GB_REGION_CART_BANK0 + 3: case GB_REGION_CART_BANK1: case GB_REGION_CART_BANK1 + 1: case GB_REGION_CART_BANK1 + 2: case GB_REGION_CART_BANK1 + 3: memory->mbc(memory, address, value); return; case GB_REGION_VRAM: case GB_REGION_VRAM + 1: // TODO: Block access in wrong modes gb->video.vram[address & (GB_SIZE_VRAM - 1)] = value; return; case GB_REGION_EXTERNAL_RAM: case GB_REGION_EXTERNAL_RAM + 1: if (memory->rtcAccess) { gb->memory.rtcRegs[memory->activeRtcReg] = value; } else if (memory->sramAccess) { gb->memory.sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)] = value; } return; case GB_REGION_WORKING_RAM_BANK0: case GB_REGION_WORKING_RAM_BANK0 + 2: memory->wram[address & (GB_SIZE_WORKING_RAM_BANK0 - 1)] = value; return; case GB_REGION_WORKING_RAM_BANK1: memory->wramBank[address & (GB_SIZE_WORKING_RAM_BANK0 - 1)] = value; return; default: if (address < GB_BASE_OAM) { memory->wramBank[address & (GB_SIZE_WORKING_RAM_BANK0 - 1)] = value; } else if (address < GB_BASE_UNUSABLE) { if (gb->video.mode < 2) { gb->video.oam.raw[address & 0xFF] = value; } } else if (address < GB_BASE_IO) { mLOG(GB_MEM, GAME_ERROR, "Attempt to write to unusable memory: %04X:%02X", address, value); } else if (address < GB_BASE_HRAM) { GBIOWrite(gb, address & (GB_SIZE_IO - 1), value); } else if (address < GB_BASE_IE) { memory->hram[address & GB_SIZE_HRAM] = value; } else { GBIOWrite(gb, REG_IE, value); } } } int32_t GBMemoryProcessEvents(struct GB* gb, int32_t cycles) { if (!gb->memory.dmaRemaining) { return INT_MAX; } gb->memory.dmaNext -= cycles; if (gb->memory.dmaNext <= 0) { _GBMemoryDMAService(gb); } return gb->memory.dmaNext; } void GBMemoryDMA(struct GB* gb, uint16_t base) { if (base > 0xF100) { return; } gb->cpu->memory.store8 = GBDMAStore8; gb->cpu->memory.load8 = GBDMALoad8; gb->memory.dmaNext = gb->cpu->cycles + 8; if (gb->memory.dmaNext < gb->cpu->nextEvent) { gb->cpu->nextEvent = gb->memory.dmaNext; } gb->memory.dmaSource = base; gb->memory.dmaDest = 0; gb->memory.dmaRemaining = 0xA0; } void _GBMemoryDMAService(struct GB* gb) { uint8_t b = GBLoad8(gb->cpu, gb->memory.dmaSource); // TODO: Can DMA write OAM during modes 2-3? gb->video.oam.raw[gb->memory.dmaDest] = b; ++gb->memory.dmaSource; ++gb->memory.dmaDest; --gb->memory.dmaRemaining; if (gb->memory.dmaRemaining) { gb->memory.dmaNext += 4; } else { gb->memory.dmaNext = INT_MAX; gb->cpu->memory.store8 = GBStore8; gb->cpu->memory.load8 = GBLoad8; } } uint8_t GBDMALoad8(struct LR35902Core* cpu, uint16_t address) { struct GB* gb = (struct GB*) cpu->master; struct GBMemory* memory = &gb->memory; if (address < 0xFF80 || address == 0xFFFF) { return 0xFF; } return memory->hram[address & GB_SIZE_HRAM]; } void GBDMAStore8(struct LR35902Core* cpu, uint16_t address, int8_t value) { struct GB* gb = (struct GB*) cpu->master; struct GBMemory* memory = &gb->memory; if (address < 0xFF80 || address == 0xFFFF) { return; } memory->hram[address & GB_SIZE_HRAM] = value; } uint8_t GBView8(struct LR35902Core* cpu, uint16_t address); void GBPatch8(struct LR35902Core* cpu, uint16_t address, int8_t value, int8_t* old); static void _switchBank(struct GBMemory* memory, int bank) { size_t bankStart = bank * GB_SIZE_CART_BANK0; if (bankStart + GB_SIZE_CART_BANK0 > memory->romSize) { mLOG(GB_MBC, GAME_ERROR, "Attempting to switch to an invalid ROM bank: %0X", bank); return; } memory->romBank = &memory->rom[bankStart]; memory->currentBank = bank; } static void _switchSramBank(struct GBMemory* memory, int bank) { size_t bankStart = bank * GB_SIZE_EXTERNAL_RAM; memory->sramBank = &memory->sram[bankStart]; memory->sramCurrentBank = bank; } static void _latchRtc(struct GBMemory* memory) { time_t t; struct mRTCSource* rtc = memory->rtc; if (rtc) { if (rtc->sample) { rtc->sample(rtc); } t = rtc->unixTime(rtc); } else { t = time(0); } struct tm date; localtime_r(&t, &date); memory->rtcRegs[0] = date.tm_sec; memory->rtcRegs[1] = date.tm_min; memory->rtcRegs[2] = date.tm_hour; memory->rtcRegs[3] = date.tm_yday; // TODO: Persist day counter memory->rtcRegs[4] &= 0xF0; memory->rtcRegs[4] |= date.tm_yday >> 8; } void _GBMBC1(struct GBMemory* memory, uint16_t address, uint8_t value) { int bank = value & 0x1F; switch (address >> 13) { case 0x0: switch (value) { case 0: memory->sramAccess = false; break; case 0xA: memory->sramAccess = true; _switchSramBank(memory, memory->sramCurrentBank); break; default: // TODO mLOG(GB_MBC, STUB, "MBC1 unknown value %02X", value); break; } break; case 0x1: if (!bank) { ++bank; } _switchBank(memory, bank | (memory->currentBank & 0x60)); break; default: // TODO mLOG(GB_MBC, STUB, "MBC1 unknown address: %04X:%02X", address, value); break; } } void _GBMBC2(struct GBMemory* memory, uint16_t address, uint8_t value) { mLOG(GB_MBC, STUB, "MBC2 unimplemented"); } void _GBMBC3(struct GBMemory* memory, uint16_t address, uint8_t value) { int bank = value & 0x7F; switch (address >> 13) { case 0x0: switch (value) { case 0: memory->sramAccess = false; break; case 0xA: memory->sramAccess = true; _switchSramBank(memory, memory->sramCurrentBank); break; default: // TODO mLOG(GB_MBC, STUB, "MBC3 unknown value %02X", value); break; } break; case 0x1: if (!bank) { ++bank; } _switchBank(memory, bank); break; case 0x2: if (value < 4) { _switchSramBank(memory, value); memory->rtcAccess = false; } else if (value >= 8 && value <= 0xC) { memory->activeRtcReg = value - 8; memory->rtcAccess = true; } break; case 0x3: if (memory->rtcLatched && value == 0) { memory->rtcLatched = value; } else if (!memory->rtcLatched && value == 1) { _latchRtc(memory); } break; } } void _GBMBC4(struct GBMemory* memory, uint16_t address, uint8_t value) { // TODO mLOG(GB_MBC, STUB, "MBC4 unimplemented"); } void _GBMBC5(struct GBMemory* memory, uint16_t address, uint8_t value) { int bank = value & 0x7F; switch (address >> 13) { case 0x0: switch (value) { case 0: memory->sramAccess = false; break; case 0xA: memory->sramAccess = true; _switchSramBank(memory, memory->sramCurrentBank); break; default: // TODO mLOG(GB_MBC, STUB, "MBC5 unknown value %02X", value); break; } break; case 0x1: _switchBank(memory, bank); break; case 0x2: if (value < 0x10) { _switchSramBank(memory, value); } break; default: // TODO mLOG(GB_MBC, STUB, "MBC5 unknown address: %04X:%02X", address, value); break; } } void _GBMBC7(struct GBMemory* memory, uint16_t address, uint8_t value) { // TODO mLOG(GB_MBC, STUB, "MBC7 unimplemented"); }