UNPKG

wasmboy

Version:

Gameboy / Gameboy Color Emulator written for Web Assembly using AssemblyScript. Shell/Debugger in Preact

251 lines (214 loc) 8.23 kB
import { Memory } from './memory'; import { Cpu } from '../cpu/index'; import { Graphics } from '../graphics/graphics'; import { Palette, writeColorPaletteToMemory, Lcd } from '../graphics/index'; import { batchProcessAudio, SoundRegisterWriteTraps, Channel3 } from '../sound/index'; import { Timers, batchProcessTimers } from '../timers/index'; import { Serial } from '../serial/serial'; import { Interrupts } from '../interrupts/index'; import { Joypad } from '../joypad/index'; import { handleBanking } from './banking'; import { eightBitStoreIntoGBMemory } from './store'; import { startDmaTransfer, startHdmaTransfer } from './dma'; // Internal function to trap any modify data trying to be written to Gameboy memory // Follows the Gameboy memory map // Return true if you want to continue the write, return false to end it here export function checkWriteTraps(offset: i32, value: i32): boolean { // Cpu if (offset === Cpu.memoryLocationSpeedSwitch) { // TCAGBD, only Bit 0 is writable eightBitStoreIntoGBMemory(Cpu.memoryLocationSpeedSwitch, value & 0x01); // We did the write, dont need to return false; } // Handle Boot ROM Switch if (Cpu.BootROMEnabled && offset === Cpu.memoryLocationBootROMSwitch) { // Disable the boot rom Cpu.BootROMEnabled = false; // Set the program counter to be incremented after this command Cpu.programCounter = 0x00ff; // Allow the write return true; } // Graphics // Cache globals used multiple times for performance let videoRamLocation = Memory.videoRamLocation; let spriteInformationTableLocation = Memory.spriteInformationTableLocation; // Handle banking if (offset < videoRamLocation) { handleBanking(offset, value); return false; } // Check the graphics mode to see if we can write to VRAM // http://gbdev.gg8.se/wiki/articles/Video_Display#Accessing_VRAM_and_OAM if (offset >= videoRamLocation && offset < Memory.cartridgeRamLocation) { // Can only read/write from VRAM During Modes 0 - 2 // See graphics/lcd.ts // TODO: This can do more harm than good in a beta emulator, // requires precise timing disabling for now // if (Graphics.currentLcdMode > 2) { // return false; // } // Not batch processing here for performance // batchProcessGraphics(); // Allow the original write, and return since we dont need to look anymore return true; } // Be sure to copy everything in EchoRam to Work Ram // Codeslinger: The ECHO memory region (0xE000-0xFDFF) is quite different because any data written here is also written in the equivelent ram memory region 0xC000-0xDDFF. // Hence why it is called echo if (offset >= Memory.echoRamLocation && offset < spriteInformationTableLocation) { let wramOffset = offset - 0x2000; eightBitStoreIntoGBMemory(wramOffset, value); // Allow the original write, and return since we dont need to look anymore return true; } // Also check for individal writes // Can only read/write from OAM During Modes 0 - 1 // See graphics/lcd.ts if (offset >= spriteInformationTableLocation && offset <= Memory.spriteInformationTableLocationEnd) { // Can only read/write from OAM During Mode 2 // See graphics/lcd.ts // if (Lcd.currentLcdMode < 2) { // return false; // } // Not batch processing here for performance // batchProcessGraphics(); // Allow the original write, and return since we dont need to look anymore // return true; return Lcd.currentLcdMode >= 2; } if (offset >= Memory.unusableMemoryLocation && offset <= Memory.unusableMemoryEndLocation) { return false; } // Serial if (offset === Serial.memoryLocationSerialTransferControl) { // SC return Serial.updateTransferControl(value); } // Sound // http://gbdev.gg8.se/wiki/articles/Gameboy_sound_hardware#Registers if (offset >= 0xff10 && offset <= 0xff26) { batchProcessAudio(); return SoundRegisterWriteTraps(offset, value); } // FF27 - FF2F not used // Final Wave Table for Channel 3 if (offset >= 0xff30 && offset <= 0xff3f) { batchProcessAudio(); // Need to handle the write if channel 3 is enabled if (Channel3.isEnabled) { Channel3.handleWaveRamWrite(value); return false; } return true; } // Other Memory effects fomr read/write to Lcd/Graphics if (offset >= Lcd.memoryLocationLcdControl && offset <= Graphics.memoryLocationWindowX) { // Not batch processing here for performance // batchProcessGraphics(); if (offset === Lcd.memoryLocationLcdControl) { // Shorcut for isLCD Enabled since it gets "hot" Lcd.updateLcdControl(value); return true; } if (offset === Lcd.memoryLocationLcdStatus) { // We are handling the write here Lcd.updateLcdStatus(value); return false; } // reset the current scanline if the game tries to write to it if (offset === Graphics.memoryLocationScanlineRegister) { Graphics.scanlineRegister = 0; eightBitStoreIntoGBMemory(offset, 0); return false; } // Cache our coincidence compare if (offset === Lcd.memoryLocationCoincidenceCompare) { Lcd.coincidenceCompare = value; return true; } // Do the direct memory access transfer for spriteInformationTable // Check the graphics mode to see if we can write to VRAM // http://gbdev.gg8.se/wiki/articles/Video_Display#Accessing_VRAM_and_OAM if (offset === Graphics.memoryLocationDmaTransfer) { // otherwise, perform a DMA transfer // And allow the original write startDmaTransfer(value); return true; } // Scroll and Window XY switch (offset) { case Graphics.memoryLocationScrollX: Graphics.scrollX = value; return true; case Graphics.memoryLocationScrollY: Graphics.scrollY = value; return true; case Graphics.memoryLocationWindowX: Graphics.windowX = value; return true; case Graphics.memoryLocationWindowY: Graphics.windowY = value; return true; } // Allow the original write, and return since we dont need to look anymore return true; } // Do an HDMA if (offset === Memory.memoryLocationHdmaTrigger) { startHdmaTransfer(value); return false; } // Don't allow banking if we are doing an Hblank HDM transfer // https://gist.github.com/drhelius/3394856 if (offset === Memory.memoryLocationGBCWRAMBank || offset === Memory.memoryLocationGBCVRAMBank) { if (Memory.isHblankHdmaActive) { let hblankHdmaSource = Memory.hblankHdmaSource; if ((hblankHdmaSource >= 0x4000 && hblankHdmaSource <= 0x7fff) || (hblankHdmaSource >= 0xd000 && hblankHdmaSource <= 0xdfff)) { return false; } } } // Handle GBC Pallete Write if (offset >= Palette.memoryLocationBackgroundPaletteIndex && offset <= Palette.memoryLocationSpritePaletteData) { // Incremeenting the palette handled by the write writeColorPaletteToMemory(offset, value); return true; } // Handle timer writes if (offset >= Timers.memoryLocationDividerRegister && offset <= Timers.memoryLocationTimerControl) { // Batch Process batchProcessTimers(); switch (offset) { case Timers.memoryLocationDividerRegister: Timers.updateDividerRegister(); return false; case Timers.memoryLocationTimerCounter: Timers.updateTimerCounter(value); return true; case Timers.memoryLocationTimerModulo: Timers.updateTimerModulo(value); return true; case Timers.memoryLocationTimerControl: Timers.updateTimerControl(value); return true; } return true; } // Handle Joypad writes for HW reg caching if (offset === Joypad.memoryLocationJoypadRegister) { Joypad.updateJoypad(value); } // Handle Interrupt writes if (offset === Interrupts.memoryLocationInterruptRequest) { Interrupts.updateInterruptRequested(value); return true; } if (offset === Interrupts.memoryLocationInterruptEnabled) { Interrupts.updateInterruptEnabled(value); return true; } // Allow the original write return true; }