@soapbox.pub/wasmboy
Version:
Soapbox fork of Wasmboy.
198 lines (168 loc) • 7.72 kB
text/typescript
import { getSaveStateMemoryOffset } from '../core';
import { loadBooleanDirectlyFromWasmMemory, storeBooleanDirectlyToWasmMemory } from '../memory/index';
import { Interrupts } from '../interrupts/index';
// Everything Static as class instances just aren't quite there yet
// https://github.com/AssemblyScript/assemblyscript/blob/master/tests/compiler/showcase.ts
export class Cpu {
// Status to track if we are currently executing the boot rom
static readonly memoryLocationBootROMSwitch: u16 = 0xff50;
static BootROMEnabled: boolean = false;
// Status to track if we are in Gameboy Color Mode, and GBC State
static GBCEnabled: boolean = false;
// Memory Location for the GBC Speed switch
// And the current status
static readonly memoryLocationSpeedSwitch: u16 = 0xff4d;
static GBCDoubleSpeed: boolean = false;
// 8-bit Cpu.registers
static registerA: u8 = 0;
static registerB: u8 = 0;
static registerC: u8 = 0;
static registerD: u8 = 0;
static registerE: u8 = 0;
static registerH: u8 = 0;
static registerL: u8 = 0;
static registerF: u8 = 0;
// 16-bit Cpu.registers
static stackPointer: u16 = 0;
// Boot rom from 0x00 to 0x99, all games start at 0x100
static programCounter: u16 = 0x00;
// Current number of cycles, shouldn't execeed max number of cycles
static currentCycles: i32 = 0;
static CLOCK_SPEED(): i32 {
// 2^23, thanks binji!
// return Cpu.GBCDoubleSpeed ? 8388608 : 4194304;
return 4194304 << (<i32>Cpu.GBCDoubleSpeed);
}
// Cycles Per Frame = Clock Speed / fps
// So: 4194304 / 59.73
static MAX_CYCLES_PER_FRAME(): i32 {
// return Cpu.GBCDoubleSpeed ? 140448 : 70224;
return 70224 << (<i32>Cpu.GBCDoubleSpeed);
}
// HALT and STOP instructions need to stop running opcodes, but simply check timers
// https://github.com/nakardo/node-gameboy/blob/master/lib/cpu/opcodes.js
// Matt said is should work to, so it must work!
// TCAGBD shows three different HALT states. Therefore, we need to handle each
static isHaltNormal: boolean = false;
static isHaltNoJump: boolean = false;
static isHaltBug: boolean = false;
static isStopped: boolean = false;
// See section 4.10 of TCAGBD
// Cpu Halting explained: https://www.reddit.com/r/EmuDev/comments/5ie3k7/infinite_loop_trying_to_pass_blarggs_interrupt/db7xnbe/
static enableHalt(): void {
if (Interrupts.masterInterruptSwitch) {
Cpu.isHaltNormal = true;
return;
}
let haltTypeValue = Interrupts.interruptsEnabledValue & Interrupts.interruptsRequestedValue & 0x1f;
if (haltTypeValue === 0) {
Cpu.isHaltNoJump = true;
return;
}
Cpu.isHaltBug = true;
}
static exitHaltAndStop(): void {
Cpu.isHaltNoJump = false;
Cpu.isHaltNormal = false;
Cpu.isHaltBug = false;
Cpu.isStopped = false;
}
static isHalted(): boolean {
return Cpu.isHaltNormal || Cpu.isHaltNoJump;
}
// Save States
static readonly saveStateSlot: u16 = 0;
// Function to save the state of the class
static saveState(): void {
// Registers
store<u8>(getSaveStateMemoryOffset(0x00, Cpu.saveStateSlot), Cpu.registerA);
store<u8>(getSaveStateMemoryOffset(0x01, Cpu.saveStateSlot), Cpu.registerB);
store<u8>(getSaveStateMemoryOffset(0x02, Cpu.saveStateSlot), Cpu.registerC);
store<u8>(getSaveStateMemoryOffset(0x03, Cpu.saveStateSlot), Cpu.registerD);
store<u8>(getSaveStateMemoryOffset(0x04, Cpu.saveStateSlot), Cpu.registerE);
store<u8>(getSaveStateMemoryOffset(0x05, Cpu.saveStateSlot), Cpu.registerH);
store<u8>(getSaveStateMemoryOffset(0x06, Cpu.saveStateSlot), Cpu.registerL);
store<u8>(getSaveStateMemoryOffset(0x07, Cpu.saveStateSlot), Cpu.registerF);
store<u16>(getSaveStateMemoryOffset(0x08, Cpu.saveStateSlot), Cpu.stackPointer);
store<u16>(getSaveStateMemoryOffset(0x0a, Cpu.saveStateSlot), Cpu.programCounter);
store<i32>(getSaveStateMemoryOffset(0x0c, Cpu.saveStateSlot), Cpu.currentCycles);
storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x11, Cpu.saveStateSlot), Cpu.isHaltNormal);
storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x12, Cpu.saveStateSlot), Cpu.isHaltNoJump);
storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x13, Cpu.saveStateSlot), Cpu.isHaltBug);
storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x14, Cpu.saveStateSlot), Cpu.isStopped);
storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x15, Cpu.saveStateSlot), Cpu.BootROMEnabled);
storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x16, Cpu.saveStateSlot), Cpu.GBCEnabled);
storeBooleanDirectlyToWasmMemory(getSaveStateMemoryOffset(0x17, Cpu.saveStateSlot), Cpu.GBCDoubleSpeed);
}
// Function to load the save state from memory
static loadState(): void {
// Registers
Cpu.registerA = load<u8>(getSaveStateMemoryOffset(0x00, Cpu.saveStateSlot));
Cpu.registerB = load<u8>(getSaveStateMemoryOffset(0x01, Cpu.saveStateSlot));
Cpu.registerC = load<u8>(getSaveStateMemoryOffset(0x02, Cpu.saveStateSlot));
Cpu.registerD = load<u8>(getSaveStateMemoryOffset(0x03, Cpu.saveStateSlot));
Cpu.registerE = load<u8>(getSaveStateMemoryOffset(0x04, Cpu.saveStateSlot));
Cpu.registerH = load<u8>(getSaveStateMemoryOffset(0x05, Cpu.saveStateSlot));
Cpu.registerL = load<u8>(getSaveStateMemoryOffset(0x06, Cpu.saveStateSlot));
Cpu.registerF = load<u8>(getSaveStateMemoryOffset(0x07, Cpu.saveStateSlot));
Cpu.stackPointer = load<u16>(getSaveStateMemoryOffset(0x08, Cpu.saveStateSlot));
Cpu.programCounter = load<u16>(getSaveStateMemoryOffset(0x0a, Cpu.saveStateSlot));
Cpu.currentCycles = load<i32>(getSaveStateMemoryOffset(0x0c, Cpu.saveStateSlot));
Cpu.isHaltNormal = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x11, Cpu.saveStateSlot));
Cpu.isHaltNoJump = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x12, Cpu.saveStateSlot));
Cpu.isHaltBug = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x13, Cpu.saveStateSlot));
Cpu.isStopped = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x14, Cpu.saveStateSlot));
Cpu.BootROMEnabled = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x15, Cpu.saveStateSlot));
Cpu.GBCEnabled = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x16, Cpu.saveStateSlot));
Cpu.GBCDoubleSpeed = loadBooleanDirectlyFromWasmMemory(getSaveStateMemoryOffset(0x17, Cpu.saveStateSlot));
}
}
// Inlined because closure compiler does so
export function initializeCpu(): void {
// Reset all stateful Cpu variables
// Cpu.GBCEnabled is done by core/initialize
Cpu.GBCDoubleSpeed = false;
Cpu.registerA = 0;
Cpu.registerB = 0;
Cpu.registerC = 0;
Cpu.registerD = 0;
Cpu.registerE = 0;
Cpu.registerH = 0;
Cpu.registerL = 0;
Cpu.registerF = 0;
Cpu.stackPointer = 0;
Cpu.programCounter = 0x00;
Cpu.currentCycles = 0;
Cpu.isHaltNormal = false;
Cpu.isHaltNoJump = false;
Cpu.isHaltBug = false;
Cpu.isStopped = false;
// Everything is done by Boot ROM is enabled.
if (Cpu.BootROMEnabled) {
return;
}
if (Cpu.GBCEnabled) {
// CPU Registers
Cpu.registerA = 0x11;
Cpu.registerF = 0x80;
Cpu.registerB = 0x00;
Cpu.registerC = 0x00;
Cpu.registerD = 0xff;
Cpu.registerE = 0x56;
Cpu.registerH = 0x00;
Cpu.registerL = 0x0d;
} else {
// Cpu Registers
Cpu.registerA = 0x01;
Cpu.registerF = 0xb0;
Cpu.registerB = 0x00;
Cpu.registerC = 0x13;
Cpu.registerD = 0x00;
Cpu.registerE = 0xd8;
Cpu.registerH = 0x01;
Cpu.registerL = 0x4d;
}
// Cpu Control Flow
Cpu.programCounter = 0x100;
Cpu.stackPointer = 0xfffe;
}