BizHawk/libgambatte/src/memory.cpp

1112 lines
28 KiB
C++

/***************************************************************************
* 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 <cstring>
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<BLIT>(144*456ul);
intreq.setEventTime<END>(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<SERIAL>(state.mem.nextSerialtime > state.cpu.cycleCounter ? state.mem.nextSerialtime : state.cpu.cycleCounter);
intreq.setEventTime<UNHALT>(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<OAM>(lastOamDmaUpdate + (oamEventPos - oamDmaPos) * 4);
}
intreq.setEventTime<BLIT>((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<BLIT>(intreq.eventTime(BLIT) + (70224 << isDoubleSpeed()));
intreq.setEventTime<END>(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<SERIAL>(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<SERIAL>(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<UNHALT>(DISABLED_TIME);
break;
case END:
intreq.setEventTime<END>(DISABLED_TIME - 1);
while (cycleCounter >= intreq.minEventTime() && intreq.eventTime(END) != DISABLED_TIME)
cycleCounter = event(cycleCounter);
intreq.setEventTime<END>(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<BLIT>(DISABLED_TIME);
intreq.setEventTime<END>(DISABLED_TIME);
while (cycleCounter >= intreq.minEventTime())
cycleCounter = event(cycleCounter);
} else
blitTime += 70224 << isDoubleSpeed();
blanklcd = lcden ^ 1;
intreq.setEventTime<BLIT>(blitTime);
}
break;
case SERIAL:
updateSerial(cycleCounter);
break;
case OAM:
intreq.setEventTime<OAM>(lastOamDmaUpdate == DISABLED_TIME ?
static_cast<unsigned long>(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<unsigned long>(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<UNHALT>(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<BLIT>((ioamhram[0x140] & 0x80) ? display.nextMode1IrqTime() : cycleCounter + (70224 << isDoubleSpeed()));
if (intreq.eventTime(END) > cycleCounter) {
intreq.setEventTime<END>(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<UNHALT>(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<SERIAL>((data & 0x81) == 0x81
? (data & isCgb() * 2 ? (cycleCounter & ~0x7ul) + 0x10 * 8 : (cycleCounter & ~0xFFul) + 0x200 * 8)
: static_cast<unsigned long>(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<BLIT>(display.nextMode1IrqTime() + (blanklcd ? 0 : 70224 << isDoubleSpeed()));
} else {
ioamhram[0x141] |= lyc;
intreq.setEventTime<BLIT>(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<OAM>(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);
}
}