UNPKG

@soapbox.pub/wasmboy

Version:

Soapbox fork of Wasmboy.

290 lines (250 loc) 14.1 kB
import { Cpu } from '../cpu/index'; import { getSaveStateMemoryOffset } from '../core'; import { eightBitLoadFromGBMemory, eightBitStoreIntoGBMemory, sixteenBitStoreIntoGBMemory, loadBooleanDirectlyFromWasmMemory, storeBooleanDirectlyToWasmMemory } from '../memory/index'; import { setBitOnByte, resetBitOnByte, checkBitOnByte } from '../helpers/index'; export class Interrupts { static masterInterruptSwitch: boolean = false; // According to mooneye, interrupts are not handled until AFTER // Next instruction // https://github.com/Gekkio/mooneye-gb/blob/master/docs/accuracy.markdown static masterInterruptSwitchDelay: boolean = false; // Biut position for each part of the interrupts HW registers static readonly bitPositionVBlankInterrupt: i32 = 0; static readonly bitPositionLcdInterrupt: i32 = 1; static readonly bitPositionTimerInterrupt: i32 = 2; static readonly bitPositionSerialInterrupt: i32 = 3; static readonly bitPositionJoypadInterrupt: i32 = 4; static readonly memoryLocationInterruptEnabled: i32 = 0xffff; // A.K.A interrupt Flag (IE) // Cache which Interrupts are enabled static interruptsEnabledValue: i32 = 0; static isVBlankInterruptEnabled: boolean = false; static isLcdInterruptEnabled: boolean = false; static isTimerInterruptEnabled: boolean = false; static isSerialInterruptEnabled: boolean = false; static isJoypadInterruptEnabled: boolean = false; static updateInterruptEnabled(value: i32): void { Interrupts.isVBlankInterruptEnabled = checkBitOnByte(Interrupts.bitPositionVBlankInterrupt, value); Interrupts.isLcdInterruptEnabled = checkBitOnByte(Interrupts.bitPositionLcdInterrupt, value); Interrupts.isTimerInterruptEnabled = checkBitOnByte(Interrupts.bitPositionTimerInterrupt, value); Interrupts.isSerialInterruptEnabled = checkBitOnByte(Interrupts.bitPositionSerialInterrupt, value); Interrupts.isJoypadInterruptEnabled = checkBitOnByte(Interrupts.bitPositionJoypadInterrupt, value); Interrupts.interruptsEnabledValue = value; } static readonly memoryLocationInterruptRequest: i32 = 0xff0f; // A.K.A interrupt Flag (IF) // Cache which Interrupts are requested static interruptsRequestedValue: i32 = 0; static isVBlankInterruptRequested: boolean = false; static isLcdInterruptRequested: boolean = false; static isTimerInterruptRequested: boolean = false; static isSerialInterruptRequested: boolean = false; static isJoypadInterruptRequested: boolean = false; static updateInterruptRequested(value: i32): void { Interrupts.isVBlankInterruptRequested = checkBitOnByte(Interrupts.bitPositionVBlankInterrupt, value); Interrupts.isLcdInterruptRequested = checkBitOnByte(Interrupts.bitPositionLcdInterrupt, value); Interrupts.isTimerInterruptRequested = checkBitOnByte(Interrupts.bitPositionTimerInterrupt, value); Interrupts.isSerialInterruptRequested = checkBitOnByte(Interrupts.bitPositionSerialInterrupt, value); Interrupts.isJoypadInterruptRequested = checkBitOnByte(Interrupts.bitPositionJoypadInterrupt, value); Interrupts.interruptsRequestedValue = value; } // Function to return if we have any pending interrupts static areInterruptsPending(): boolean { return (Interrupts.interruptsRequestedValue & Interrupts.interruptsEnabledValue & 0x1f) > 0; } // Save States static readonly saveStateSlot: i32 = 2; // Function to save the state of the class static saveState(): void { // Interrupt Master Interrupt Switch storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x00, Interrupts.saveStateSlot), Interrupts.masterInterruptSwitch); storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x01, Interrupts.saveStateSlot), Interrupts.masterInterruptSwitchDelay); // Interrupt Enabled store<u8>(getSaveStateMemoryOffset(0x10, Interrupts.saveStateSlot), <u8>Interrupts.interruptsEnabledValue); storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x11, Interrupts.saveStateSlot), Interrupts.isVBlankInterruptEnabled); storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x12, Interrupts.saveStateSlot), Interrupts.isLcdInterruptEnabled); storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x13, Interrupts.saveStateSlot), Interrupts.isTimerInterruptEnabled); storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x14, Interrupts.saveStateSlot), Interrupts.isSerialInterruptEnabled); storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x15, Interrupts.saveStateSlot), Interrupts.isJoypadInterruptEnabled); // Interrupt Request store<u8>(getSaveStateMemoryOffset(0x20, Interrupts.saveStateSlot), <u8>Interrupts.interruptsRequestedValue); storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x21, Interrupts.saveStateSlot), Interrupts.isVBlankInterruptRequested); storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x22, Interrupts.saveStateSlot), Interrupts.isLcdInterruptRequested); storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x23, Interrupts.saveStateSlot), Interrupts.isTimerInterruptRequested); storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x24, Interrupts.saveStateSlot), Interrupts.isSerialInterruptRequested); storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x25, Interrupts.saveStateSlot), Interrupts.isJoypadInterruptRequested); } // Function to load the save state from memory static loadState(): void { // Interrupt Master Interrupt Switch Interrupts.masterInterruptSwitch = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x00, Interrupts.saveStateSlot)); Interrupts.masterInterruptSwitchDelay = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x01, Interrupts.saveStateSlot)); // Interrupt Enabled Interrupts.interruptsEnabledValue = load<u8>(getSaveStateMemoryOffset(0x10, Interrupts.saveStateSlot)); Interrupts.isVBlankInterruptEnabled = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x11, Interrupts.saveStateSlot)); Interrupts.isLcdInterruptEnabled = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x12, Interrupts.saveStateSlot)); Interrupts.isTimerInterruptEnabled = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x13, Interrupts.saveStateSlot)); Interrupts.isSerialInterruptEnabled = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x14, Interrupts.saveStateSlot)); Interrupts.isJoypadInterruptEnabled = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x15, Interrupts.saveStateSlot)); // Interrupt Request Interrupts.interruptsRequestedValue = load<u8>(getSaveStateMemoryOffset(0x20, Interrupts.saveStateSlot)); Interrupts.isVBlankInterruptRequested = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x21, Interrupts.saveStateSlot)); Interrupts.isLcdInterruptRequested = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x22, Interrupts.saveStateSlot)); Interrupts.isTimerInterruptRequested = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x23, Interrupts.saveStateSlot)); Interrupts.isSerialInterruptRequested = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x24, Interrupts.saveStateSlot)); Interrupts.isJoypadInterruptRequested = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x25, Interrupts.saveStateSlot)); } } // Inlined because closure compiler inlines export function initializeInterrupts(): void { // Values from BGB // IE Interrupts.updateInterruptEnabled(0x00); eightBitStoreIntoGBMemory(Interrupts.memoryLocationInterruptEnabled, Interrupts.interruptsEnabledValue); // IF Interrupts.updateInterruptRequested(0xe1); eightBitStoreIntoGBMemory(Interrupts.memoryLocationInterruptRequest, Interrupts.interruptsRequestedValue); } // NOTE: Interrupts should be handled before reading an opcode // Inlined because closure compiler inlines export function checkInterrupts(): i32 { // First check for our delay was enabled if (Interrupts.masterInterruptSwitchDelay) { Interrupts.masterInterruptSwitch = true; Interrupts.masterInterruptSwitchDelay = false; } // Check if we have an enabled and requested interrupt let isAnInterruptRequestedAndEnabledValue: i32 = Interrupts.interruptsEnabledValue & Interrupts.interruptsRequestedValue & 0x1f; if (isAnInterruptRequestedAndEnabledValue > 0) { // Boolean to track if interrupts were handled // Interrupt handling requires 20 cycles // https://github.com/Gekkio/mooneye-gb/blob/master/docs/accuracy.markdown#what-is-the-exact-timing-of-cpu-servicing-an-interrupt let wasInterruptHandled: boolean = false; // Service our interrupts, if we have the master switch enabled // https://www.reddit.com/r/EmuDev/comments/5ie3k7/infinite_loop_trying_to_pass_blarggs_interrupt/ if (Interrupts.masterInterruptSwitch && !Cpu.isHaltNoJump) { if (Interrupts.isVBlankInterruptEnabled && Interrupts.isVBlankInterruptRequested) { _handleInterrupt(Interrupts.bitPositionVBlankInterrupt); wasInterruptHandled = true; } else if (Interrupts.isLcdInterruptEnabled && Interrupts.isLcdInterruptRequested) { _handleInterrupt(Interrupts.bitPositionLcdInterrupt); wasInterruptHandled = true; } else if (Interrupts.isTimerInterruptEnabled && Interrupts.isTimerInterruptRequested) { _handleInterrupt(Interrupts.bitPositionTimerInterrupt); wasInterruptHandled = true; } else if (Interrupts.isSerialInterruptEnabled && Interrupts.isSerialInterruptRequested) { _handleInterrupt(Interrupts.bitPositionSerialInterrupt); wasInterruptHandled = true; } else if (Interrupts.isJoypadInterruptEnabled && Interrupts.isJoypadInterruptRequested) { _handleInterrupt(Interrupts.bitPositionJoypadInterrupt); wasInterruptHandled = true; } } let interuptHandlerCycles: i32 = 0; if (wasInterruptHandled) { // Interrupt handling requires 20 cycles, TCAGBD interuptHandlerCycles = 20; if (Cpu.isHalted()) { // If the CPU was halted, now is the time to un-halt // Should be done here when the jump occurs according to: // https://www.reddit.com/r/EmuDev/comments/6fmjch/gb_glitches_in_links_awakening_and_pok%C3%A9mon_gold/ Cpu.exitHaltAndStop(); interuptHandlerCycles += 4; } } if (Cpu.isHalted()) { Cpu.exitHaltAndStop(); } return interuptHandlerCycles; } return 0; } function _handleInterrupt(bitPosition: i32): void { // Disable the master switch setInterrupts(false); // Disable the bit on the interruptRequest let interruptRequest = eightBitLoadFromGBMemory(Interrupts.memoryLocationInterruptRequest); interruptRequest = resetBitOnByte(bitPosition, interruptRequest); Interrupts.interruptsRequestedValue = interruptRequest; eightBitStoreIntoGBMemory(Interrupts.memoryLocationInterruptRequest, interruptRequest); // Push the programCounter onto the stacks // Push the next instruction, not the halt itself (TCAGBD). Cpu.stackPointer = Cpu.stackPointer - 2; if (Cpu.isHalted()) { // TODO: This breaks Pokemon Yellow, And OG Link's awakening. Find out why... // sixteenBitStoreIntoGBMemory(Cpu.stackPointer, Cpu.programCounter + 1); sixteenBitStoreIntoGBMemory(Cpu.stackPointer, Cpu.programCounter); } else { sixteenBitStoreIntoGBMemory(Cpu.stackPointer, Cpu.programCounter); } // Jump to the correct interrupt location // Also piggyback off of the switch to reset our HW Register caching // http://www.codeslinger.co.uk/pages/projects/gameboy/interupts.html switch (bitPosition) { case Interrupts.bitPositionVBlankInterrupt: Interrupts.isVBlankInterruptRequested = false; Cpu.programCounter = 0x40; break; case Interrupts.bitPositionLcdInterrupt: Interrupts.isLcdInterruptRequested = false; Cpu.programCounter = 0x48; break; case Interrupts.bitPositionTimerInterrupt: Interrupts.isTimerInterruptRequested = false; Cpu.programCounter = 0x50; break; case Interrupts.bitPositionSerialInterrupt: Interrupts.isSerialInterruptRequested = false; Cpu.programCounter = 0x58; break; case Interrupts.bitPositionJoypadInterrupt: Interrupts.isJoypadInterruptRequested = false; Cpu.programCounter = 0x60; break; } } function _requestInterrupt(bitPosition: i32): void { let interruptRequest = eightBitLoadFromGBMemory(Interrupts.memoryLocationInterruptRequest); // Pass to set the correct interrupt bit on interruptRequest interruptRequest = setBitOnByte(bitPosition, interruptRequest); Interrupts.interruptsRequestedValue = interruptRequest; eightBitStoreIntoGBMemory(Interrupts.memoryLocationInterruptRequest, interruptRequest); } export function setInterrupts(value: boolean): void { // If we are enabling interrupts, // we want to wait 4 cycles before enabling if (value) { Interrupts.masterInterruptSwitchDelay = true; } else { Interrupts.masterInterruptSwitch = false; } } // Inlined because closure compiler inlines export function requestVBlankInterrupt(): void { Interrupts.isVBlankInterruptRequested = true; _requestInterrupt(Interrupts.bitPositionVBlankInterrupt); } // Inlined because closure compiler inlines export function requestLcdInterrupt(): void { Interrupts.isLcdInterruptRequested = true; _requestInterrupt(Interrupts.bitPositionLcdInterrupt); } // Inlined because closure compiler inlines export function requestTimerInterrupt(): void { Interrupts.isTimerInterruptRequested = true; _requestInterrupt(Interrupts.bitPositionTimerInterrupt); } // Inlined because closure compiler inlines export function requestJoypadInterrupt(): void { Interrupts.isJoypadInterruptRequested = true; _requestInterrupt(Interrupts.bitPositionJoypadInterrupt); } // Inlined because closure compiler inlines export function requestSerialInterrupt(): void { Interrupts.isSerialInterruptRequested = true; _requestInterrupt(Interrupts.bitPositionSerialInterrupt); }