public function memoryWrite($address, $data)
{
if ($address < 0x8000) {
if ($this->cMBC1) {
if ($address < 0x2000) {
//MBC RAM Bank Enable/Disable:
$this->MBCRAMBanksEnabled = ($data & 0xf) == 0xa;
//If lower nibble is 0x0A, then enable, otherwise disable.
} elseif ($address < 0x4000) {
// MBC1WriteROMBank
//MBC1 ROM bank switching:
$this->ROMBank1offs = $this->ROMBank1offs & 0x60 | $data & 0x1f;
$this->setCurrentMBC1ROMBank();
} elseif ($address < 0x6000) {
//MBC1WriteRAMBank
//MBC1 RAM bank switching
if ($this->MBC1Mode) {
//4/32 Mode
$this->currMBCRAMBank = $data & 0x3;
$this->currMBCRAMBankPosition = ($this->currMBCRAMBank << 13) - 0xa000;
} else {
//16/8 Mode
$this->ROMBank1offs = ($data & 0x3) << 5 | $this->ROMBank1offs & 0x1f;
$this->setCurrentMBC1ROMBank();
}
} else {
//MBC1WriteType
//MBC1 mode setting:
$this->MBC1Mode = ($data & 0x1) == 0x1;
}
} elseif ($this->cMBC2) {
if ($address < 0x1000) {
//MBC RAM Bank Enable/Disable:
$this->MBCRAMBanksEnabled = ($data & 0xf) == 0xa;
//If lower nibble is 0x0A, then enable, otherwise disable.
} elseif ($address >= 0x2100 && $address < 0x2200) {
//MBC2WriteROMBank
//MBC2 ROM bank switching:
$this->ROMBank1offs = $data & 0xf;
$this->setCurrentMBC2AND3ROMBank();
} else {
//We might have encountered illegal RAM writing or such, so just do nothing...
}
} elseif ($this->cMBC3) {
if ($address < 0x2000) {
//MBC RAM Bank Enable/Disable:
$this->MBCRAMBanksEnabled = ($data & 0xf) == 0xa;
//If lower nibble is 0x0A, then enable, otherwise disable.
} elseif ($address < 0x4000) {
//MBC3 ROM bank switching:
$this->ROMBank1offs = $data & 0x7f;
$this->setCurrentMBC2AND3ROMBank();
} elseif ($address < 0x6000) {
//MBC3WriteRAMBank
$this->currMBCRAMBank = $data;
if ($data < 4) {
//MBC3 RAM bank switching
$this->currMBCRAMBankPosition = ($this->currMBCRAMBank << 13) - 0xa000;
}
} else {
//MBC3WriteRTCLatch
if ($data == 0) {
$this->RTCisLatched = false;
} elseif (!$this->RTCisLatched) {
//Copy over the current RTC time for reading.
$this->RTCisLatched = true;
$this->latchedSeconds = floor($this->RTCSeconds);
$this->latchedMinutes = $this->RTCMinutes;
$this->latchedHours = $this->RTCHours;
$this->latchedLDays = $this->RTCDays & 0xff;
$this->latchedHDays = $this->RTCDays >> 8;
}
}
} elseif ($this->cMBC5 || $this->cRUMBLE) {
if ($address < 0x2000) {
//MBC RAM Bank Enable/Disable:
$this->MBCRAMBanksEnabled = ($data & 0xf) == 0xa;
//If lower nibble is 0x0A, then enable, otherwise disable.
} elseif ($address < 0x3000) {
//MBC5WriteROMBankLow
//MBC5 ROM bank switching:
$this->ROMBank1offs = $this->ROMBank1offs & 0x100 | $data;
$this->setCurrentMBC5ROMBank();
} elseif ($address < 0x4000) {
//MBC5WriteROMBankHigh
//MBC5 ROM bank switching (by least significant bit):
$this->ROMBank1offs = ($data & 0x1) << 8 | $this->ROMBank1offs & 0xff;
$this->setCurrentMBC5ROMBank();
} elseif ($address < 0x6000) {
if ($this->cRUMBLE) {
//MBC5 RAM bank switching
//Like MBC5, but bit 3 of the lower nibble is used for rumbling and bit 2 is ignored.
$this->currMBCRAMBank = $data & 0x3;
$this->currMBCRAMBankPosition = ($this->currMBCRAMBank << 13) - 0xa000;
} else {
//MBC5 RAM bank switching
$this->currMBCRAMBank = $data & 0xf;
$this->currMBCRAMBankPosition = ($this->currMBCRAMBank << 13) - 0xa000;
}
} else {
//We might have encountered illegal RAM writing or such, so just do nothing...
}
} elseif ($this->cHuC3) {
if ($address < 0x2000) {
//MBC RAM Bank Enable/Disable:
$this->MBCRAMBanksEnabled = ($data & 0xf) == 0xa;
//If lower nibble is 0x0A, then enable, otherwise disable.
} elseif ($address < 0x4000) {
//MBC3 ROM bank switching:
$this->ROMBank1offs = $data & 0x7f;
$this->setCurrentMBC2AND3ROMBank();
} elseif ($address < 0x6000) {
//HuC3WriteRAMBank
//HuC3 RAM bank switching
$this->currMBCRAMBank = $data & 0x3;
$this->currMBCRAMBankPosition = ($this->currMBCRAMBank << 13) - 0xa000;
} else {
//We might have encountered illegal RAM writing or such, so just do nothing...
}
} else {
//We might have encountered illegal RAM writing or such, so just do nothing...
}
} elseif ($address < 0xa000) {
// VRAMWrite
//VRAM cannot be written to during mode 3
if ($this->lcdController->modeSTAT < 3) {
// Bkg Tile data area
if ($address < 0x9800) {
$tileIndex = ($address - 0x8000 >> 4) + 384 * $this->currVRAMBank;
if ($this->tileReadState[$tileIndex] == 1) {
$r = count($this->tileData) - $this->tileCount + $tileIndex;
do {
$this->tileData[$r] = null;
$r -= $this->tileCount;
} while ($r >= 0);
$this->tileReadState[$tileIndex] = 0;
}
}
if ($this->currVRAMBank == 0) {
$this->memory[$address] = $data;
} else {
$this->VRAM[$address - 0x8000] = $data;
}
}
} elseif ($address < 0xc000) {
if ($this->numRAMBanks == 1 / 16 && $address < 0xa200 || $this->numRAMBanks >= 1) {
if (!$this->cMBC3) {
//memoryWriteMBCRAM
if ($this->MBCRAMBanksEnabled || Settings::$overrideMBC) {
$this->MBCRam[$address + $this->currMBCRAMBankPosition] = $data;
}
} else {
//MBC3 RTC + RAM:
//memoryWriteMBC3RAM
if ($this->MBCRAMBanksEnabled || Settings::$overrideMBC) {
switch ($this->currMBCRAMBank) {
case 0x0:
case 0x1:
case 0x2:
case 0x3:
$this->MBCRam[$address + $this->currMBCRAMBankPosition] = $data;
break;
case 0x8:
if ($data < 60) {
$this->RTCSeconds = $data;
} else {
echo '(Bank #' . $this->currMBCRAMBank . ') RTC write out of range: ' . $data . PHP_EOL;
}
break;
case 0x9:
if ($data < 60) {
$this->RTCMinutes = $data;
} else {
echo '(Bank #' . $this->currMBCRAMBank . ') RTC write out of range: ' . $data . PHP_EOL;
}
break;
case 0xa:
if ($data < 24) {
$this->RTCHours = $data;
} else {
echo '(Bank #' . $this->currMBCRAMBank . ') RTC write out of range: ' . $data . PHP_EOL;
}
break;
case 0xb:
$this->RTCDays = $data & 0xff | $this->RTCDays & 0x100;
break;
case 0xc:
$this->RTCDayOverFlow = ($data & 0x80) == 0x80;
$this->RTCHalt = ($data & 0x40) == 0x40;
$this->RTCDays = ($data & 0x1) << 8 | $this->RTCDays & 0xff;
break;
default:
echo 'Invalid MBC3 bank address selected: ' . $this->currMBCRAMBank . PHP_EOL;
}
}
}
} else {
//We might have encountered illegal RAM writing or such, so just do nothing...
}
} elseif ($address < 0xe000) {
if ($this->cGBC && $address >= 0xd000) {
//memoryWriteGBCRAM
$this->GBCMemory[$address + $this->gbcRamBankPosition] = $data;
} else {
//memoryWriteNormal
$this->memory[$address] = $data;
}
} elseif ($address < 0xfe00) {
if ($this->cGBC && $address >= 0xf000) {
//memoryWriteECHOGBCRAM
$this->GBCMemory[$address + $this->gbcRamBankPositionECHO] = $data;
} else {
//memoryWriteECHONormal
$this->memory[$address - 0x2000] = $data;
}
} elseif ($address <= 0xfea0) {
//memoryWriteOAMRAM
//OAM RAM cannot be written to in mode 2 & 3
if ($this->lcdController->modeSTAT < 2) {
$this->memory[$address] = $data;
}
} elseif ($address < 0xff00) {
//Only GBC has access to this RAM.
if ($this->cGBC) {
//memoryWriteNormal
$this->memory[$address] = $data;
} else {
//We might have encountered illegal RAM writing or such, so just do nothing...
}
//I/O Registers (GB + GBC):
} elseif ($address == 0xff00) {
$this->memory[0xff00] = $data & 0x30 | (($data & 0x20) == 0 ? $this->JoyPad >> 4 : 0xf) & (($data & 0x10) == 0 ? $this->JoyPad & 0xf : 0xf);
} elseif ($address == 0xff02) {
if (($data & 0x1) == 0x1) {
//Internal clock:
$this->memory[0xff02] = $data & 0x7f;
$this->memory[0xff0f] |= 0x8;
//Get this time delayed...
} else {
//External clock:
$this->memory[0xff02] = $data;
//No connected serial device, so don't trigger interrupt...
}
} elseif ($address == 0xff04) {
$this->memory[0xff04] = 0;
} elseif ($address == 0xff07) {
$this->memory[0xff07] = $data & 0x7;
$this->TIMAEnabled = ($data & 0x4) == 0x4;
$this->TACClocker = pow(4, ($data & 0x3) != 0 ? $data & 0x3 : 4);
//TODO: Find a way to not make a conditional in here...
} elseif ($address == 0xff40) {
if ($this->cGBC) {
$temp_var = ($data & 0x80) == 0x80;
if ($temp_var != $this->lcdController->LCDisOn) {
//When the display mode changes...
$this->lcdController->LCDisOn = $temp_var;
$this->memory[0xff41] &= 0xf8;
$this->lcdController->STATTracker = $this->lcdController->modeSTAT = $this->LCDTicks = $this->lcdController->actualScanLine = $this->memory[0xff44] = 0;
if ($this->lcdController->LCDisOn) {
$this->lcdController->matchLYC();
//Get the compare of the first scan line.
} else {
$this->displayShowOff();
}
$this->memory[0xff0f] &= 0xfd;
}
$this->gfxWindowY = ($data & 0x40) == 0x40;
$this->gfxWindowDisplay = ($data & 0x20) == 0x20;
$this->gfxBackgroundX = ($data & 0x10) == 0x10;
$this->gfxBackgroundY = ($data & 0x8) == 0x8;
$this->gfxSpriteDouble = ($data & 0x4) == 0x4;
$this->gfxSpriteShow = ($data & 0x2) == 0x2;
$this->spritePriorityEnabled = ($data & 0x1) == 0x1;
$this->memory[0xff40] = $data;
} else {
$temp_var = ($data & 0x80) == 0x80;
if ($temp_var != $this->lcdController->LCDisOn) {
//When the display mode changes...
$this->lcdController->LCDisOn = $temp_var;
$this->memory[0xff41] &= 0xf8;
$this->lcdController->STATTracker = $this->lcdController->modeSTAT = $this->LCDTicks = $this->lcdController->actualScanLine = $this->memory[0xff44] = 0;
if ($this->lcdController->LCDisOn) {
$this->lcdController->matchLYC();
//Get the compare of the first scan line.
} else {
$this->displayShowOff();
}
$this->memory[0xff0f] &= 0xfd;
}
$this->gfxWindowY = ($data & 0x40) == 0x40;
$this->gfxWindowDisplay = ($data & 0x20) == 0x20;
$this->gfxBackgroundX = ($data & 0x10) == 0x10;
$this->gfxBackgroundY = ($data & 0x8) == 0x8;
$this->gfxSpriteDouble = ($data & 0x4) == 0x4;
$this->gfxSpriteShow = ($data & 0x2) == 0x2;
if (($data & 0x1) == 0) {
// this emulates the gbc-in-gb-mode, not the original gb-mode
$this->bgEnabled = false;
$this->gfxWindowDisplay = false;
} else {
$this->bgEnabled = true;
}
$this->memory[0xff40] = $data;
}
} elseif ($address == 0xff41) {
if ($this->cGBC) {
$this->lcdController->LYCMatchTriggerSTAT = ($data & 0x40) == 0x40;
$this->lcdController->mode2TriggerSTAT = ($data & 0x20) == 0x20;
$this->lcdController->mode1TriggerSTAT = ($data & 0x10) == 0x10;
$this->lcdController->mode0TriggerSTAT = ($data & 0x8) == 0x8;
$this->memory[0xff41] = $data & 0xf8;
} else {
$this->lcdController->LYCMatchTriggerSTAT = ($data & 0x40) == 0x40;
$this->lcdController->mode2TriggerSTAT = ($data & 0x20) == 0x20;
$this->lcdController->mode1TriggerSTAT = ($data & 0x10) == 0x10;
$this->lcdController->mode0TriggerSTAT = ($data & 0x8) == 0x8;
$this->memory[0xff41] = $data & 0xf8;
if ($this->lcdController->LCDisOn && $this->lcdController->modeSTAT < 2) {
$this->memory[0xff0f] |= 0x2;
}
}
} elseif ($address == 0xff45) {
$this->memory[0xff45] = $data;
if ($this->lcdController->LCDisOn) {
$this->lcdController->matchLYC();
//Get the compare of the first scan line.
}
} elseif ($address == 0xff46) {
$this->memory[0xff46] = $data;
//DMG cannot DMA from the ROM banks.
if ($this->cGBC || $data > 0x7f) {
$data <<= 8;
$address = 0xfe00;
while ($address < 0xfea0) {
$this->memory[$address++] = $this->memoryRead($data++);
}
}
} elseif ($address == 0xff47) {
$this->decodePalette(0, $data);
if ($this->memory[0xff47] != $data) {
$this->memory[0xff47] = $data;
$this->invalidateAll(0);
}
} elseif ($address == 0xff48) {
$this->decodePalette(4, $data);
if ($this->memory[0xff48] != $data) {
$this->memory[0xff48] = $data;
$this->invalidateAll(1);
}
} elseif ($address == 0xff49) {
$this->decodePalette(8, $data);
if ($this->memory[0xff49] != $data) {
$this->memory[0xff49] = $data;
$this->invalidateAll(2);
}
} elseif ($address == 0xff4d) {
if ($this->cGBC) {
$this->memory[0xff4d] = ($data & 0x7f) + ($this->memory[0xff4d] & 0x80);
} else {
$this->memory[0xff4d] = $data;
}
} elseif ($address == 0xff4f) {
if ($this->cGBC) {
$this->currVRAMBank = $data & 0x1;
//Only writable by GBC.
}
} elseif ($address == 0xff50) {
if ($this->inBootstrap) {
echo 'Boot ROM reads blocked: Bootstrap process has ended.' . PHP_EOL;
$this->inBootstrap = false;
$this->disableBootROM();
//Fill in the boot ROM ranges with ROM bank 0 ROM ranges
$this->memory[0xff50] = $data;
//Bits are sustained in memory?
}
} elseif ($address == 0xff51) {
if ($this->cGBC) {
if (!$this->hdmaRunning) {
$this->memory[0xff51] = $data;
}
}
} elseif ($address == 0xff52) {
if ($this->cGBC) {
if (!$this->hdmaRunning) {
$this->memory[0xff52] = $data & 0xf0;
}
}
} elseif ($address == 0xff53) {
if ($this->cGBC) {
if (!$this->hdmaRunning) {
$this->memory[0xff53] = $data & 0x1f;
}
}
} elseif ($address == 0xff54) {
if ($this->cGBC) {
if (!$this->hdmaRunning) {
$this->memory[0xff54] = $data & 0xf0;
}
}
} elseif ($address == 0xff55) {
if ($this->cGBC) {
if (!$this->hdmaRunning) {
if (($data & 0x80) == 0) {
//DMA
$this->CPUTicks += 1 + 8 * (($data & 0x7f) + 1) * $this->multiplier;
$dmaSrc = ($this->memory[0xff51] << 8) + $this->memory[0xff52];
$dmaDst = 0x8000 + ($this->memory[0xff53] << 8) + $this->memory[0xff54];
$endAmount = ($data & 0x7f) * 0x10 + 0x10;
for ($loopAmount = 0; $loopAmount < $endAmount; ++$loopAmount) {
$this->memoryWrite($dmaDst++, $this->memoryRead($dmaSrc++));
}
$this->memory[0xff51] = ($dmaSrc & 0xff00) >> 8;
$this->memory[0xff52] = $dmaSrc & 0xf0;
$this->memory[0xff53] = ($dmaDst & 0x1f00) >> 8;
$this->memory[0xff54] = $dmaDst & 0xf0;
$this->memory[0xff55] = 0xff;
//Transfer completed.
} else {
//H-Blank DMA
if ($data > 0x80) {
$this->hdmaRunning = true;
$this->memory[0xff55] = $data & 0x7f;
} else {
$this->memory[0xff55] = 0xff;
}
}
} elseif (($data & 0x80) == 0) {
//Stop H-Blank DMA
$this->hdmaRunning = false;
$this->memory[0xff55] |= 0x80;
}
} else {
$this->memory[0xff55] = $data;
}
} elseif ($address == 0xff68) {
if ($this->cGBC) {
$this->memory[0xff69] = 0xff & $this->gbcRawPalette[$data & 0x3f];
$this->memory[0xff68] = $data;
} else {
$this->memory[0xff68] = $data;
}
} elseif ($address == 0xff69) {
if ($this->cGBC) {
$this->setGBCPalette($this->memory[0xff68] & 0x3f, $data);
// high bit = autoincrement
if ($this->usbtsb($this->memory[0xff68]) < 0) {
$next = $this->usbtsb($this->memory[0xff68]) + 1 & 0x3f;
$this->memory[0xff68] = $next | 0x80;
$this->memory[0xff69] = 0xff & $this->gbcRawPalette[$next];
} else {
$this->memory[0xff69] = $data;
}
} else {
$this->memory[0xff69] = $data;
}
} elseif ($address == 0xff6a) {
if ($this->cGBC) {
$this->memory[0xff6b] = 0xff & $this->gbcRawPalette[$data & 0x3f | 0x40];
$this->memory[0xff6a] = $data;
} else {
$this->memory[0xff6a] = $data;
}
} elseif ($address == 0xff6b) {
if ($this->cGBC) {
$this->setGBCPalette(($this->memory[0xff6a] & 0x3f) + 0x40, $data);
// high bit = autoincrement
if ($this->usbtsb($this->memory[0xff6a]) < 0) {
$next = $this->memory[0xff6a] + 1 & 0x3f;
$this->memory[0xff6a] = $next | 0x80;
$this->memory[0xff6b] = 0xff & $this->gbcRawPalette[$next | 0x40];
} else {
$this->memory[0xff6b] = $data;
}
} else {
$this->memory[0xff6b] = $data;
}
} elseif ($address == 0xff6c) {
if ($this->inBootstrap) {
if ($this->inBootstrap) {
$this->cGBC = $data == 0x80;
echo 'Booted to GBC Mode: ' . $this->cGBC . PHP_EOL;
}
$this->memory[0xff6c] = $data;
}
} elseif ($address == 0xff70) {
if ($this->cGBC) {
$addressCheck = $this->memory[0xff51] << 8 | $this->memory[0xff52];
//Cannot change the RAM bank while WRAM is the source of a running HDMA.
if (!$this->hdmaRunning || $addressCheck < 0xd000 || $addressCheck >= 0xe000) {
$this->gbcRamBank = max($data & 0x7, 1);
//Bank range is from 1-7
$this->gbcRamBankPosition = ($this->gbcRamBank - 1) * 0x1000 - 0xd000;
$this->gbcRamBankPositionECHO = ($this->gbcRamBank - 1) * 0x1000 - 0xf000;
}
$this->memory[0xff70] = $data | 0x40;
//Bit 6 cannot be written to.
} else {
$this->memory[0xff70] = $data;
}
} else {
//Start the I/O initialization by filling in the slots as normal memory:
//memoryWriteNormal
$this->memory[$address] = $data;
}
}