/*************************************************************************** * Copyright (C) 2007 by Sindre AamÄs * * aamas@stud.ntnu.no * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License version 2 as * * published by the Free Software Foundation. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License version 2 for more details. * * * * You should have received a copy of the GNU General Public License * * version 2 along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "memory.h" #include "video.h" #include "sound.h" #include "savestate.h" #include namespace gambatte { Memory::Memory(const Interrupter &interrupter_in) : readCallback(0), writeCallback(0), execCallback(0), cdCallback(0), getInput(0), divLastUpdate(0), lastOamDmaUpdate(DISABLED_TIME), display(ioamhram, 0, VideoInterruptRequester(&intreq)), interrupter(interrupter_in), dmaSource(0), dmaDestination(0), oamDmaPos(0xFE), serialCnt(0), blanklcd(false), LINKCABLE(false), linkClockTrigger(false) { intreq.setEventTime(144*456ul); intreq.setEventTime(0); } void Memory::setStatePtrs(SaveState &state) { state.mem.ioamhram.set(ioamhram, sizeof ioamhram); cart.setStatePtrs(state); display.setStatePtrs(state); sound.setStatePtrs(state); } static inline int serialCntFrom(const unsigned long cyclesUntilDone, const bool cgbFast) { return cgbFast ? (cyclesUntilDone + 0xF) >> 4 : (cyclesUntilDone + 0x1FF) >> 9; } void Memory::loadState(const SaveState &state) { sound.loadState(state); display.loadState(state, state.mem.oamDmaPos < 0xA0 ? cart.rdisabledRam() : ioamhram); tima.loadState(state, TimaInterruptRequester(intreq)); cart.loadState(state); intreq.loadState(state); divLastUpdate = state.mem.divLastUpdate; intreq.setEventTime(state.mem.nextSerialtime > state.cpu.cycleCounter ? state.mem.nextSerialtime : state.cpu.cycleCounter); intreq.setEventTime(state.mem.unhaltTime); lastOamDmaUpdate = state.mem.lastOamDmaUpdate; dmaSource = state.mem.dmaSource; dmaDestination = state.mem.dmaDestination; oamDmaPos = state.mem.oamDmaPos; serialCnt = intreq.eventTime(SERIAL) != DISABLED_TIME ? serialCntFrom(intreq.eventTime(SERIAL) - state.cpu.cycleCounter, ioamhram[0x102] & isCgb() * 2) : 8; cart.setVrambank(ioamhram[0x14F] & isCgb()); cart.setOamDmaSrc(OAM_DMA_SRC_OFF); cart.setWrambank(isCgb() && (ioamhram[0x170] & 0x07) ? ioamhram[0x170] & 0x07 : 1); if (lastOamDmaUpdate != DISABLED_TIME) { oamDmaInitSetup(); const unsigned oamEventPos = oamDmaPos < 0xA0 ? 0xA0 : 0x100; intreq.setEventTime(lastOamDmaUpdate + (oamEventPos - oamDmaPos) * 4); } intreq.setEventTime((ioamhram[0x140] & 0x80) ? display.nextMode1IrqTime() : state.cpu.cycleCounter); blanklcd = false; if (!isCgb()) std::memset(cart.vramdata() + 0x2000, 0, 0x2000); } void Memory::setEndtime(const unsigned long cycleCounter, const unsigned long inc) { if (intreq.eventTime(BLIT) <= cycleCounter) intreq.setEventTime(intreq.eventTime(BLIT) + (70224 << isDoubleSpeed())); intreq.setEventTime(cycleCounter + (inc << isDoubleSpeed())); } void Memory::updateSerial(const unsigned long cc) { if (!LINKCABLE) { if (intreq.eventTime(SERIAL) != DISABLED_TIME) { if (intreq.eventTime(SERIAL) <= cc) { ioamhram[0x101] = (((ioamhram[0x101] + 1) << serialCnt) - 1) & 0xFF; ioamhram[0x102] &= 0x7F; intreq.setEventTime(DISABLED_TIME); intreq.flagIrq(8); } else { const int targetCnt = serialCntFrom(intreq.eventTime(SERIAL) - cc, ioamhram[0x102] & isCgb() * 2); ioamhram[0x101] = (((ioamhram[0x101] + 1) << (serialCnt - targetCnt)) - 1) & 0xFF; serialCnt = targetCnt; } } } else { if (intreq.eventTime(SERIAL) != DISABLED_TIME) { if (intreq.eventTime(SERIAL) <= cc) { linkClockTrigger = true; intreq.setEventTime(DISABLED_TIME); } } } } void Memory::updateTimaIrq(const unsigned long cc) { while (intreq.eventTime(TIMA) <= cc) tima.doIrqEvent(TimaInterruptRequester(intreq)); } void Memory::updateIrqs(const unsigned long cc) { updateSerial(cc); updateTimaIrq(cc); display.update(cc); } unsigned long Memory::event(unsigned long cycleCounter) { if (lastOamDmaUpdate != DISABLED_TIME) updateOamDma(cycleCounter); switch (intreq.minEventId()) { case UNHALT: intreq.unhalt(); intreq.setEventTime(DISABLED_TIME); break; case END: intreq.setEventTime(DISABLED_TIME - 1); while (cycleCounter >= intreq.minEventTime() && intreq.eventTime(END) != DISABLED_TIME) cycleCounter = event(cycleCounter); intreq.setEventTime(DISABLED_TIME); break; case BLIT: { const bool lcden = ioamhram[0x140] >> 7 & 1; unsigned long blitTime = intreq.eventTime(BLIT); if (lcden | blanklcd) { display.updateScreen(blanklcd, cycleCounter); intreq.setEventTime(DISABLED_TIME); intreq.setEventTime(DISABLED_TIME); while (cycleCounter >= intreq.minEventTime()) cycleCounter = event(cycleCounter); } else blitTime += 70224 << isDoubleSpeed(); blanklcd = lcden ^ 1; intreq.setEventTime(blitTime); } break; case SERIAL: updateSerial(cycleCounter); break; case OAM: intreq.setEventTime(lastOamDmaUpdate == DISABLED_TIME ? static_cast(DISABLED_TIME) : intreq.eventTime(OAM) + 0xA0 * 4); break; case DMA: { const bool doubleSpeed = isDoubleSpeed(); unsigned dmaSrc = dmaSource; unsigned dmaDest = dmaDestination; unsigned dmaLength = ((ioamhram[0x155] & 0x7F) + 0x1) * 0x10; unsigned length = hdmaReqFlagged(intreq) ? 0x10 : dmaLength; ackDmaReq(&intreq); if ((static_cast(dmaDest) + length) & 0x10000) { length = 0x10000 - dmaDest; ioamhram[0x155] |= 0x80; } dmaLength -= length; if (!(ioamhram[0x140] & 0x80)) dmaLength = 0; { unsigned long lOamDmaUpdate = lastOamDmaUpdate; lastOamDmaUpdate = DISABLED_TIME; while (length--) { const unsigned src = dmaSrc++ & 0xFFFF; const unsigned data = ((src & 0xE000) == 0x8000 || src > 0xFDFF) ? 0xFF : read(src, cycleCounter); cycleCounter += 2 << doubleSpeed; if (cycleCounter - 3 > lOamDmaUpdate) { oamDmaPos = (oamDmaPos + 1) & 0xFF; lOamDmaUpdate += 4; if (oamDmaPos < 0xA0) { if (oamDmaPos == 0) startOamDma(lOamDmaUpdate - 1); ioamhram[src & 0xFF] = data; } else if (oamDmaPos == 0xA0) { endOamDma(lOamDmaUpdate - 1); lOamDmaUpdate = DISABLED_TIME; } } nontrivial_write(0x8000 | (dmaDest++ & 0x1FFF), data, cycleCounter); } lastOamDmaUpdate = lOamDmaUpdate; } cycleCounter += 4; dmaSource = dmaSrc; dmaDestination = dmaDest; ioamhram[0x155] = ((dmaLength / 0x10 - 0x1) & 0xFF) | (ioamhram[0x155] & 0x80); if ((ioamhram[0x155] & 0x80) && display.hdmaIsEnabled()) { if (lastOamDmaUpdate != DISABLED_TIME) updateOamDma(cycleCounter); display.disableHdma(cycleCounter); } } break; case TIMA: tima.doIrqEvent(TimaInterruptRequester(intreq)); break; case VIDEO: display.update(cycleCounter); break; case INTERRUPTS: if (halted()) { if (isCgb()) cycleCounter += 4; intreq.unhalt(); intreq.setEventTime(DISABLED_TIME); } if (ime()) { unsigned address; const unsigned pendingIrqs = intreq.pendingIrqs(); const unsigned n = pendingIrqs & -pendingIrqs; if (n < 8) { static const unsigned char lut[] = { 0x40, 0x48, 0x48, 0x50 }; address = lut[n-1]; } else address = 0x50 + n; intreq.ackIrq(n); cycleCounter = interrupter.interrupt(address, cycleCounter, *this); } break; } return cycleCounter; } unsigned long Memory::stop(unsigned long cycleCounter) { cycleCounter += 4 << isDoubleSpeed(); if (ioamhram[0x14D] & isCgb()) { sound.generate_samples(cycleCounter, isDoubleSpeed()); display.speedChange(cycleCounter); ioamhram[0x14D] ^= 0x81; intreq.setEventTime((ioamhram[0x140] & 0x80) ? display.nextMode1IrqTime() : cycleCounter + (70224 << isDoubleSpeed())); if (intreq.eventTime(END) > cycleCounter) { intreq.setEventTime(cycleCounter + (isDoubleSpeed() ? (intreq.eventTime(END) - cycleCounter) << 1 : (intreq.eventTime(END) - cycleCounter) >> 1)); } // when switching speed, it seems that the CPU spontaneously restarts soon? // otherwise, the cpu should be allowed to stay halted as long as needed // so only execute this line when switching speed intreq.setEventTime(cycleCounter + 0x20000 + isDoubleSpeed() * 8); } intreq.halt(); return cycleCounter; } static void decCycles(unsigned long &counter, const unsigned long dec) { if (counter != DISABLED_TIME) counter -= dec; } void Memory::decEventCycles(const MemEventId eventId, const unsigned long dec) { if (intreq.eventTime(eventId) != DISABLED_TIME) intreq.setEventTime(eventId, intreq.eventTime(eventId) - dec); } unsigned long Memory::resetCounters(unsigned long cycleCounter) { if (lastOamDmaUpdate != DISABLED_TIME) updateOamDma(cycleCounter); updateIrqs(cycleCounter); const unsigned long oldCC = cycleCounter; { const unsigned long divinc = (cycleCounter - divLastUpdate) >> 8; ioamhram[0x104] = (ioamhram[0x104] + divinc) & 0xFF; divLastUpdate += divinc << 8; } const unsigned long dec = cycleCounter < 0x10000 ? 0 : (cycleCounter & ~0x7FFFul) - 0x8000; decCycles(divLastUpdate, dec); decCycles(lastOamDmaUpdate, dec); decEventCycles(SERIAL, dec); decEventCycles(OAM, dec); decEventCycles(BLIT, dec); decEventCycles(END, dec); decEventCycles(UNHALT, dec); cycleCounter -= dec; intreq.resetCc(oldCC, cycleCounter); tima.resetCc(oldCC, cycleCounter, TimaInterruptRequester(intreq)); display.resetCc(oldCC, cycleCounter); sound.resetCounter(cycleCounter, oldCC, isDoubleSpeed()); return cycleCounter; } void Memory::updateInput() { unsigned state = 0xF; if ((ioamhram[0x100] & 0x30) != 0x30 && getInput) { unsigned input = (*getInput)(); unsigned dpad_state = ~input >> 4; unsigned button_state = ~input; if (!(ioamhram[0x100] & 0x10)) state &= dpad_state; if (!(ioamhram[0x100] & 0x20)) state &= button_state; } if (state != 0xF && (ioamhram[0x100] & 0xF) == 0xF) intreq.flagIrq(0x10); ioamhram[0x100] = (ioamhram[0x100] & -0x10u) | state; } void Memory::updateOamDma(const unsigned long cycleCounter) { const unsigned char *const oamDmaSrc = oamDmaSrcPtr(); unsigned cycles = (cycleCounter - lastOamDmaUpdate) >> 2; while (cycles--) { oamDmaPos = (oamDmaPos + 1) & 0xFF; lastOamDmaUpdate += 4; if (oamDmaPos < 0xA0) { if (oamDmaPos == 0) startOamDma(lastOamDmaUpdate - 1); ioamhram[oamDmaPos] = oamDmaSrc ? oamDmaSrc[oamDmaPos] : cart.rtcRead(); } else if (oamDmaPos == 0xA0) { endOamDma(lastOamDmaUpdate - 1); lastOamDmaUpdate = DISABLED_TIME; break; } } } void Memory::oamDmaInitSetup() { if (ioamhram[0x146] < 0xA0) { cart.setOamDmaSrc(ioamhram[0x146] < 0x80 ? OAM_DMA_SRC_ROM : OAM_DMA_SRC_VRAM); } else if (ioamhram[0x146] < 0xFE - isCgb() * 0x1E) { cart.setOamDmaSrc(ioamhram[0x146] < 0xC0 ? OAM_DMA_SRC_SRAM : OAM_DMA_SRC_WRAM); } else cart.setOamDmaSrc(OAM_DMA_SRC_INVALID); } static const unsigned char * oamDmaSrcZero() { static unsigned char zeroMem[0xA0]; return zeroMem; } const unsigned char * Memory::oamDmaSrcPtr() const { switch (cart.oamDmaSrc()) { case OAM_DMA_SRC_ROM: return cart.romdata(ioamhram[0x146] >> 6) + (ioamhram[0x146] << 8); case OAM_DMA_SRC_SRAM: return cart.rsrambankptr() ? cart.rsrambankptr() + (ioamhram[0x146] << 8) : 0; case OAM_DMA_SRC_VRAM: return cart.vrambankptr() + (ioamhram[0x146] << 8); case OAM_DMA_SRC_WRAM: return cart.wramdata(ioamhram[0x146] >> 4 & 1) + (ioamhram[0x146] << 8 & 0xFFF); case OAM_DMA_SRC_INVALID: case OAM_DMA_SRC_OFF: break; } return ioamhram[0x146] == 0xFF && !isCgb() ? oamDmaSrcZero() : cart.rdisabledRam(); } void Memory::startOamDma(const unsigned long cycleCounter) { display.oamChange(cart.rdisabledRam(), cycleCounter); } void Memory::endOamDma(const unsigned long cycleCounter) { oamDmaPos = 0xFE; cart.setOamDmaSrc(OAM_DMA_SRC_OFF); display.oamChange(ioamhram, cycleCounter); } unsigned Memory::nontrivial_ff_read(const unsigned P, const unsigned long cycleCounter) { if (lastOamDmaUpdate != DISABLED_TIME) updateOamDma(cycleCounter); switch (P & 0x7F) { case 0x00: updateInput(); break; case 0x01: case 0x02: updateSerial(cycleCounter); break; case 0x04: { const unsigned long divcycles = (cycleCounter - divLastUpdate) >> 8; ioamhram[0x104] = (ioamhram[0x104] + divcycles) & 0xFF; divLastUpdate += divcycles << 8; } break; case 0x05: ioamhram[0x105] = tima.tima(cycleCounter); break; case 0x0F: updateIrqs(cycleCounter); ioamhram[0x10F] = intreq.ifreg(); break; case 0x26: if (ioamhram[0x126] & 0x80) { sound.generate_samples(cycleCounter, isDoubleSpeed()); ioamhram[0x126] = 0xF0 | sound.getStatus(); } else ioamhram[0x126] = 0x70; break; case 0x30: 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: sound.generate_samples(cycleCounter, isDoubleSpeed()); return sound.waveRamRead(P & 0xF); case 0x41: return ioamhram[0x141] | display.getStat(ioamhram[0x145], cycleCounter); case 0x44: return display.getLyReg(cycleCounter/*+4*/); case 0x69: return display.cgbBgColorRead(ioamhram[0x168] & 0x3F, cycleCounter); case 0x6B: return display.cgbSpColorRead(ioamhram[0x16A] & 0x3F, cycleCounter); default: break; } return ioamhram[P - 0xFE00]; } static bool isInOamDmaConflictArea(const OamDmaSrc oamDmaSrc, const unsigned addr, const bool cgb) { struct Area { unsigned short areaUpper, exceptAreaLower, exceptAreaWidth, pad; }; static const Area cgbAreas[] = { { 0xC000, 0x8000, 0x2000, 0 }, { 0xC000, 0x8000, 0x2000, 0 }, { 0xA000, 0x0000, 0x8000, 0 }, { 0xFE00, 0x0000, 0xC000, 0 }, { 0xC000, 0x8000, 0x2000, 0 }, { 0x0000, 0x0000, 0x0000, 0 } }; static const Area dmgAreas[] = { { 0xFE00, 0x8000, 0x2000, 0 }, { 0xFE00, 0x8000, 0x2000, 0 }, { 0xA000, 0x0000, 0x8000, 0 }, { 0xFE00, 0x8000, 0x2000, 0 }, { 0xFE00, 0x8000, 0x2000, 0 }, { 0x0000, 0x0000, 0x0000, 0 } }; const Area *const a = cgb ? cgbAreas : dmgAreas; return addr < a[oamDmaSrc].areaUpper && addr - a[oamDmaSrc].exceptAreaLower >= a[oamDmaSrc].exceptAreaWidth; } unsigned Memory::nontrivial_read(const unsigned P, const unsigned long cycleCounter) { if (P < 0xFF80) { if (lastOamDmaUpdate != DISABLED_TIME) { updateOamDma(cycleCounter); if (isInOamDmaConflictArea(cart.oamDmaSrc(), P, isCgb()) && oamDmaPos < 0xA0) return ioamhram[oamDmaPos]; } if (P < 0xC000) { if (P < 0x8000) return cart.romdata(P >> 14)[P]; if (P < 0xA000) { if (!display.vramAccessible(cycleCounter)) return 0xFF; return cart.vrambankptr()[P]; } if (cart.rsrambankptr()) return cart.rsrambankptr()[P]; return cart.rtcRead(); } if (P < 0xFE00) return cart.wramdata(P >> 12 & 1)[P & 0xFFF]; if (P >= 0xFF00) return nontrivial_ff_read(P, cycleCounter); if (!display.oamReadable(cycleCounter) || oamDmaPos < 0xA0) return 0xFF; } return ioamhram[P - 0xFE00]; } unsigned Memory::nontrivial_peek(const unsigned P) { if (P < 0xC000) { if (P < 0x8000) return cart.romdata(P >> 14)[P]; if (P < 0xA000) { return cart.vrambankptr()[P]; } if (cart.rsrambankptr()) return cart.rsrambankptr()[P]; return cart.rtcRead(); // verified side-effect free } if (P < 0xFE00) return cart.wramdata(P >> 12 & 1)[P & 0xFFF]; if (P >= 0xFF00 && P < 0xFF80) return nontrivial_ff_peek(P); return ioamhram[P - 0xFE00]; } unsigned Memory::nontrivial_ff_peek(const unsigned P) { // some regs may be somewhat wrong with this return ioamhram[P - 0xFE00]; } void Memory::nontrivial_ff_write(const unsigned P, unsigned data, const unsigned long cycleCounter) { if (lastOamDmaUpdate != DISABLED_TIME) updateOamDma(cycleCounter); switch (P & 0xFF) { case 0x00: if ((data ^ ioamhram[0x100]) & 0x30) { ioamhram[0x100] = (ioamhram[0x100] & ~0x30u) | (data & 0x30); updateInput(); } return; case 0x01: updateSerial(cycleCounter); break; case 0x02: updateSerial(cycleCounter); serialCnt = 8; intreq.setEventTime((data & 0x81) == 0x81 ? (data & isCgb() * 2 ? (cycleCounter & ~0x7ul) + 0x10 * 8 : (cycleCounter & ~0xFFul) + 0x200 * 8) : static_cast(DISABLED_TIME)); data |= 0x7E - isCgb() * 2; break; case 0x04: ioamhram[0x104] = 0; divLastUpdate = cycleCounter; return; case 0x05: tima.setTima(data, cycleCounter, TimaInterruptRequester(intreq)); break; case 0x06: tima.setTma(data, cycleCounter, TimaInterruptRequester(intreq)); break; case 0x07: data |= 0xF8; tima.setTac(data, cycleCounter, TimaInterruptRequester(intreq)); break; case 0x0F: updateIrqs(cycleCounter); intreq.setIfreg(0xE0 | data); return; case 0x10: if (!sound.isEnabled()) return; sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_nr10(data); data |= 0x80; break; case 0x11: if (!sound.isEnabled()) { if (isCgb()) return; data &= 0x3F; } sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_nr11(data); data |= 0x3F; break; case 0x12: if (!sound.isEnabled()) return; sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_nr12(data); break; case 0x13: if (!sound.isEnabled()) return; sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_nr13(data); return; case 0x14: if (!sound.isEnabled()) return; sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_nr14(data); data |= 0xBF; break; case 0x16: if (!sound.isEnabled()) { if (isCgb()) return; data &= 0x3F; } sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_nr21(data); data |= 0x3F; break; case 0x17: if (!sound.isEnabled()) return; sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_nr22(data); break; case 0x18: if (!sound.isEnabled()) return; sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_nr23(data); return; case 0x19: if (!sound.isEnabled()) return; sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_nr24(data); data |= 0xBF; break; case 0x1A: if (!sound.isEnabled()) return; sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_nr30(data); data |= 0x7F; break; case 0x1B: if (!sound.isEnabled() && isCgb()) return; sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_nr31(data); return; case 0x1C: if (!sound.isEnabled()) return; sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_nr32(data); data |= 0x9F; break; case 0x1D: if (!sound.isEnabled()) return; sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_nr33(data); return; case 0x1E: if (!sound.isEnabled()) return; sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_nr34(data); data |= 0xBF; break; case 0x20: if (!sound.isEnabled() && isCgb()) return; sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_nr41(data); return; case 0x21: if (!sound.isEnabled()) return; sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_nr42(data); break; case 0x22: if (!sound.isEnabled()) return; sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_nr43(data); break; case 0x23: if (!sound.isEnabled()) return; sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_nr44(data); data |= 0xBF; break; case 0x24: if (!sound.isEnabled()) return; sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.set_so_volume(data); break; case 0x25: if (!sound.isEnabled()) return; sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.map_so(data); break; case 0x26: if ((ioamhram[0x126] ^ data) & 0x80) { sound.generate_samples(cycleCounter, isDoubleSpeed()); if (!(data & 0x80)) { for (unsigned i = 0xFF10; i < 0xFF26; ++i) ff_write(i, 0, cycleCounter); sound.setEnabled(false); } else { sound.reset(); sound.setEnabled(true); } } data = (data & 0x80) | (ioamhram[0x126] & 0x7F); break; case 0x30: 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: sound.generate_samples(cycleCounter, isDoubleSpeed()); sound.waveRamWrite(P & 0xF, data); break; case 0x40: if (ioamhram[0x140] != data) { if ((ioamhram[0x140] ^ data) & 0x80) { const unsigned lyc = display.getStat(ioamhram[0x145], cycleCounter) & 4; const bool hdmaEnabled = display.hdmaIsEnabled(); display.lcdcChange(data, cycleCounter); ioamhram[0x144] = 0; ioamhram[0x141] &= 0xF8; if (data & 0x80) { intreq.setEventTime(display.nextMode1IrqTime() + (blanklcd ? 0 : 70224 << isDoubleSpeed())); } else { ioamhram[0x141] |= lyc; intreq.setEventTime(cycleCounter + (456 * 4 << isDoubleSpeed())); if (hdmaEnabled) flagHdmaReq(&intreq); } } else display.lcdcChange(data, cycleCounter); ioamhram[0x140] = data; } return; case 0x41: display.lcdstatChange(data, cycleCounter); data = (ioamhram[0x141] & 0x87) | (data & 0x78); break; case 0x42: display.scyChange(data, cycleCounter); break; case 0x43: display.scxChange(data, cycleCounter); break; case 0x45: display.lycRegChange(data, cycleCounter); break; case 0x46: if (lastOamDmaUpdate != DISABLED_TIME) endOamDma(cycleCounter); lastOamDmaUpdate = cycleCounter; intreq.setEventTime(cycleCounter + 8); ioamhram[0x146] = data; oamDmaInitSetup(); return; case 0x47: if (!isCgb()) display.dmgBgPaletteChange(data, cycleCounter); break; case 0x48: if (!isCgb()) display.dmgSpPalette1Change(data, cycleCounter); break; case 0x49: if (!isCgb()) display.dmgSpPalette2Change(data, cycleCounter); break; case 0x4A: display.wyChange(data, cycleCounter); break; case 0x4B: display.wxChange(data, cycleCounter); break; case 0x4D: if (isCgb()) ioamhram[0x14D] = (ioamhram[0x14D] & ~1u) | (data & 1); return; case 0x4F: if (isCgb()) { cart.setVrambank(data & 1); ioamhram[0x14F] = 0xFE | data; } return; case 0x51: dmaSource = data << 8 | (dmaSource & 0xFF); return; case 0x52: dmaSource = (dmaSource & 0xFF00) | (data & 0xF0); return; case 0x53: dmaDestination = data << 8 | (dmaDestination & 0xFF); return; case 0x54: dmaDestination = (dmaDestination & 0xFF00) | (data & 0xF0); return; case 0x55: if (isCgb()) { ioamhram[0x155] = data & 0x7F; if (display.hdmaIsEnabled()) { if (!(data & 0x80)) { ioamhram[0x155] |= 0x80; display.disableHdma(cycleCounter); } } else { if (data & 0x80) { if (ioamhram[0x140] & 0x80) { display.enableHdma(cycleCounter); } else flagHdmaReq(&intreq); } else flagGdmaReq(&intreq); } } return; case 0x56: if (isCgb()) ioamhram[0x156] = data | 0x3E; return; case 0x68: if (isCgb()) ioamhram[0x168] = data | 0x40; return; case 0x69: if (isCgb()) { const unsigned index = ioamhram[0x168] & 0x3F; display.cgbBgColorChange(index, data, cycleCounter); ioamhram[0x168] = (ioamhram[0x168] & ~0x3F) | ((index + (ioamhram[0x168] >> 7)) & 0x3F); } return; case 0x6A: if (isCgb()) ioamhram[0x16A] = data | 0x40; return; case 0x6B: if (isCgb()) { const unsigned index = ioamhram[0x16A] & 0x3F; display.cgbSpColorChange(index, data, cycleCounter); ioamhram[0x16A] = (ioamhram[0x16A] & ~0x3F) | ((index + (ioamhram[0x16A] >> 7)) & 0x3F); } return; case 0x6C: if (isCgb()) ioamhram[0x16C] = data | 0xFE; return; case 0x70: if (isCgb()) { cart.setWrambank((data & 0x07) ? (data & 0x07) : 1); ioamhram[0x170] = data | 0xF8; } return; case 0x72: case 0x73: case 0x74: if (isCgb()) break; return; case 0x75: if (isCgb()) ioamhram[0x175] = data | 0x8F; return; case 0xFF: intreq.setIereg(data); break; default: return; } ioamhram[P - 0xFE00] = data; } void Memory::nontrivial_write(const unsigned P, const unsigned data, const unsigned long cycleCounter) { if (lastOamDmaUpdate != DISABLED_TIME) { updateOamDma(cycleCounter); if (isInOamDmaConflictArea(cart.oamDmaSrc(), P, isCgb()) && oamDmaPos < 0xA0) { ioamhram[oamDmaPos] = data; return; } } if (P < 0xFE00) { if (P < 0xA000) { if (P < 0x8000) { cart.mbcWrite(P, data); } else if (display.vramAccessible(cycleCounter)) { display.vramChange(cycleCounter); cart.vrambankptr()[P] = data; } } else if (P < 0xC000) { if (cart.wsrambankptr()) cart.wsrambankptr()[P] = data; else cart.rtcWrite(data); } else cart.wramdata(P >> 12 & 1)[P & 0xFFF] = data; } else if (P - 0xFF80u >= 0x7Fu) { if (P < 0xFF00) { if (display.oamWritable(cycleCounter) && oamDmaPos >= 0xA0 && (P < 0xFEA0 || isCgb())) { display.oamChange(cycleCounter); ioamhram[P - 0xFE00] = data; } } else nontrivial_ff_write(P, data, cycleCounter); } else ioamhram[P - 0xFE00] = data; } int Memory::loadROM(const char *romfiledata, unsigned romfilelength, const bool forceDmg, const bool multicartCompat) { if (const int fail = cart.loadROM(romfiledata, romfilelength, forceDmg, multicartCompat)) return fail; sound.init(cart.isCgb()); display.reset(ioamhram, cart.vramdata(), cart.isCgb()); return 0; } unsigned Memory::fillSoundBuffer(const unsigned long cycleCounter) { sound.generate_samples(cycleCounter, isDoubleSpeed()); return sound.fillBuffer(); } void Memory::setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned long rgb32) { display.setDmgPaletteColor(palNum, colorNum, rgb32); } void Memory::setCgbPalette(unsigned *lut) { display.setCgbPalette(lut); } bool Memory::getMemoryArea(int which, unsigned char **data, int *length) { if (!data || !length) return false; switch (which) { case 4: // oam *data = &ioamhram[0]; *length = 160; return true; case 5: // hram *data = &ioamhram[384]; *length = 128; return true; case 6: // bgpal *data = (unsigned char *)display.bgPalette(); *length = 32; return true; case 7: // sppal *data = (unsigned char *)display.spPalette(); *length = 32; return true; default: // pass to cartridge return cart.getMemoryArea(which, data, length); } } int Memory::LinkStatus(int which) { switch (which) { case 256: // ClockSignaled return linkClockTrigger; case 257: // AckClockSignal linkClockTrigger = false; return 0; case 258: // GetOut return ioamhram[0x101] & 0xff; case 259: // connect link cable LINKCABLE = true; return 0; default: // ShiftIn if (ioamhram[0x102] & 0x80) // was enabled { ioamhram[0x101] = which; ioamhram[0x102] &= 0x7F; intreq.flagIrq(8); } return 0; } return -1; } SYNCFUNC(Memory) { SSS(cart); NSS(ioamhram); NSS(divLastUpdate); NSS(lastOamDmaUpdate); SSS(intreq); SSS(tima); SSS(display); SSS(sound); //SSS(interrupter); // no state NSS(dmaSource); NSS(dmaDestination); NSS(oamDmaPos); NSS(serialCnt); NSS(blanklcd); NSS(LINKCABLE); NSS(linkClockTrigger); } }