mirror of
https://github.com/QuiteAFancyEmerald/Holy-Unblocker.git
synced 2025-05-13 03:50:02 -04:00
3038 lines
105 KiB
JavaScript
3038 lines
105 KiB
JavaScript
function loadboot(p) {
|
||
var boot = [
|
||
0x31, 0xFE, 0xFF, 0xAF, 0x21, 0xFF, 0x9F, 0x32, 0xCB, 0x7C, 0x20, 0xFB, 0x21, 0x26, 0xFF, 0x0E,
|
||
0x11, 0x3E, 0x80, 0x32, 0xE2, 0x0C, 0x3E, 0xF3, 0xE2, 0x32, 0x3E, 0x77, 0x77, 0x3E, 0xFC, 0xE0,
|
||
0x47, 0x11, 0x04, 0x01, 0x21, 0x10, 0x80, 0x1A, 0xCD, 0x95, 0x00, 0xCD, 0x96, 0x00, 0x13, 0x7B,
|
||
0xFE, 0x34, 0x20, 0xF3, 0x11, 0xD8, 0x00, 0x06, 0x08, 0x1A, 0x13, 0x22, 0x23, 0x05, 0x20, 0xF9,
|
||
0x3E, 0x19, 0xEA, 0x10, 0x99, 0x21, 0x2F, 0x99, 0x0E, 0x0C, 0x3D, 0x28, 0x08, 0x32, 0x0D, 0x20,
|
||
0xF9, 0x2E, 0x0F, 0x18, 0xF3, 0x67, 0x3E, 0x64, 0x57, 0xE0, 0x42, 0x3E, 0x91, 0xE0, 0x40, 0x04,
|
||
0x1E, 0x02, 0x0E, 0x0C, 0xF0, 0x44, 0xFE, 0x90, 0x20, 0xFA, 0x0D, 0x20, 0xF7, 0x1D, 0x20, 0xF2,
|
||
0x0E, 0x13, 0x24, 0x7C, 0x1E, 0x83, 0xFE, 0x62, 0x28, 0x06, 0x1E, 0xC1, 0xFE, 0x64, 0x20, 0x06,
|
||
0x7B, 0xE2, 0x0C, 0x3E, 0x87, 0xE2, 0xF0, 0x42, 0x90, 0xE0, 0x42, 0x15, 0x20, 0xD2, 0x05, 0x20,
|
||
0x4F, 0x16, 0x20, 0x18, 0xCB, 0x4F, 0x06, 0x04, 0xC5, 0xCB, 0x11, 0x17, 0xC1, 0xCB, 0x11, 0x17,
|
||
0x05, 0x20, 0xF5, 0x22, 0x23, 0x22, 0x23, 0xC9, 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B,
|
||
0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E,
|
||
0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC,
|
||
0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E, 0x3C, 0x42, 0xB9, 0xA5, 0xB9, 0xA5, 0x42, 0x3C,
|
||
0x21, 0x04, 0x01, 0x11, 0xA8, 0x00, 0x1A, 0x13, 0xBE, 0x00, 0x00, 0x23, 0x7D, 0xFE, 0x34, 0x20,
|
||
0xF5, 0x06, 0x19, 0x78, 0x86, 0x23, 0x05, 0x20, 0xFB, 0x86, 0x00, 0x00, 0x3E, 0x01, 0xE0, 0x50
|
||
];
|
||
|
||
for (var i in boot) {
|
||
p.memory[i] = boot[i];
|
||
}
|
||
p.r.pc = 0;
|
||
p.usingBootRom = true;
|
||
}
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
// CPU class
|
||
var CPU = function(gameboy) {
|
||
this.gameboy = gameboy;
|
||
|
||
this.r = {A:0, F: 0, B:0, C:0, D:0, E:0, H:0, L:0, pc:0, sp:0};
|
||
this.IME = true;
|
||
this.clock = {c: 0, serial: 0};
|
||
this.isHalted = false;
|
||
this.isPaused = false;
|
||
this.usingBootRom = false;
|
||
|
||
this.createDevices();
|
||
};
|
||
|
||
CPU.INTERRUPTS = {
|
||
VBLANK: 0,
|
||
LCDC: 1,
|
||
TIMER: 2,
|
||
SERIAL: 3,
|
||
HILO: 4
|
||
};
|
||
CPU.interruptRoutines = {
|
||
0: function(p){GameboyJS.cpuOps.RSTn(p, 0x40);},
|
||
1: function(p){GameboyJS.cpuOps.RSTn(p, 0x48);},
|
||
2: function(p){GameboyJS.cpuOps.RSTn(p, 0x50);},
|
||
3: function(p){GameboyJS.cpuOps.RSTn(p, 0x58);},
|
||
4: function(p){GameboyJS.cpuOps.RSTn(p, 0x60);}
|
||
};
|
||
|
||
CPU.prototype.createDevices = function() {
|
||
this.memory = new GameboyJS.Memory(this);
|
||
this.timer = new GameboyJS.Timer(this, this.memory);
|
||
this.apu = new GameboyJS.APU(this.memory);
|
||
|
||
this.SERIAL_INTERNAL_INSTR = 512; // instr to wait per bit if internal clock
|
||
this.enableSerial = 0;
|
||
this.serialHandler = GameboyJS.ConsoleSerial;
|
||
};
|
||
|
||
CPU.prototype.reset = function() {
|
||
this.memory.reset();
|
||
|
||
this.r.sp = 0xFFFE;
|
||
};
|
||
|
||
CPU.prototype.loadRom = function(data) {
|
||
this.memory.setRomData(data);
|
||
};
|
||
|
||
CPU.prototype.getRamSize = function() {
|
||
var size = 0;
|
||
switch (this.memory.rb(0x149)) {
|
||
case 1:
|
||
size = 2048;
|
||
break;
|
||
case 2:
|
||
size = 2048 * 4;
|
||
break;
|
||
case 3:
|
||
size = 2048 * 16;
|
||
break;
|
||
}
|
||
|
||
return size;
|
||
};
|
||
|
||
CPU.prototype.getGameName = function() {
|
||
var name = '';
|
||
for (var i = 0x134; i < 0x143; i++) {
|
||
var char = this.memory.rb(i) || 32;
|
||
name += String.fromCharCode(char);
|
||
}
|
||
|
||
return name;
|
||
};
|
||
|
||
// Start the execution of the emulator
|
||
CPU.prototype.run = function() {
|
||
if (this.usingBootRom) {
|
||
this.r.pc = 0x0000;
|
||
} else {
|
||
this.r.pc = 0x0100;
|
||
}
|
||
this.frame();
|
||
};
|
||
|
||
CPU.prototype.stop = function() {
|
||
clearTimeout(this.nextFrameTimer);
|
||
};
|
||
|
||
// Fetch-and-execute loop
|
||
// Will execute instructions for the duration of a frame
|
||
//
|
||
// The screen unit will notify the vblank period which
|
||
// is considered the end of a frame
|
||
//
|
||
// The function is called on a regular basis with a timeout
|
||
CPU.prototype.frame = function() {
|
||
if (!this.isPaused) {
|
||
this.nextFrameTimer = setTimeout(this.frame.bind(this), 1000 / GameboyJS.Screen.physics.FREQUENCY);
|
||
}
|
||
|
||
try {
|
||
var vblank = false;
|
||
while (!vblank) {
|
||
var oldInstrCount = this.clock.c;
|
||
if (!this.isHalted) {
|
||
var opcode = this.fetchOpcode();
|
||
GameboyJS.opcodeMap[opcode](this);
|
||
this.r.F &= 0xF0; // tmp fix
|
||
|
||
if (this.enableSerial) {
|
||
var instr = this.clock.c - oldInstrCount;
|
||
this.clock.serial += instr;
|
||
if (this.clock.serial >= 8 * this.SERIAL_INTERNAL_INSTR) {
|
||
this.endSerialTransfer();
|
||
}
|
||
}
|
||
} else {
|
||
this.clock.c += 4;
|
||
}
|
||
|
||
var elapsed = this.clock.c - oldInstrCount;
|
||
vblank = this.gpu.update(elapsed);
|
||
this.timer.update(elapsed);
|
||
this.input.update();
|
||
this.apu.update(elapsed);
|
||
this.checkInterrupt();
|
||
}
|
||
this.clock.c = 0;
|
||
} catch (e) {
|
||
this.gameboy.handleException(e);
|
||
}
|
||
};
|
||
|
||
CPU.prototype.fetchOpcode = function() {
|
||
var opcode = this.memory.rb(this.r.pc++);
|
||
if (opcode === undefined) {console.log(opcode + ' at ' + (this.r.pc-1).toString(16));this.stop();return;}
|
||
if (!GameboyJS.opcodeMap[opcode]) {
|
||
console.error('Unknown opcode '+opcode.toString(16)+' at address '+(this.r.pc-1).toString(16)+', stopping execution...');
|
||
this.stop();
|
||
return null;
|
||
}
|
||
|
||
return opcode;
|
||
};
|
||
|
||
// read register
|
||
CPU.prototype.rr = function(register) {
|
||
return this.r[register];
|
||
};
|
||
|
||
// write register
|
||
CPU.prototype.wr = function(register, value) {
|
||
this.r[register] = value;
|
||
};
|
||
|
||
CPU.prototype.halt = function() {
|
||
this.isHalted = true;
|
||
};
|
||
CPU.prototype.unhalt = function() {
|
||
this.isHalted = false;
|
||
};
|
||
CPU.prototype.pause = function() {
|
||
this.isPaused = true;
|
||
};
|
||
CPU.prototype.unpause = function() {
|
||
if (this.isPaused) {
|
||
this.isPaused = false;
|
||
this.frame();
|
||
}
|
||
};
|
||
|
||
// Look for interrupt flags
|
||
CPU.prototype.checkInterrupt = function() {
|
||
if (!this.IME) {
|
||
return;
|
||
}
|
||
for (var i = 0; i < 5; i++) {
|
||
var IFval = this.memory.rb(0xFF0F);
|
||
if (GameboyJS.Util.readBit(IFval, i) && this.isInterruptEnable(i)) {
|
||
IFval &= (0xFF - (1<<i));
|
||
this.memory.wb(0xFF0F, IFval);
|
||
this.disableInterrupts();
|
||
this.clock.c += 4; // 20 clocks to serve interrupt, with 16 for RSTn
|
||
CPU.interruptRoutines[i](this);
|
||
break;
|
||
}
|
||
}
|
||
};
|
||
|
||
// Set an interrupt flag
|
||
CPU.prototype.requestInterrupt = function(type) {
|
||
var IFval = this.memory.rb(0xFF0F);
|
||
IFval |= (1 << type)
|
||
this.memory.wb(0xFF0F, IFval) ;
|
||
this.unhalt();
|
||
};
|
||
|
||
CPU.prototype.isInterruptEnable = function(type) {
|
||
return GameboyJS.Util.readBit(this.memory.rb(0xFFFF), type) != 0;
|
||
};
|
||
|
||
CPU.prototype.enableInterrupts = function() {
|
||
this.IME = true;
|
||
};
|
||
CPU.prototype.disableInterrupts = function() {
|
||
this.IME = false;
|
||
};
|
||
|
||
CPU.prototype.enableSerialTransfer = function() {
|
||
this.enableSerial = 1;
|
||
this.clock.serial = 0;
|
||
};
|
||
|
||
CPU.prototype.endSerialTransfer = function() {
|
||
this.enableSerial = 0;
|
||
var data = this.memory.rb(0xFF01);
|
||
this.memory.wb(0xFF02, 0);
|
||
this.serialHandler.out(data);
|
||
this.memory.wb(0xFF01, this.serialHandler.in());
|
||
};
|
||
|
||
CPU.prototype.resetDivTimer = function() {
|
||
this.timer.resetDiv();
|
||
};
|
||
GameboyJS.CPU = CPU;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
var Debug = {};
|
||
// Output a range of 16 memory addresses
|
||
Debug.view_memory = function(addr, gameboy) {
|
||
var memory = gameboy.cpu.memory;
|
||
addr = addr & 0xFFF0;
|
||
var pad = '00';
|
||
var str = addr.toString(16) + ':';
|
||
for (var i = addr; i < addr + 0x10; i++) {
|
||
if ((i & 0x1) == 0) {
|
||
str += ' ';
|
||
}
|
||
var val = memory[i] || 0;
|
||
|
||
val = val.toString(16);
|
||
str += pad.substring(val.length) + val;
|
||
}
|
||
|
||
return str;
|
||
};
|
||
|
||
Debug.view_tile = function(gameboy, index, dataStart) {
|
||
var memory = gameboy.cpu.memory;
|
||
var screen = gameboy.screen;
|
||
var LCDC = screen.deviceram(screen.LCDC);
|
||
if (typeof dataStart === 'undefined') {
|
||
dataStart = 0x8000;
|
||
if (!GameboyJS.Util.readBit(LCDC, 4)) {
|
||
dataStart = 0x8800;
|
||
index = GameboyJS.cpuOps._getSignedValue(index) + 128;
|
||
}
|
||
}
|
||
|
||
var tileData = screen.readTileData(index, dataStart);
|
||
|
||
var pixelData = new Array(8 * 8)
|
||
for (var line = 0; line < 8; line++) {
|
||
var b1 = tileData.shift();
|
||
var b2 = tileData.shift();
|
||
|
||
for (var pixel = 0; pixel < 8; pixel++) {
|
||
var mask = (1 << (7-pixel));
|
||
var colorValue = ((b1 & mask) >> (7-pixel)) + ((b2 & mask) >> (7-pixel))*2;
|
||
pixelData[line * 8 + pixel] = colorValue;
|
||
}
|
||
}
|
||
|
||
var i = 0;
|
||
while (pixelData.length) {
|
||
console.log(i++ + ' ' + pixelData.splice(0, 8).join(''));
|
||
}
|
||
};
|
||
|
||
Debug.list_visible_sprites = function(gameboy) {
|
||
var memory = gameboy.cpu.memory;
|
||
var indexes = new Array();
|
||
for (var i = 0xFE00; i < 0xFE9F; i += 4) {
|
||
var x = memory.oamram(i + 1);
|
||
var y = memory.oamram(i);
|
||
var tileIndex = memory.oamram(i + 2);
|
||
if (x == 0 || x >= 168) {
|
||
continue;
|
||
}
|
||
indexes.push({oamIndex:i, x:x, y:y, tileIndex:tileIndex});
|
||
}
|
||
|
||
return indexes;
|
||
};
|
||
GameboyJS.Debug = Debug;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
var Screen;
|
||
var GPU = function(screen, cpu) {
|
||
this.cpu = cpu;
|
||
this.screen = screen;
|
||
|
||
this.LCDC= 0xFF40;
|
||
this.STAT= 0xFF41;
|
||
this.SCY = 0xFF42;
|
||
this.SCX = 0xFF43;
|
||
this.LY = 0xFF44;
|
||
this.LYC = 0xFF45;
|
||
this.BGP = 0xFF47;
|
||
this.OBP0= 0xFF48;
|
||
this.OBP1= 0xFF49;
|
||
this.WY = 0xFF4A;
|
||
this.WX = 0xFF4B;
|
||
|
||
this.vram = cpu.memory.vram.bind(cpu.memory);
|
||
|
||
this.OAM_START = 0xFE00;
|
||
this.OAM_END = 0xFE9F;
|
||
this.deviceram = cpu.memory.deviceram.bind(cpu.memory);
|
||
this.oamram = cpu.memory.oamram.bind(cpu.memory);
|
||
this.VBLANK_TIME = 70224;
|
||
this.clock = 0;
|
||
this.mode = 2;
|
||
this.line = 0;
|
||
|
||
Screen = GameboyJS.Screen;
|
||
this.buffer = new Array(Screen.physics.WIDTH * Screen.physics.HEIGHT);
|
||
this.tileBuffer = new Array(8);
|
||
this.bgTileCache = {};
|
||
};
|
||
|
||
GPU.tilemap = {
|
||
HEIGHT: 32,
|
||
WIDTH: 32,
|
||
START_0: 0x9800,
|
||
START_1: 0x9C00,
|
||
LENGTH: 0x0400 // 1024 bytes = 32*32
|
||
};
|
||
|
||
GPU.prototype.update = function(clockElapsed) {
|
||
this.clock += clockElapsed;
|
||
var vblank = false;
|
||
|
||
switch (this.mode) {
|
||
case 0: // HBLANK
|
||
if (this.clock >= 204) {
|
||
this.clock -= 204;
|
||
this.line++;
|
||
this.updateLY();
|
||
if (this.line == 144) {
|
||
this.setMode(1);
|
||
vblank = true;
|
||
this.cpu.requestInterrupt(GameboyJS.CPU.INTERRUPTS.VBLANK);
|
||
this.drawFrame();
|
||
} else {
|
||
this.setMode(2);
|
||
}
|
||
}
|
||
break;
|
||
case 1: // VBLANK
|
||
if (this.clock >= 456) {
|
||
this.clock -= 456;
|
||
this.line++;
|
||
if (this.line > 153) {
|
||
this.line = 0;
|
||
this.setMode(2);
|
||
}
|
||
this.updateLY();
|
||
}
|
||
|
||
break;
|
||
case 2: // SCANLINE OAM
|
||
if (this.clock >= 80) {
|
||
this.clock -= 80;
|
||
this.setMode(3);
|
||
}
|
||
break;
|
||
case 3: // SCANLINE VRAM
|
||
if (this.clock >= 172) {
|
||
this.clock -= 172;
|
||
this.drawScanLine(this.line);
|
||
this.setMode(0);
|
||
}
|
||
break;
|
||
}
|
||
|
||
return vblank;
|
||
};
|
||
|
||
GPU.prototype.updateLY = function() {
|
||
this.deviceram(this.LY, this.line);
|
||
var STAT = this.deviceram(this.STAT);
|
||
if (this.deviceram(this.LY) == this.deviceram(this.LYC)) {
|
||
this.deviceram(this.STAT, STAT | (1 << 2));
|
||
if (STAT & (1 << 6)) {
|
||
this.cpu.requestInterrupt(GameboyJS.CPU.INTERRUPTS.LCDC);
|
||
}
|
||
} else {
|
||
this.deviceram(this.STAT, STAT & (0xFF - (1 << 2)));
|
||
}
|
||
};
|
||
|
||
GPU.prototype.setMode = function(mode) {
|
||
this.mode = mode;
|
||
var newSTAT = this.deviceram(this.STAT);
|
||
newSTAT &= 0xFC;
|
||
newSTAT |= mode;
|
||
this.deviceram(this.STAT, newSTAT);
|
||
|
||
if (mode < 3) {
|
||
if (newSTAT & (1 << (3+mode))) {
|
||
this.cpu.requestInterrupt(GameboyJS.CPU.INTERRUPTS.LCDC);
|
||
}
|
||
}
|
||
};
|
||
|
||
// Push one scanline into the main buffer
|
||
GPU.prototype.drawScanLine = function(line) {
|
||
var LCDC = this.deviceram(this.LCDC);
|
||
var enable = GameboyJS.Util.readBit(LCDC, 7);
|
||
if (enable) {
|
||
var lineBuffer = new Array(Screen.physics.WIDTH);
|
||
this.drawBackground(LCDC, line, lineBuffer);
|
||
this.drawSprites(LCDC, line, lineBuffer);
|
||
// TODO draw a line for the window here too
|
||
}
|
||
};
|
||
|
||
GPU.prototype.drawFrame = function() {
|
||
var LCDC = this.deviceram(this.LCDC);
|
||
var enable = GameboyJS.Util.readBit(LCDC, 7);
|
||
if (enable) {
|
||
//this.drawSprites(LCDC);
|
||
this.drawWindow(LCDC);
|
||
}
|
||
this.bgTileCache = {};
|
||
this.screen.render(this.buffer);
|
||
};
|
||
|
||
GPU.prototype.drawBackground = function(LCDC, line, lineBuffer) {
|
||
if (!GameboyJS.Util.readBit(LCDC, 0)) {
|
||
return;
|
||
}
|
||
|
||
var mapStart = GameboyJS.Util.readBit(LCDC, 3) ? GPU.tilemap.START_1 : GPU.tilemap.START_0;
|
||
|
||
var dataStart, signedIndex = false;
|
||
if (GameboyJS.Util.readBit(LCDC, 4)) {
|
||
dataStart = 0x8000;
|
||
} else {
|
||
dataStart = 0x8800;
|
||
signedIndex = true;
|
||
}
|
||
|
||
var bgx = this.deviceram(this.SCX);
|
||
var bgy = this.deviceram(this.SCY);
|
||
var tileLine = ((line + bgy) & 7);
|
||
|
||
// browse BG tilemap for the line to render
|
||
var tileRow = ((((bgy + line) / 8) | 0) & 0x1F);
|
||
var firstTile = ((bgx / 8) | 0) + 32 * tileRow;
|
||
var lastTile = firstTile + Screen.physics.WIDTH / 8 + 1;
|
||
if ((lastTile & 0x1F) < (firstTile & 0x1F)) {
|
||
lastTile -= 32;
|
||
}
|
||
var x = (firstTile & 0x1F) * 8 - bgx; // x position of the first tile's leftmost pixel
|
||
for (var i = firstTile; i != lastTile; i++, (i & 0x1F) == 0 ? i-=32 : null) {
|
||
var tileIndex = this.vram(i + mapStart);
|
||
|
||
if (signedIndex) {
|
||
tileIndex = GameboyJS.Util.getSignedValue(tileIndex) + 128;
|
||
}
|
||
|
||
// try to retrieve the tile data from the cache, or use readTileData() to read from ram
|
||
// TODO find a better cache system now that the BG is rendered line by line
|
||
var tileData = this.bgTileCache[tileIndex] || (this.bgTileCache[tileIndex] = this.readTileData(tileIndex, dataStart));
|
||
|
||
this.drawTileLine(tileData, tileLine);
|
||
this.copyBGTileLine(lineBuffer, this.tileBuffer, x);
|
||
x += 8;
|
||
}
|
||
|
||
this.copyLineToBuffer(lineBuffer, line);
|
||
};
|
||
|
||
// Copy a tile line from a tileBuffer to a line buffer, at a given x position
|
||
GPU.prototype.copyBGTileLine = function(lineBuffer, tileBuffer, x) {
|
||
// copy tile line to buffer
|
||
for (var k = 0; k < 8; k++, x++) {
|
||
if (x < 0 || x >= Screen.physics.WIDTH) continue;
|
||
lineBuffer[x] = tileBuffer[k];
|
||
}
|
||
};
|
||
|
||
// Copy a scanline into the main buffer
|
||
GPU.prototype.copyLineToBuffer = function(lineBuffer, line) {
|
||
var bgPalette = GPU.getPalette(this.deviceram(this.BGP));
|
||
|
||
for (var x = 0; x < Screen.physics.WIDTH; x++) {
|
||
var color = lineBuffer[x];
|
||
this.drawPixel(x, line, bgPalette[color]);
|
||
}
|
||
};
|
||
|
||
// Write a line of a tile (8 pixels) into a buffer array
|
||
GPU.prototype.drawTileLine = function(tileData, line, xflip, yflip) {
|
||
xflip = xflip | 0;
|
||
yflip = yflip | 0;
|
||
var l = yflip ? 7 - line : line;
|
||
var byteIndex = l * 2;
|
||
var b1 = tileData[byteIndex++];
|
||
var b2 = tileData[byteIndex++];
|
||
|
||
var offset = 8;
|
||
for (var pixel = 0; pixel < 8; pixel++) {
|
||
offset--;
|
||
var mask = (1 << offset);
|
||
var colorValue = ((b1 & mask) >> offset) + ((b2 & mask) >> offset)*2;
|
||
var p = xflip ? offset : pixel;
|
||
this.tileBuffer[p] = colorValue;
|
||
}
|
||
};
|
||
|
||
GPU.prototype.drawSprites = function(LCDC, line, lineBuffer) {
|
||
if (!GameboyJS.Util.readBit(LCDC, 1)) {
|
||
return;
|
||
}
|
||
var spriteHeight = GameboyJS.Util.readBit(LCDC, 2) ? 16 : 8;
|
||
|
||
var sprites = new Array();
|
||
for (var i = this.OAM_START; i < this.OAM_END && sprites.length < 10; i += 4) {
|
||
var y = this.oamram(i);
|
||
var x = this.oamram(i+1);
|
||
var index = this.oamram(i+2);
|
||
var flags = this.oamram(i+3);
|
||
|
||
if (y - 16 > line || y - 16 < line - spriteHeight) {
|
||
continue;
|
||
}
|
||
sprites.push({x:x, y:y, index:index, flags:flags})
|
||
}
|
||
|
||
if (sprites.length == 0) return;
|
||
|
||
// cache object to store read tiles from this frame
|
||
var cacheTile = {};
|
||
var spriteLineBuffer = new Array(Screen.physics.WIDTH);
|
||
|
||
for (var i = 0; i < sprites.length; i++) {
|
||
var sprite = sprites[i];
|
||
var tileLine = line - sprite.y + 16;
|
||
var paletteNumber = GameboyJS.Util.readBit(flags, 4);
|
||
var xflip = GameboyJS.Util.readBit(sprite.flags, 5);
|
||
var yflip = GameboyJS.Util.readBit(sprite.flags, 6);
|
||
var tileData = cacheTile[sprite.index] || (cacheTile[sprite.index] = this.readTileData(sprite.index, 0x8000, spriteHeight * 2));
|
||
this.drawTileLine(tileData, tileLine, xflip, yflip);
|
||
this.copySpriteTileLine(spriteLineBuffer, this.tileBuffer, sprite.x - 8, paletteNumber);
|
||
}
|
||
|
||
this.copySpriteLineToBuffer(spriteLineBuffer, line);
|
||
};
|
||
|
||
// Copy a tile line from a tileBuffer to a line buffer, at a given x position
|
||
GPU.prototype.copySpriteTileLine = function(lineBuffer, tileBuffer, x, palette) {
|
||
// copy tile line to buffer
|
||
for (var k = 0; k < 8; k++, x++) {
|
||
if (x < 0 || x >= Screen.physics.WIDTH || tileBuffer[k] == 0) continue;
|
||
lineBuffer[x] = {color:tileBuffer[k], palette: palette};
|
||
}
|
||
};
|
||
|
||
// Copy a sprite scanline into the main buffer
|
||
GPU.prototype.copySpriteLineToBuffer = function(spriteLineBuffer, line) {
|
||
var spritePalettes = {};
|
||
spritePalettes[0] = GPU.getPalette(this.deviceram(this.OBP0));
|
||
spritePalettes[1] = GPU.getPalette(this.deviceram(this.OBP1));
|
||
|
||
for (var x = 0; x < Screen.physics.WIDTH; x++) {
|
||
if (!spriteLineBuffer[x]) continue;
|
||
var color = spriteLineBuffer[x].color;
|
||
if (color === 0) continue;
|
||
var paletteNumber = spriteLineBuffer[x].palette;
|
||
this.drawPixel(x, line, spritePalettes[paletteNumber][color]);
|
||
}
|
||
};
|
||
|
||
GPU.prototype.drawTile = function(tileData, x, y, buffer, bufferWidth, xflip, yflip, spriteMode) {
|
||
xflip = xflip | 0;
|
||
yflip = yflip | 0;
|
||
spriteMode = spriteMode | 0;
|
||
var byteIndex = 0;
|
||
for (var line = 0; line < 8; line++) {
|
||
var l = yflip ? 7 - line : line;
|
||
var b1 = tileData[byteIndex++];
|
||
var b2 = tileData[byteIndex++];
|
||
|
||
for (var pixel = 0; pixel < 8; pixel++) {
|
||
var mask = (1 << (7-pixel));
|
||
var colorValue = ((b1 & mask) >> (7-pixel)) + ((b2 & mask) >> (7-pixel))*2;
|
||
if (spriteMode && colorValue == 0) continue;
|
||
var p = xflip ? 7 - pixel : pixel;
|
||
var bufferIndex = (x + p) + (y + l) * bufferWidth;
|
||
buffer[bufferIndex] = colorValue;
|
||
}
|
||
}
|
||
};
|
||
|
||
// get an array of tile bytes data (16 entries for 8*8px)
|
||
GPU.prototype.readTileData = function(tileIndex, dataStart, tileSize) {
|
||
tileSize = tileSize || 0x10; // 16 bytes / tile by default (8*8 px)
|
||
var tileData = new Array();
|
||
|
||
var tileAddressStart = dataStart + (tileIndex * 0x10);
|
||
for (var i = tileAddressStart; i < tileAddressStart + tileSize; i++) {
|
||
tileData.push(this.vram(i));
|
||
}
|
||
|
||
return tileData;
|
||
};
|
||
|
||
GPU.prototype.drawWindow = function(LCDC) {
|
||
if (!GameboyJS.Util.readBit(LCDC, 5)) {
|
||
return;
|
||
}
|
||
|
||
var buffer = new Array(256*256);
|
||
var mapStart = GameboyJS.Util.readBit(LCDC, 6) ? GPU.tilemap.START_1 : GPU.tilemap.START_0;
|
||
|
||
var dataStart, signedIndex = false;
|
||
if (GameboyJS.Util.readBit(LCDC, 4)) {
|
||
dataStart = 0x8000;
|
||
} else {
|
||
dataStart = 0x8800;
|
||
signedIndex = true;
|
||
}
|
||
|
||
// browse Window tilemap
|
||
for (var i = 0; i < GPU.tilemap.LENGTH; i++) {
|
||
var tileIndex = this.vram(i + mapStart);
|
||
|
||
if (signedIndex) {
|
||
tileIndex = GameboyJS.Util.getSignedValue(tileIndex) + 128;
|
||
}
|
||
|
||
var tileData = this.readTileData(tileIndex, dataStart);
|
||
var x = i % GPU.tilemap.WIDTH;
|
||
var y = (i / GPU.tilemap.WIDTH) | 0;
|
||
this.drawTile(tileData, x * 8, y * 8, buffer, 256);
|
||
}
|
||
|
||
var wx = this.deviceram(this.WX) - 7;
|
||
var wy = this.deviceram(this.WY);
|
||
for (var x = Math.max(0, -wx); x < Math.min(Screen.physics.WIDTH, Screen.physics.WIDTH - wx); x++) {
|
||
for (var y = Math.max(0, -wy); y < Math.min(Screen.physics.HEIGHT, Screen.physics.HEIGHT - wy); y++) {
|
||
var color = buffer[(x & 255) + (y & 255) * 256];
|
||
this.drawPixel(x + wx, y + wy, color);
|
||
}
|
||
}
|
||
};
|
||
|
||
GPU.prototype.drawPixel = function(x, y, color) {
|
||
this.buffer[y * 160 + x] = color;
|
||
};
|
||
|
||
GPU.prototype.getPixel = function(x, y) {
|
||
return this.buffer[y * 160 + x];
|
||
};
|
||
|
||
// Get the palette mapping from a given palette byte as stored in memory
|
||
// A palette will map a tile color to a final palette color index
|
||
// used with Screen.colors to get a shade of grey
|
||
GPU.getPalette = function(paletteByte) {
|
||
var palette = [];
|
||
for (var i = 0; i < 8; i += 2) {
|
||
var shade = (paletteByte & (3 << i)) >> i;
|
||
palette.push(shade);
|
||
}
|
||
return palette;
|
||
};
|
||
|
||
GameboyJS.GPU = GPU;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
// Screen device
|
||
var Screen = function(canvas, pixelSize) {
|
||
this.context = canvas.getContext('2d');
|
||
this.canvas = canvas;
|
||
this.pixelSize = pixelSize || 1;
|
||
this.initImageData();
|
||
};
|
||
|
||
Screen.colors = [
|
||
0xFF,
|
||
0xAA,
|
||
0x55,
|
||
0x00
|
||
];
|
||
|
||
Screen.physics = {
|
||
WIDTH : 160,
|
||
HEIGHT : 144,
|
||
FREQUENCY: 60
|
||
};
|
||
|
||
Screen.prototype.setPixelSize = function(pixelSize) {
|
||
this.pixelSize = pixelSize;
|
||
this.initImageData();
|
||
};
|
||
|
||
Screen.prototype.initImageData = function() {
|
||
this.canvas.width = Screen.physics.WIDTH * this.pixelSize;
|
||
this.canvas.height = Screen.physics.HEIGHT * this.pixelSize;
|
||
this.imageData = this.context.createImageData(this.canvas.width, this.canvas.height);
|
||
};
|
||
|
||
Screen.prototype.clearScreen = function() {
|
||
this.context.fillStyle = '#FFF';
|
||
this.context.fillRect(0, 0, Screen.physics.WIDTH * this.pixelSize, Screen.physics.HEIGHT * this.pixelSize);
|
||
};
|
||
|
||
Screen.prototype.fillImageData = function(buffer) {
|
||
for (var y = 0; y < Screen.physics.HEIGHT; y++) {
|
||
for (var py = 0; py < this.pixelSize; py++) {
|
||
var _y = y * this.pixelSize + py;
|
||
for (var x = 0; x < Screen.physics.WIDTH; x++) {
|
||
for (var px = 0; px < this.pixelSize; px++) {
|
||
var offset = _y * this.canvas.width + (x * this.pixelSize + px);
|
||
var v = Screen.colors[buffer[y * Screen.physics.WIDTH + x]];
|
||
this.imageData.data[offset * 4] = v;
|
||
this.imageData.data[offset * 4 + 1] = v;
|
||
this.imageData.data[offset * 4 + 2] = v;
|
||
this.imageData.data[offset * 4 + 3] = 255;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
Screen.prototype.render = function(buffer) {
|
||
this.fillImageData(buffer);
|
||
this.context.putImageData(this.imageData, 0, 0);
|
||
};
|
||
|
||
GameboyJS.Screen = Screen;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
// This exception should be thrown whenever a critical feature that
|
||
// has not been implemented is requested
|
||
function UnimplementedException(message, fatal) {
|
||
this.message = message;
|
||
this.name = UnimplementedException;
|
||
if (fatal === undefined) {
|
||
fatal = true;
|
||
}
|
||
this.fatal = fatal;
|
||
}
|
||
GameboyJS.UnimplementedException = UnimplementedException;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
// Object for mapping the cartridge RAM
|
||
var ExtRam = function() {
|
||
this.extRam = null;
|
||
this.ramSize = 0;
|
||
this.ramBank = 0;
|
||
};
|
||
|
||
ExtRam.prototype.loadRam = function(game, size) {
|
||
this.gameName = game;
|
||
|
||
this.ramSize = size;
|
||
this.ramBanksize = this.ramSize >= 0x2000 ? 8192 : 2048;
|
||
|
||
var key = this.getStorageKey();
|
||
var data = localStorage.getItem(key);
|
||
if (data == null) {
|
||
this.extRam = Array.apply(null, new Array(this.ramSize)).map(function(){return 0;});
|
||
} else {
|
||
this.extRam = JSON.parse(data);
|
||
if (this.extRam.length != size) {
|
||
console.error('Found RAM data but not matching expected size.');
|
||
}
|
||
}
|
||
};
|
||
|
||
ExtRam.prototype.setRamBank = function(bank) {
|
||
this.ramBank = bank;
|
||
};
|
||
|
||
ExtRam.prototype.manageWrite = function(offset, value) {
|
||
this.extRam[this.ramBank * 8192 + offset] = value;
|
||
};
|
||
|
||
ExtRam.prototype.manageRead = function(offset) {
|
||
return this.extRam[this.ramBank * 8192 + offset];
|
||
};
|
||
|
||
ExtRam.prototype.getStorageKey = function() {
|
||
return this.gameName + '_EXTRAM';;
|
||
};
|
||
// Actually save the RAM in the physical storage (localStorage)
|
||
ExtRam.prototype.saveRamData = function() {
|
||
localStorage.setItem(this.getStorageKey(), JSON.stringify(this.extRam));
|
||
};
|
||
GameboyJS.ExtRam = ExtRam;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
// This is the default buttons mapping for the Gamepad
|
||
// It's optimized for the XBOX pad
|
||
//
|
||
// Any other mapping can be provided as a constructor argument of the Gamepad object
|
||
// An alternative mapping should be an object with keys being the indexes
|
||
// of the gamepad buttons and values the normalized gameboy button names
|
||
var xboxMapping = {
|
||
0: 'UP',
|
||
1: 'DOWN',
|
||
2: 'LEFT',
|
||
3: 'RIGHT',
|
||
4: 'START',
|
||
5: 'SELECT',
|
||
11: 'A',
|
||
12: 'B'
|
||
};
|
||
|
||
// Gamepad listener
|
||
// Communication layer between the Gamepad API and the Input class
|
||
// Any physical controller can be used but the mapping should be provided
|
||
// in order to get an optimal layout of the buttons (see above)
|
||
var Gamepad = function(mapping) {
|
||
this.gamepad = null;
|
||
this.state = {A:0,B:0,START:0,SELECT:0,LEFT:0,RIGHT:0,UP:0,DOWN:0};
|
||
this.pullInterval = null;
|
||
this.buttonMapping = mapping || xboxMapping;
|
||
};
|
||
|
||
// Initialize the keyboard listeners and set up the callbacks
|
||
// for button press / release
|
||
Gamepad.prototype.init = function(onPress, onRelease) {
|
||
this.onPress = onPress;
|
||
this.onRelease = onRelease;
|
||
|
||
var self = this;
|
||
window.addEventListener('gamepadconnected', function(e) {
|
||
self.gamepad = e.gamepad;
|
||
self.activatePull();
|
||
});
|
||
window.addEventListener('gamepaddisconnected', function(e) {
|
||
self.gamepad = null;
|
||
self.deactivatePull();
|
||
});
|
||
};
|
||
|
||
Gamepad.prototype.activatePull = function() {
|
||
this.deactivatePull();
|
||
this.pullInterval = setInterval(this.pullState.bind(this), 100);
|
||
};
|
||
|
||
Gamepad.prototype.deactivatePull = function() {
|
||
clearInterval(this.pullInterval);
|
||
};
|
||
|
||
// Check the state of the current gamepad in order to detect any press/release action
|
||
Gamepad.prototype.pullState = function() {
|
||
for (var index in this.buttonMapping) {
|
||
var button = this.buttonMapping[index];
|
||
var oldState = this.state[button];
|
||
this.state[button] = this.gamepad.buttons[index].pressed;
|
||
|
||
if (this.state[button] == 1 && oldState == 0) {
|
||
this.managePress(button);
|
||
} else if (this.state[button] == 0 && oldState == 1) {
|
||
this.manageRelease(button);
|
||
}
|
||
}
|
||
};
|
||
|
||
Gamepad.prototype.managePress = function(key) {
|
||
this.onPress(key);
|
||
};
|
||
|
||
Gamepad.prototype.manageRelease = function(key) {
|
||
this.onRelease(key);
|
||
};
|
||
|
||
GameboyJS.Gamepad = Gamepad;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
// The Input management system
|
||
//
|
||
// The pressKey() and releaseKey() functions should be called by a device class
|
||
// like GameboyJS.Keyboard after a physical button trigger event
|
||
//
|
||
// They rely on the name of the original buttons as parameters (see Input.keys)
|
||
var Input = function(cpu, pad) {
|
||
this.cpu = cpu;
|
||
this.memory = cpu.memory;
|
||
this.P1 = 0xFF00;
|
||
this.state = 0;
|
||
|
||
pad.init(this.pressKey.bind(this), this.releaseKey.bind(this));
|
||
};
|
||
|
||
Input.keys = {
|
||
START: 0x80,
|
||
SELECT: 0x40,
|
||
B: 0x20,
|
||
A: 0x10,
|
||
DOWN: 0x08,
|
||
UP: 0x04,
|
||
LEFT: 0x02,
|
||
RIGHT: 0x01
|
||
};
|
||
|
||
Input.prototype.pressKey = function(key) {
|
||
this.state |= Input.keys[key];
|
||
|
||
this.cpu.requestInterrupt(GameboyJS.CPU.INTERRUPTS.HILO);
|
||
};
|
||
|
||
Input.prototype.releaseKey = function(key) {
|
||
var mask = 0xFF - Input.keys[key];
|
||
this.state &= mask;
|
||
};
|
||
|
||
Input.prototype.update = function() {
|
||
var value = this.memory.rb(this.P1);
|
||
value = ((~value) & 0x30); // invert the value so 1 means 'active'
|
||
if (value & 0x10) { // direction keys listened
|
||
value |= (this.state & 0x0F);
|
||
} else if (value & 0x20) { // action keys listened
|
||
value |= ((this.state & 0xF0) >> 4);
|
||
} else if ((value & 0x30) == 0) { // no keys listened
|
||
value &= 0xF0;
|
||
}
|
||
|
||
value = ((~value) & 0x3F); // invert back
|
||
this.memory[this.P1] = value;
|
||
};
|
||
GameboyJS.Input = Input;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
// Keyboard listener
|
||
// Does the mapping between the keyboard and the Input class
|
||
var Keyboard = function() {};
|
||
|
||
// Initialize the keyboard listeners and set up the callbacks
|
||
// for button press / release
|
||
Keyboard.prototype.init = function(onPress, onRelease) {
|
||
this.onPress = onPress;
|
||
this.onRelease = onRelease;
|
||
|
||
var self = this;
|
||
document.addEventListener('keydown', function(e) {
|
||
self.managePress(e.keyCode);
|
||
});
|
||
document.addEventListener('keyup', function(e) {
|
||
self.manageRelease(e.keyCode);
|
||
});
|
||
}
|
||
|
||
Keyboard.prototype.managePress = function(keycode) {
|
||
var key = this.translateKey(keycode);
|
||
if (key) {
|
||
this.onPress(key);
|
||
}
|
||
};
|
||
|
||
Keyboard.prototype.manageRelease = function(keycode) {
|
||
var key = this.translateKey(keycode);
|
||
if (key) {
|
||
this.onRelease(key);
|
||
}
|
||
};
|
||
|
||
// Transform a keyboard keycode into a key of the Input.keys object
|
||
Keyboard.prototype.translateKey = function(keycode) {
|
||
var key = null;
|
||
switch (keycode) {
|
||
case 71: // G
|
||
key = 'A';
|
||
break;
|
||
case 66: // B
|
||
key = 'B';
|
||
break;
|
||
case 72: // H
|
||
key = 'START';
|
||
break;
|
||
case 78: // N
|
||
key = 'SELECT';
|
||
break;
|
||
case 37: // left
|
||
key = 'LEFT';
|
||
break;
|
||
case 38: // up
|
||
key = 'UP';
|
||
break;
|
||
case 39: // right
|
||
key = 'RIGHT';
|
||
break;
|
||
case 40: // down
|
||
key = 'DOWN';
|
||
break;
|
||
}
|
||
|
||
return key;
|
||
};
|
||
GameboyJS.Keyboard = Keyboard;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
// List of CPU operations
|
||
// Most operations have been factorized here to limit code redundancy
|
||
//
|
||
// How to read operations:
|
||
// Uppercase letters qualify the kind of operation (LD = LOAD, INC = INCREMENT, etc.)
|
||
// Lowercase letters are used to hint parameters :
|
||
// r = register, n = 1 memory byte, sp = sp register,
|
||
// a = suffix for memory address, i = bit index
|
||
// Example : LDrrar = LOAD operation with two-registers memory address
|
||
// as first parameter and one register value as second
|
||
//
|
||
// Underscore-prefixed functions are here to delegate the logic between similar operations,
|
||
// they should not be called from outside
|
||
//
|
||
// It's up to each operation to update the CPU clock
|
||
var ops = {
|
||
LDrrnn: function(p, r1, r2) {p.wr(r2, p.memory.rb(p.r.pc));p.wr(r1, p.memory.rb(p.r.pc+1)); p.r.pc+=2;p.clock.c += 12;},
|
||
LDrrar: function(p, r1, r2, r3) {ops._LDav(p, GameboyJS.Util.getRegAddr(p, r1, r2), p.r[r3]);p.clock.c += 8;},
|
||
LDrrra: function(p, r1, r2, r3) {p.wr(r1, p.memory.rb(GameboyJS.Util.getRegAddr(p, r2, r3)));p.clock.c += 8;},
|
||
LDrn: function(p, r1) {p.wr(r1, p.memory.rb(p.r.pc++));p.clock.c += 8;},
|
||
LDrr: function(p, r1, r2) {p.wr(r1, p.r[r2]);p.clock.c += 4;},
|
||
LDrar: function(p, r1, r2) {p.memory.wb(p.r[r1]+0xFF00, p.r[r2]);p.clock.c += 8;},
|
||
LDrra: function(p, r1, r2) {p.wr(r1, p.memory.rb(p.r[r2]+0xFF00));p.clock.c += 8;},
|
||
LDspnn: function(p) {p.wr('sp', (p.memory.rb(p.r.pc + 1) << 8) + p.memory.rb(p.r.pc));p.r.pc+=2;p.clock.c += 12;},
|
||
LDsprr: function(p, r1, r2) {p.wr('sp', GameboyJS.Util.getRegAddr(p, r1, r2));p.clock.c += 8;},
|
||
LDnnar: function(p, r1) {var addr=(p.memory.rb(p.r.pc + 1) << 8) + p.memory.rb(p.r.pc);p.memory.wb(addr,p.r[r1]);p.r.pc+=2; p.clock.c += 16;},
|
||
LDrnna: function(p, r1) {var addr=(p.memory.rb(p.r.pc + 1) << 8) + p.memory.rb(p.r.pc);p.wr(r1, p.memory.rb(addr));p.r.pc+=2; p.clock.c += 16;},
|
||
LDrrspn:function(p, r1, r2) {var rel = p.memory.rb(p.r.pc++);rel=GameboyJS.Util.getSignedValue(rel);var val=p.r.sp + rel;
|
||
var c = (p.r.sp&0xFF) + (rel&0xFF) > 0xFF;var h = (p.r.sp & 0xF) + (rel & 0xF) > 0xF;val &= 0xFFFF;
|
||
var f = 0; if(h)f|=0x20;if(c)f|=0x10;p.wr('F', f);
|
||
p.wr(r1, val >> 8);p.wr(r2, val&0xFF);
|
||
p.clock.c+=12;},
|
||
LDnnsp: function(p) {var addr = p.memory.rb(p.r.pc++) + (p.memory.rb(p.r.pc++)<<8); ops._LDav(p, addr, p.r.sp & 0xFF);ops._LDav(p, addr+1, p.r.sp >> 8);p.clock.c+=20;},
|
||
LDrran: function(p, r1, r2){var addr = GameboyJS.Util.getRegAddr(p, r1, r2);ops._LDav(p, addr, p.memory.rb(p.r.pc++));p.clock.c+=12;},
|
||
_LDav: function(p, addr, val){p.memory.wb(addr, val);},
|
||
LDHnar: function(p, r1){p.memory.wb(0xFF00 + p.memory.rb(p.r.pc++), p.r[r1]);p.clock.c+=12;},
|
||
LDHrna: function(p, r1){p.wr(r1, p.memory.rb(0xFF00 + p.memory.rb(p.r.pc++)));p.clock.c+=12;},
|
||
INCrr: function(p, r1, r2) {p.wr(r2, (p.r[r2]+1)&0xFF); if (p.r[r2] == 0) p.wr(r1, (p.r[r1]+1)&0xFF);p.clock.c += 8;},
|
||
INCrra: function(p, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);var val = (p.memory.rb(addr)+1)&0xFF;var z = val==0;var h=(p.memory.rb(addr)&0xF)+1 > 0xF;
|
||
p.memory.wb(addr, val);
|
||
p.r.F&=0x10;if(h)p.r.F|=0x20;if(z)p.r.F|=0x80;
|
||
p.clock.c+=12;},
|
||
INCsp: function(p){p.wr('sp', p.r.sp+1); p.r.sp &= 0xFFFF; p.clock.c+=8;},
|
||
INCr: function(p, r1) {var h = ((p.r[r1]&0xF) + 1)&0x10;p.wr(r1, (p.r[r1] + 1)&0xFF);var z = p.r[r1]==0;
|
||
p.r.F&=0x10;if(h)p.r.F|=0x20;if(z)p.r.F|=0x80;
|
||
p.clock.c += 4;},
|
||
DECrr: function(p, r1, r2) {p.wr(r2, (p.r[r2] - 1) & 0xFF); if (p.r[r2] == 0xFF) p.wr(r1, (p.r[r1] - 1)&0xFF);p.clock.c += 8;},
|
||
DECsp: function(p){p.wr('sp', p.r.sp-1); p.r.sp &= 0xFFFF; p.clock.c+=8;},
|
||
DECr: function(p, r1) {var h = (p.r[r1]&0xF) < 1;p.wr(r1, (p.r[r1] - 1) & 0xFF);var z = p.r[r1]==0;
|
||
p.r.F&=0x10;p.r.F|=0x40;if(h)p.r.F|=0x20;if(z)p.r.F|=0x80;
|
||
p.clock.c += 4;},
|
||
DECrra: function(p, r1, r2){var addr = GameboyJS.Util.getRegAddr(p, r1, r2);var val = (p.memory.rb(addr)-1)&0xFF;var z = val==0;var h=(p.memory.rb(addr)&0xF) < 1;
|
||
p.memory.wb(addr, val);
|
||
p.r.F&=0x10;p.r.F|=0x40;if(h)p.r.F|=0x20;if(z)p.r.F|=0x80;
|
||
p.clock.c+=12;},
|
||
ADDrr: function(p, r1, r2) {var n = p.r[r2];ops._ADDrn(p, r1, n); p.clock.c += 4;},
|
||
ADDrn: function(p, r1) {var n = p.memory.rb(p.r.pc++);ops._ADDrn(p, r1, n); p.clock.c+=8;},
|
||
_ADDrn: function(p, r1, n) {var h=((p.r[r1]&0xF)+(n&0xF))&0x10;p.wr(r1, p.r[r1]+n);var c=p.r[r1]&0x100;p.r[r1]&=0xFF;
|
||
var f = 0;if (p.r[r1]==0)f|=0x80;if (h)f|=0x20;if (c)f|=0x10;p.wr('F', f);},
|
||
ADDrrrr:function(p, r1, r2, r3, r4) {ops._ADDrrn(p, r1, r2, (p.r[r3]<<8) + p.r[r4]); p.clock.c+=8;},
|
||
ADDrrsp:function(p, r1, r2) {ops._ADDrrn(p, r1, r2, p.r.sp); p.clock.c += 8;},
|
||
ADDspn: function(p) {var v = p.memory.rb(p.r.pc++);v = GameboyJS.Util.getSignedValue(v);
|
||
var c = ((p.r.sp&0xFF) + (v&0xFF)) > 0xFF; var h = (p.r.sp & 0xF) + (v&0xF) > 0xF;
|
||
var f = 0; if(h)f|=0x20;if(c)f|=0x10;p.wr('F', f);
|
||
p.wr('sp', (p.r.sp + v) & 0xFFFF);
|
||
p.clock.c+=16;},
|
||
_ADDrrn:function(p, r1, r2, n) {var v1 = (p.r[r1]<<8) + p.r[r2];var v2 = n;
|
||
var res = v1 + v2;var c = res&0x10000;var h = ((v1&0xFFF) + (v2&0xFFF))&0x1000;var z = p.r.F&0x80;
|
||
res&=0xFFFF;p.r[r2]=res&0xFF;res=res>>8;p.r[r1]=res&0xFF;
|
||
var f=0;if(z)f|=0x80;if(h)f|=0x20;if(c)f|=0x10;p.r.F=f;},
|
||
ADCrr: function(p, r1, r2) {var n = p.r[r2]; ops._ADCrn(p, r1, n); p.clock.c += 4;},
|
||
ADCrn: function(p, r1) {var n = p.memory.rb(p.r.pc++); ops._ADCrn(p, r1, n); p.clock.c += 8;},
|
||
_ADCrn: function(p, r1, n) {
|
||
var c = p.r.F&0x10?1:0;var h=((p.r[r1]&0xF)+(n&0xF)+c)&0x10;
|
||
p.wr(r1, p.r[r1]+n+c);c=p.r[r1]&0x100;p.r[r1]&=0xFF;
|
||
var f = 0;if (p.r[r1]==0)f|=0x80;if (h)f|=0x20;if (c)f|=0x10;p.r.F=f;},
|
||
ADCrrra:function(p, r1, r2, r3) {var n = p.memory.rb(GameboyJS.Util.getRegAddr(p, r2, r3)); ops._ADCrn(p, r1, n); p.clock.c += 8;},
|
||
ADDrrra:function(p, r1, r2, r3) {var v = p.memory.rb(GameboyJS.Util.getRegAddr(p, r2, r3));var h=((p.r[r1]&0xF)+(v&0xF))&0x10;p.wr(r1, p.r[r1]+v);var c=p.r[r1]&0x100;p.r[r1]&=0xFF;
|
||
var f = 0;if (p.r[r1]==0)f|=0x80;if (h)f|=0x20;if (c)f|=0x10;p.wr('F', f);
|
||
p.clock.c += 8;},
|
||
SUBr: function(p, r1) {var n = p.r[r1];ops._SUBn(p, n);p.clock.c += 4;},
|
||
SUBn: function(p) {var n = p.memory.rb(p.r.pc++);ops._SUBn(p, n);p.clock.c += 8;},
|
||
SUBrra: function(p, r1, r2) {var n = p.memory.rb(GameboyJS.Util.getRegAddr(p, r1, r2));ops._SUBn(p, n);p.clock.c+=8;},
|
||
_SUBn: function(p, n) {var c = p.r.A < n;var h = (p.r.A&0xF) < (n&0xF);
|
||
p.wr('A', p.r.A - n);p.r.A&=0xFF; var z = p.r.A==0;
|
||
var f = 0x40;if (z)f|=0x80;if (h)f|=0x20;if (c)f|=0x10;p.wr('F', f);},
|
||
SBCn: function(p) {var n = p.memory.rb(p.r.pc++); ops._SBCn(p, n); p.clock.c += 8;},
|
||
SBCr: function(p, r1) {var n = p.r[r1]; ops._SBCn(p, n); p.clock.c += 4;},
|
||
SBCrra: function(p, r1, r2) {var v = p.memory.rb((p.r[r1] << 8) + p.r[r2]); ops._SBCn(p, v); p.clock.c += 8;},
|
||
_SBCn: function(p, n) {var carry = p.r.F&0x10 ? 1 : 0;
|
||
var c = p.r.A < n + carry;var h = (p.r.A&0xF) < (n&0xF) + carry;
|
||
p.wr('A', p.r.A - n - carry); p.r.A&=0xFF; var z = p.r.A == 0;
|
||
var f = 0x40;if (z)f|=0x80;if (h)f|=0x20;if (c)f|=0x10;p.r.F=f;},
|
||
ORr: function(p, r1) {p.r.A|=p.r[r1];p.r.F=(p.r.A==0)?0x80:0x00;p.clock.c += 4;},
|
||
ORn: function(p) {p.r.A|=p.memory.rb(p.r.pc++);p.r.F=(p.r.A==0)?0x80:0x00;p.clock.c += 8;},
|
||
ORrra: function(p, r1, r2) {p.r.A|=p.memory.rb((p.r[r1] << 8)+ p.r[r2]);p.r.F=(p.r.A==0)?0x80:0x00;p.clock.c += 8;},
|
||
ANDr: function(p, r1) {p.r.A&=p.r[r1];p.r.F=(p.r.A==0)?0xA0:0x20;p.clock.c += 4;},
|
||
ANDn: function(p) {p.r.A&=p.memory.rb(p.r.pc++);p.r.F=(p.r.A==0)?0xA0:0x20;p.clock.c += 8;},
|
||
ANDrra: function(p, r1, r2) {p.r.A&=p.memory.rb(GameboyJS.Util.getRegAddr(p, r1, r2));p.r.F=(p.r.A==0)?0xA0:0x20;p.clock.c += 8;},
|
||
XORr: function(p, r1) {p.r.A^=p.r[r1];p.r.F=(p.r.A==0)?0x80:0x00;p.clock.c += 4;},
|
||
XORn: function(p) {p.r.A^=p.memory.rb(p.r.pc++);p.r.F=(p.r.A==0)?0x80:0x00;p.clock.c += 8;},
|
||
XORrra: function(p, r1, r2) {p.r.A^=p.memory.rb((p.r[r1] << 8)+ p.r[r2]);p.r.F=(p.r.A==0)?0x80:0x00;p.clock.c += 8;},
|
||
CPr: function(p, r1) {var n = p.r[r1];ops._CPn(p, n); p.clock.c += 4;},
|
||
CPn: function(p) {var n =p.memory.rb(p.r.pc++);ops._CPn(p, n);p.clock.c+=8;},
|
||
CPrra: function(p, r1, r2) {var n = p.memory.rb(GameboyJS.Util.getRegAddr(p, r1, r2));ops._CPn(p, n);p.clock.c+=8;},
|
||
_CPn: function(p, n) {
|
||
var c = p.r.A < n;var z = p.r.A == n;var h = (p.r.A&0xF) < (n&0xF);
|
||
var f = 0x40;if(z)f+=0x80;if (h)f+=0x20;if (c)f+=0x10;p.r.F=f;},
|
||
RRCr: function(p, r1) {p.r.F=0;var out=p.r[r1] & 0x01;if(out)p.r.F|=0x10;p.r[r1]=(p.r[r1]>>1)|(out*0x80);if(p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},
|
||
RRCrra: function(p, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);p.r.F=0;var out=p.memory.rb(addr)&0x01;if(out)p.r.F|=0x10;p.memory.wb(addr, (p.memory.rb(addr)>>1)|(out*0x80));if(p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},
|
||
RLCr: function(p, r1) {p.r.F=0;var out=p.r[r1]&0x80?1:0;if(out)p.r.F|=0x10;p.r[r1]=((p.r[r1]<<1)+out)&0xFF;if(p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},
|
||
RLCrra: function(p, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);p.r.F=0;var out=p.memory.rb(addr)&0x80?1:0;if(out)p.r.F|=0x10;p.memory.wb(addr, ((p.memory.rb(addr)<<1)+out)&0xFF);if(p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},
|
||
RLr: function(p, r1) {var c=(p.r.F&0x10)?1:0;p.r.F=0;var out=p.r[r1]&0x80;out?p.r.F|=0x10:p.r.F&=0xEF;p.r[r1]=((p.r[r1]<<1)+c)&0xFF;if(p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},
|
||
RLrra: function(p, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);var c=(p.r.F&0x10)?1:0;p.r.F=0;var out=p.memory.rb(addr)&0x80;out?p.r.F|=0x10:p.r.F&=0xEF;p.memory.wb(addr,((p.memory.rb(addr)<<1)+c)&0xFF);if(p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},
|
||
RRr: function(p, r1) {var c=(p.r.F&0x10)?1:0;p.r.F=0;var out=p.r[r1]&0x01;out?p.r.F|=0x10:p.r.F&=0xEF;p.r[r1]=(p.r[r1]>>1)|(c*0x80);if(p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},
|
||
RRrra: function(p, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);var c=(p.r.F&0x10)?1:0;p.r.F=0;var out=p.memory.rb(addr)&0x01;out?p.r.F|=0x10:p.r.F&=0xEF;p.memory.wb(addr,(p.memory.rb(addr)>>1)|(c*0x80));if(p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},
|
||
SRAr: function(p, r1) {p.r.F = 0;if (p.r[r1]&0x01)p.r.F|=0x10;var msb=p.r[r1]&0x80;p.r[r1]=(p.r[r1]>>1)|msb;if (p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},
|
||
SRArra: function(p, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);p.r.F = 0;if (p.memory.rb(addr)&0x01)p.r.F|=0x10;var msb=p.memory.rb(addr)&0x80;p.memory.wb(addr, (p.memory.rb(addr)>>1)|msb);if (p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},
|
||
SLAr: function(p, r1) {p.r.F = 0;if (p.r[r1]&0x80)p.r.F|=0x10;p.r[r1]=(p.r[r1]<<1)&0xFF;if (p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},
|
||
SLArra: function(p, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);p.r.F = 0;if (p.memory.rb(addr)&0x80)p.r.F|=0x10;p.memory.wb(addr, (p.memory.rb(addr)<<1)&0xFF);if (p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},
|
||
SRLr: function(p, r1) {p.r.F = 0;if (p.r[r1]&0x01)p.r.F|=0x10;p.r[r1]=p.r[r1]>>1;if (p.r[r1]==0)p.r.F|=0x80;p.clock.c+=4;},
|
||
SRLrra: function(p, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);p.r.F = 0;if (p.memory.rb(addr)&0x01)p.r.F|=0x10;p.memory.wb(addr, p.memory.rb(addr)>>1);if (p.memory.rb(addr)==0)p.r.F|=0x80;p.clock.c+=12;},
|
||
BITir: function(p, i, r1) {var mask=1<<i;var z=(p.r[r1]&mask)?0:1;var f=p.r.F&0x10;f |= 0x20;if(z)f|=0x80;p.r.F=f;p.clock.c+=4;},
|
||
BITirra:function(p, i, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);var mask=1<<i;var z=(p.memory.rb(addr)&mask)?0:1;var f=p.r.F&0x10;f |= 0x20;if(z)f|=0x80;p.r.F=f;p.clock.c+=8;},
|
||
SETir: function(p, i, r1) {var mask=1<<i;p.r[r1]|=mask;p.clock.c += 4;},
|
||
SETirra:function(p, i, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);var mask=1<<i;p.memory.wb(addr, p.memory.rb(addr)|mask);p.clock.c += 12;},
|
||
RESir: function(p, i, r1) {var mask=0xFF - (1<<i);p.r[r1]&=mask;p.clock.c += 4;},
|
||
RESirra:function(p, i, r1, r2) {var addr = GameboyJS.Util.getRegAddr(p, r1, r2);var mask=0xFF - (1<<i);p.memory.wb(addr, p.memory.rb(addr)&mask);p.clock.c += 12;},
|
||
SWAPr: function(p, r1) {p.r[r1] = ops._SWAPn(p, p.r[r1]);p.clock.c+=4;},
|
||
SWAPrra:function(p, r1, r2){var addr = (p.r[r1] << 8)+ p.r[r2]; p.memory.wb(addr, ops._SWAPn(p, p.memory.rb(addr))); p.clock.c+=12;},
|
||
_SWAPn: function(p, n){p.r.F = n==0?0x80:0;return ((n&0xF0) >> 4) | ((n&0x0F) << 4);},
|
||
JPnn: function(p) {p.wr('pc', (p.memory.rb(p.r.pc+1) << 8) + p.memory.rb(p.r.pc));p.clock.c += 16;},
|
||
JRccn: function(p, cc) {if (GameboyJS.Util.testFlag(p, cc)){var v=p.memory.rb(p.r.pc++);v=GameboyJS.Util.getSignedValue(v);p.r.pc += v;p.clock.c+=4;}else{p.r.pc++;}p.clock.c += 8;},
|
||
JPccnn: function(p, cc) {if (GameboyJS.Util.testFlag(p, cc)){p.wr('pc', (p.memory.rb(p.r.pc+1) << 8) + p.memory.rb(p.r.pc));p.clock.c+=4;}else{p.r.pc+=2;}p.clock.c += 12;},
|
||
JPrr: function(p, r1, r2) {p.r.pc = (p.r[r1] << 8) + p.r[r2];p.clock.c += 4;},
|
||
JRn: function(p) {var v=p.memory.rb(p.r.pc++);v=GameboyJS.Util.getSignedValue(v);p.r.pc += v;p.clock.c += 12;},
|
||
PUSHrr: function(p, r1, r2) {p.wr('sp', p.r.sp-1);p.memory.wb(p.r.sp, p.r[r1]);p.wr('sp', p.r.sp-1);p.memory.wb(p.r.sp, p.r[r2]);p.clock.c+=16;},
|
||
POPrr: function(p, r1, r2) {p.wr(r2, p.memory.rb(p.r.sp));p.wr('sp', p.r.sp+1);p.wr(r1, p.memory.rb(p.r.sp));p.wr('sp', p.r.sp+1);p.clock.c+=12;},
|
||
RSTn: function(p, n) {p.wr('sp', p.r.sp-1);p.memory.wb(p.r.sp,p.r.pc>>8);p.wr('sp', p.r.sp-1);p.memory.wb(p.r.sp,p.r.pc&0xFF);p.r.pc=n;p.clock.c+=16;},
|
||
RET: function(p) {p.r.pc = p.memory.rb(p.r.sp);p.wr('sp', p.r.sp+1);p.r.pc+=p.memory.rb(p.r.sp)<<8;p.wr('sp', p.r.sp+1);p.clock.c += 16;},
|
||
RETcc: function(p, cc) {if (GameboyJS.Util.testFlag(p, cc)){p.r.pc = p.memory.rb(p.r.sp);p.wr('sp', p.r.sp+1);p.r.pc+=p.memory.rb(p.r.sp)<<8;p.wr('sp', p.r.sp+1);p.clock.c+=12;}p.clock.c+=8;},
|
||
CALLnn: function(p) {ops._CALLnn(p); p.clock.c+=24;},
|
||
CALLccnn:function(p, cc) {if (GameboyJS.Util.testFlag(p, cc)){ops._CALLnn(p);p.clock.c+=12;}else{p.r.pc+=2;}p.clock.c+=12; },
|
||
_CALLnn:function(p){p.wr('sp', p.r.sp - 1); p.memory.wb(p.r.sp, ((p.r.pc+2)&0xFF00)>>8);
|
||
p.wr('sp', p.r.sp - 1); p.memory.wb(p.r.sp, (p.r.pc+2)&0x00FF);
|
||
var j=p.memory.rb(p.r.pc)+(p.memory.rb(p.r.pc+1)<<8);p.r.pc=j;},
|
||
CPL: function(p) {p.wr('A', (~p.r.A)&0xFF);p.r.F|=0x60,p.clock.c += 4;},
|
||
CCF: function(p) {p.r.F&=0x9F;p.r.F&0x10?p.r.F&=0xE0:p.r.F|=0x10;p.clock.c += 4;},
|
||
SCF: function(p) {p.r.F&=0x9F;p.r.F|=0x10;p.clock.c+=4;},
|
||
DAA: function(p) {
|
||
var sub = (p.r.F&0x40) ? 1 : 0; var h = (p.r.F&0x20)?1:0;var c = (p.r.F&0x10)?1:0;
|
||
if (sub) {
|
||
if (h) {
|
||
p.r.A = (p.r.A - 0x6) & 0xFF;
|
||
}
|
||
if (c) {
|
||
p.r.A -= 0x60;
|
||
}
|
||
} else {
|
||
if ((p.r.A&0xF) > 9 || h) {
|
||
p.r.A += 0x6;
|
||
}
|
||
if (p.r.A > 0x9F || c) {
|
||
p.r.A += 0x60;
|
||
}
|
||
}
|
||
if (p.r.A&0x100) c = 1;
|
||
|
||
p.r.A &= 0xFF;
|
||
p.r.F &= 0x40;if (p.r.A == 0) p.r.F|=0x80;if (c) p.r.F|=0x10;
|
||
p.clock.c += 4;
|
||
},
|
||
HALT: function(p) {p.halt(); p.clock.c+=4;},
|
||
DI: function(p) {p.disableInterrupts();p.clock.c += 4;},
|
||
EI: function(p) {p.enableInterrupts();p.clock.c += 4;},
|
||
RETI: function(p) {p.enableInterrupts();ops.RET(p);},
|
||
CB: function(p) {var opcode = p.memory.rb(p.r.pc++);
|
||
GameboyJS.opcodeCbmap[opcode](p);
|
||
p.clock.c+=4;}
|
||
};
|
||
GameboyJS.cpuOps = ops;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
var defaultOptions = {
|
||
pad: {class: GameboyJS.Keyboard, mapping: null},
|
||
zoom: 1,
|
||
romReaders: [],
|
||
statusContainerId: 'status',
|
||
gameNameContainerId: 'game-name',
|
||
errorContainerId: 'error'
|
||
};
|
||
|
||
// Gameboy class
|
||
//
|
||
// This object is the entry point of the application
|
||
// Will delegate user actions to the emulated devices
|
||
// and provide information where needed
|
||
var Gameboy = function(canvas, options) {
|
||
options = options || {};
|
||
this.options = GameboyJS.Util.extend({}, defaultOptions, options);
|
||
|
||
var cpu = new GameboyJS.CPU(this);
|
||
var screen = new GameboyJS.Screen(canvas, this.options.zoom);
|
||
var gpu = new GameboyJS.GPU(screen, cpu);
|
||
cpu.gpu = gpu;
|
||
|
||
var pad = new this.options.pad.class(this.options.pad.mapping);
|
||
var input = new GameboyJS.Input(cpu, pad);
|
||
cpu.input = input;
|
||
|
||
this.cpu = cpu;
|
||
this.screen = screen;
|
||
this.input = input;
|
||
this.pad = pad;
|
||
|
||
this.createRom(this.options.romReaders);
|
||
|
||
this.statusContainer = document.getElementById(this.options.statusContainerId) || document.createElement('div');
|
||
this.gameNameContainer = document.getElementById(this.options.gameNameContainerId) || document.createElement('div');
|
||
this.errorContainer = document.getElementById(this.options.errorContainerId) || document.createElement('div');
|
||
};
|
||
|
||
// Create the ROM object and bind one or more readers
|
||
Gameboy.prototype.createRom = function (readers) {
|
||
var rom = new GameboyJS.Rom(this);
|
||
if (readers.length == 0) {
|
||
// add the default rom reader
|
||
var romReader = new GameboyJS.RomFileReader();
|
||
rom.addReader(romReader);
|
||
} else {
|
||
for (var i in readers) {
|
||
if (readers.hasOwnProperty(i)) {
|
||
rom.addReader(readers[i]);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
Gameboy.prototype.startRom = function(rom) {
|
||
this.errorContainer.classList.add('hide');
|
||
this.cpu.reset();
|
||
try {
|
||
this.cpu.loadRom(rom.data);
|
||
this.setStatus('Game Running :');
|
||
this.setGameName(this.cpu.getGameName());
|
||
this.cpu.run();
|
||
} catch (e) {
|
||
this.handleException(e);
|
||
}
|
||
};
|
||
|
||
Gameboy.prototype.pause = function(value) {
|
||
if (value) {
|
||
this.setStatus('Game Paused :');
|
||
this.cpu.pause();
|
||
} else {
|
||
this.setStatus('Game Running :');
|
||
this.cpu.unpause();
|
||
}
|
||
};
|
||
|
||
Gameboy.prototype.error = function(message) {
|
||
this.setStatus('Error during execution');
|
||
this.setError('An error occurred during execution:' + message);
|
||
this.cpu.stop();
|
||
};
|
||
|
||
Gameboy.prototype.setStatus = function(status) {
|
||
this.statusContainer.innerHTML = status;
|
||
};
|
||
// Display an error message
|
||
Gameboy.prototype.setError = function(message) {
|
||
this.errorContainer.classList.remove('hide');
|
||
this.errorContainer.innerHTML = message;
|
||
};
|
||
// Display the name of the game running
|
||
Gameboy.prototype.setGameName = function(name) {
|
||
this.gameNameContainer.innerHTML = name;
|
||
};
|
||
Gameboy.prototype.setSoundEnabled = function(value) {
|
||
if (value) {
|
||
this.cpu.apu.connect();
|
||
} else {
|
||
this.cpu.apu.disconnect();
|
||
}
|
||
};
|
||
Gameboy.prototype.setScreenZoom = function(value) {
|
||
this.screen.setPixelSize(value);
|
||
};
|
||
Gameboy.prototype.handleException = function(e) {
|
||
if (e instanceof GameboyJS.UnimplementedException) {
|
||
if (e.fatal) {
|
||
this.error('This cartridge is not supported ('+ e.message +')');
|
||
} else {
|
||
console.error(e.message);
|
||
}
|
||
} else {
|
||
throw e;
|
||
}
|
||
};
|
||
GameboyJS.Gameboy = Gameboy;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
// Memory bank controllers
|
||
|
||
var MBC = {};
|
||
|
||
// Create an MBC instance depending on the type specified in the cartridge
|
||
MBC.getMbcInstance = function(memory, type) {
|
||
var instance;
|
||
switch (type) {
|
||
case 0x00:
|
||
instance = new MBC0(memory);
|
||
break;
|
||
case 0x01: case 0x02: case 0x03:
|
||
instance = new MBC1(memory);
|
||
break;
|
||
case 0x0F: case 0x10: case 0x11: case 0x12: case 0x13:
|
||
instance = new MBC3(memory);
|
||
break;
|
||
case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E:
|
||
instance = new MBC5(memory);
|
||
break;
|
||
default:
|
||
throw new GameboyJS.UnimplementedException('MBC type not supported');
|
||
}
|
||
|
||
return instance;
|
||
};
|
||
|
||
var MBC1 = function(memory) {
|
||
this.memory = memory;
|
||
this.romBankNumber = 1;
|
||
this.mode = 0; // mode 0 = ROM, mode 1 = RAM
|
||
this.ramEnabled = true;
|
||
this.extRam = new GameboyJS.ExtRam();
|
||
};
|
||
|
||
MBC1.prototype.loadRam = function(game, size) {
|
||
this.extRam.loadRam(game, size);
|
||
};
|
||
|
||
MBC1.prototype.manageWrite = function(addr, value) {
|
||
switch (addr & 0xF000) {
|
||
case 0x0000: case 0x1000: // enable RAM
|
||
this.ramEnabled = (value & 0x0A) ? true : false;
|
||
if (this.ramEnabled) {
|
||
this.extRam.saveRamData();
|
||
}
|
||
break;
|
||
case 0x2000: case 0x3000: // ROM bank number lower 5 bits
|
||
value &= 0x1F;
|
||
if (value == 0) value = 1;
|
||
var mask = this.mode ? 0 : 0xE0;
|
||
this.romBankNumber = (this.romBankNumber & mask) +value;
|
||
this.memory.loadRomBank(this.romBankNumber);
|
||
break;
|
||
case 0x4000: case 0x5000: // RAM bank or high bits ROM
|
||
value &= 0x03;
|
||
if (this.mode == 0) { // ROM upper bits
|
||
this.romBankNumber = (this.romBankNumber&0x1F) | (value << 5);
|
||
this.memory.loadRomBank(this.romBankNumber);
|
||
} else { // RAM bank
|
||
this.extRam.setRamBank(value);
|
||
}
|
||
break;
|
||
case 0x6000: case 0x7000: // ROM / RAM mode
|
||
this.mode = value & 1;
|
||
break;
|
||
case 0xA000: case 0xB000:
|
||
this.extRam.manageWrite(addr - 0xA000, value);
|
||
break;
|
||
}
|
||
};
|
||
MBC1.prototype.readRam = function(addr) {
|
||
return this.extRam.manageRead(addr - 0xA000);
|
||
};
|
||
|
||
var MBC3 = function(memory) {
|
||
this.memory = memory;
|
||
this.romBankNumber = 1;
|
||
this.ramEnabled = true;
|
||
this.extRam = new GameboyJS.ExtRam();
|
||
};
|
||
|
||
MBC3.prototype.loadRam = function(game, size) {
|
||
this.extRam.loadRam(game, size);
|
||
};
|
||
|
||
MBC3.prototype.manageWrite = function(addr, value) {
|
||
switch (addr & 0xF000) {
|
||
case 0x0000: case 0x1000: // enable RAM
|
||
this.ramEnabled = (value & 0x0A) ? true : false;
|
||
if (this.ramEnabled) {
|
||
this.extRam.saveRamData();
|
||
}
|
||
break;
|
||
case 0x2000: case 0x3000: // ROM bank number
|
||
value &= 0x7F;
|
||
if (value == 0) value = 1;
|
||
this.romBankNumber = value;
|
||
this.memory.loadRomBank(this.romBankNumber);
|
||
break;
|
||
case 0x4000: case 0x5000: // RAM bank
|
||
this.extRam.setRamBank(value);
|
||
break;
|
||
case 0x6000: case 0x7000: // Latch clock data
|
||
throw new GameboyJS.UnimplementedException('cartridge clock not supported', false);
|
||
break;
|
||
case 0xA000: case 0xB000:
|
||
this.extRam.manageWrite(addr - 0xA000, value);
|
||
break;
|
||
}
|
||
};
|
||
MBC3.prototype.readRam = function(addr) {
|
||
return this.extRam.manageRead(addr - 0xA000);
|
||
};
|
||
|
||
// declare MBC5 for compatibility with most cartriges
|
||
// does not support rumble feature
|
||
var MBC5 = MBC3;
|
||
|
||
// MBC0 exists for consistency and manages the no-MBC cartriges
|
||
var MBC0 = function(memory) {this.memory = memory;};
|
||
|
||
MBC0.prototype.manageWrite = function(addr, value) {
|
||
this.memory.loadRomBank(value);
|
||
};
|
||
MBC0.prototype.readRam = function(addr) {return 0;};
|
||
MBC0.prototype.loadRam = function() {};
|
||
|
||
GameboyJS.MBC = MBC;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
// Memory unit
|
||
var Memory = function(cpu) {
|
||
this.MEM_SIZE = 65536; // 64KB
|
||
|
||
this.MBCtype = 0;
|
||
this.banksize = 0x4000;
|
||
this.rom = null;
|
||
this.mbc = null;
|
||
this.cpu = cpu;
|
||
};
|
||
|
||
Memory.addresses = {
|
||
VRAM_START : 0x8000,
|
||
VRAM_END : 0x9FFF,
|
||
|
||
EXTRAM_START : 0xA000,
|
||
EXTRAM_END : 0xBFFF,
|
||
|
||
OAM_START : 0xFE00,
|
||
OAM_END : 0xFE9F,
|
||
|
||
DEVICE_START: 0xFF00,
|
||
DEVICE_END: 0xFF7F
|
||
};
|
||
|
||
// Memory can be accessed as an Array
|
||
Memory.prototype = new Array();
|
||
|
||
Memory.prototype.reset = function() {
|
||
this.length = this.MEM_SIZE;
|
||
for (var i = Memory.addresses.VRAM_START; i <= Memory.addresses.VRAM_END; i++) {
|
||
this[i] = 0;
|
||
}
|
||
for (var i = Memory.addresses.DEVICE_START; i <= Memory.addresses.DEVICE_END; i++) {
|
||
this[i] = 0;
|
||
}
|
||
this[0xFFFF] = 0;
|
||
};
|
||
|
||
Memory.prototype.setRomData = function(data) {
|
||
this.rom = data;
|
||
this.loadRomBank(0);
|
||
this.mbc = GameboyJS.MBC.getMbcInstance(this, this[0x147]);
|
||
this.loadRomBank(1);
|
||
this.mbc.loadRam(this.cpu.getGameName(), this.cpu.getRamSize());
|
||
};
|
||
|
||
Memory.prototype.loadRomBank = function(index) {
|
||
var start = index ? 0x4000 : 0x0;
|
||
var romStart = index * 0x4000;
|
||
for (var i = 0; i < this.banksize; i++) {
|
||
this[i + start] = this.rom[romStart + i];
|
||
}
|
||
};
|
||
|
||
// Video ram accessor
|
||
Memory.prototype.vram = function(address) {
|
||
if (address < Memory.addresses.VRAM_START || address > Memory.addresses.VRAM_END) {
|
||
throw 'VRAM access in out of bounds address ' + address;
|
||
}
|
||
|
||
return this[address];
|
||
};
|
||
|
||
// OAM ram accessor
|
||
Memory.prototype.oamram = function(address) {
|
||
if (address < Memory.addresses.OAM_START || address > Memory.addresses.OAM_END) {
|
||
throw 'OAMRAM access in out of bounds address ' + address;
|
||
}
|
||
|
||
return this[address];
|
||
};
|
||
|
||
// Device ram accessor
|
||
Memory.prototype.deviceram = function(address, value) {
|
||
if (address < Memory.addresses.DEVICERAM_START || address > Memory.addresses.DEVICERAM_END) {
|
||
throw 'Device RAM access in out of bounds address ' + address;
|
||
}
|
||
if (typeof value === "undefined") {
|
||
return this[address];
|
||
} else {
|
||
this[address] = value;
|
||
}
|
||
|
||
};
|
||
|
||
// Memory read proxy function
|
||
// Used to centralize memory read access
|
||
Memory.prototype.rb = function (addr) {
|
||
if (addr >= 0xFF10 && addr < 0xFF40) {
|
||
var mask = apuMask[addr - 0xFF10];
|
||
return this[addr] | mask;
|
||
}
|
||
if ((addr >= 0xA000 && addr < 0xC000)) {
|
||
return this.mbc.readRam(addr);
|
||
}
|
||
return this[addr];
|
||
};
|
||
|
||
// Bitmasks for audio addresses reads
|
||
var apuMask = [
|
||
0x80,0x3F,0x00,0xFF,0xBF, // NR10-NR15
|
||
0xFF,0x3F,0x00,0xFF,0xBF, // NR20-NR25
|
||
0x7F,0xFF,0x9F,0xFF,0xBF, // NR30-NR35
|
||
0xFF,0xFF,0x00,0x00,0xBF, // NR40-NR45
|
||
0x00,0x00,0x70, // NR50-NR52
|
||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // Wave RAM
|
||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
|
||
];
|
||
|
||
// Memory write proxy function
|
||
// Used to centralize memory writes and delegate specific behaviour
|
||
// to the correct units
|
||
Memory.prototype.wb = function(addr, value) {
|
||
if (addr < 0x8000 || (addr >= 0xA000 && addr < 0xC000)) { // MBC
|
||
this.mbc.manageWrite(addr, value);
|
||
} else if (addr >= 0xFF10 && addr <= 0xFF3F) { // sound registers
|
||
this.cpu.apu.manageWrite(addr, value);
|
||
} else if (addr == 0xFF00) { // input register
|
||
this[addr] = ((this[addr] & 0x0F) | (value & 0x30));
|
||
} else {
|
||
this[addr] = value;
|
||
if ((addr & 0xFF00) == 0xFF00) {
|
||
if (addr == 0xFF02) {
|
||
if (value & 0x80) {
|
||
this.cpu.enableSerialTransfer();
|
||
}
|
||
}
|
||
if (addr == 0xFF04) {
|
||
this.cpu.resetDivTimer();
|
||
}
|
||
if (addr == 0xFF46) { // OAM DMA transfer
|
||
this.dmaTransfer(value);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Start a DMA transfer (OAM data from cartrige to RAM)
|
||
Memory.prototype.dmaTransfer = function(startAddressPrefix) {
|
||
var startAddress = (startAddressPrefix << 8);
|
||
for (var i = 0; i < 0xA0; i++) {
|
||
this[Memory.addresses.OAM_START + i] = this[startAddress + i];
|
||
}
|
||
};
|
||
|
||
GameboyJS.Memory = Memory;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
var ops = GameboyJS.cpuOps;
|
||
// Each opcode (0 to 0xFF) is associated to a CPU operation
|
||
// CPU operations are implemented separately
|
||
// The cbmap object holds operations for CB prefixed opcodes (0xCB00 to 0xCBFF)
|
||
// Non existent opcodes are commented out and marked empty
|
||
var map = {
|
||
0x00: function(p){p.clock.c += 4;},
|
||
0x01: function(p){ops.LDrrnn(p, 'B', 'C');},
|
||
0x02: function(p){ops.LDrrar(p, 'B', 'C', 'A');},
|
||
0x03: function(p){ops.INCrr(p, 'B', 'C');},
|
||
0x04: function(p){ops.INCr(p, 'B');},
|
||
0x05: function(p){ops.DECr(p, 'B');},
|
||
0x06: function(p){ops.LDrn(p, 'B');},
|
||
0x07: function(p){var out=p.r.A & 0x80?1:0; out ? p.r.F=0x10:p.r.F=0; p.wr('A', ((p.r.A<<1)+out)&0xFF);p.clock.c+=4;},
|
||
0x08: function(p){ops.LDnnsp(p);},
|
||
0x09: function(p){ops.ADDrrrr(p, 'H', 'L', 'B', 'C');},
|
||
0x0A: function(p){ops.LDrrra(p, 'A', 'B', 'C');},
|
||
0x0B: function(p){ops.DECrr(p, 'B', 'C');},
|
||
0x0C: function(p){ops.INCr(p, 'C');},
|
||
0x0D: function(p){ops.DECr(p, 'C');},
|
||
0x0E: function(p){ops.LDrn(p, 'C');},
|
||
0x0F: function(p){var out=p.r.A & 0x01; out ? p.r.F=0x10:p.r.F=0; p.wr('A', (p.r.A>>1)|(out*0x80));p.clock.c+=4;},
|
||
|
||
0x10: function(p){p.r.pc++;p.clock.c+=4;},
|
||
0x11: function(p){ops.LDrrnn(p, 'D', 'E');},
|
||
0x12: function(p){ops.LDrrar(p, 'D', 'E', 'A');},
|
||
0x13: function(p){ops.INCrr(p, 'D', 'E');},
|
||
0x14: function(p){ops.INCr(p, 'D');},
|
||
0x15: function(p){ops.DECr(p, 'D');},
|
||
0x16: function(p){ops.LDrn(p, 'D');},
|
||
0x17: function(p){var c = (p.r.F&0x10)?1:0;var out=p.r.A & 0x80?1:0; out ? p.r.F=0x10:p.r.F=0; p.wr('A',((p.r.A<<1)+c)&0xFF);p.clock.c+=4;},
|
||
0x18: function(p){ops.JRn(p);},
|
||
0x19: function(p){ops.ADDrrrr(p, 'H', 'L', 'D', 'E');},
|
||
0x1A: function(p){ops.LDrrra(p, 'A', 'D', 'E');},
|
||
0x1B: function(p){ops.DECrr(p, 'D', 'E');},
|
||
0x1C: function(p){ops.INCr(p, 'E');},
|
||
0x1D: function(p){ops.DECr(p, 'E');},
|
||
0x1E: function(p){ops.LDrn(p, 'E');},
|
||
0x1F: function(p){var c = (p.r.F&0x10)?1:0;var out=p.r.A & 0x01; out ? p.r.F=0x10:p.r.F=0; p.wr('A', (p.r.A>>1)|(c*0x80));p.clock.c+=4;},
|
||
|
||
0x20: function(p){ops.JRccn(p, 'NZ');},
|
||
0x21: function(p){ops.LDrrnn(p, 'H', 'L');},
|
||
0x22: function(p){ops.LDrrar(p, 'H', 'L', 'A');ops.INCrr(p, 'H', 'L');p.clock.c -= 8;},
|
||
0x23: function(p){ops.INCrr(p, 'H', 'L');},
|
||
0x24: function(p){ops.INCr(p, 'H');},
|
||
0x25: function(p){ops.DECr(p, 'H');},
|
||
0x26: function(p){ops.LDrn(p, 'H');},
|
||
0x27: function(p){ops.DAA(p);},
|
||
0x28: function(p){ops.JRccn(p, 'Z');},
|
||
0x29: function(p){ops.ADDrrrr(p, 'H', 'L', 'H', 'L');},
|
||
0x2A: function(p){ops.LDrrra(p, 'A', 'H', 'L');ops.INCrr(p, 'H', 'L');p.clock.c -= 8;},
|
||
0x2B: function(p){ops.DECrr(p, 'H', 'L');},
|
||
0x2C: function(p){ops.INCr(p, 'L');},
|
||
0x2D: function(p){ops.DECr(p, 'L');},
|
||
0x2E: function(p){ops.LDrn(p, 'L');},
|
||
0x2F: function(p){ops.CPL(p);},
|
||
|
||
0x30: function(p){ops.JRccn(p, 'NC');},
|
||
0x31: function(p){ops.LDspnn(p);},
|
||
0x32: function(p){ops.LDrrar(p, 'H', 'L', 'A');ops.DECrr(p, 'H', 'L');p.clock.c -= 8;},
|
||
0x33: function(p){ops.INCsp(p);},
|
||
0x34: function(p){ops.INCrra(p, 'H', 'L');},
|
||
0x35: function(p){ops.DECrra(p, 'H', 'L');},
|
||
0x36: function(p){ops.LDrran(p, 'H', 'L');},
|
||
0x37: function(p){ops.SCF(p);},
|
||
0x38: function(p){ops.JRccn(p, 'C');},
|
||
0x39: function(p){ops.ADDrrsp(p, 'H', 'L');},
|
||
0x3A: function(p){ops.LDrrra(p, 'A', 'H', 'L');ops.DECrr(p, 'H', 'L');p.clock.c -= 8;},
|
||
0x3B: function(p){ops.DECsp(p);},
|
||
0x3C: function(p){ops.INCr(p, 'A');},
|
||
0x3D: function(p){ops.DECr(p, 'A');},
|
||
0x3E: function(p){ops.LDrn(p, 'A');},
|
||
0x3F: function(p){ops.CCF(p);},
|
||
|
||
0x40: function(p){ops.LDrr(p, 'B', 'B');},
|
||
0x41: function(p){ops.LDrr(p, 'B', 'C');},
|
||
0x42: function(p){ops.LDrr(p, 'B', 'D');},
|
||
0x43: function(p){ops.LDrr(p, 'B', 'E');},
|
||
0x44: function(p){ops.LDrr(p, 'B', 'H');},
|
||
0x45: function(p){ops.LDrr(p, 'B', 'L');},
|
||
0x46: function(p){ops.LDrrra(p, 'B', 'H', 'L');},
|
||
0x47: function(p){ops.LDrr(p, 'B', 'A');},
|
||
0x48: function(p){ops.LDrr(p, 'C', 'B');},
|
||
0x49: function(p){ops.LDrr(p, 'C', 'C');},
|
||
0x4A: function(p){ops.LDrr(p, 'C', 'D');},
|
||
0x4B: function(p){ops.LDrr(p, 'C', 'E');},
|
||
0x4C: function(p){ops.LDrr(p, 'C', 'H');},
|
||
0x4D: function(p){ops.LDrr(p, 'C', 'L');},
|
||
0x4E: function(p){ops.LDrrra(p, 'C', 'H', 'L');},
|
||
0x4F: function(p){ops.LDrr(p, 'C', 'A');},
|
||
|
||
0x50: function(p){ops.LDrr(p, 'D', 'B');},
|
||
0x51: function(p){ops.LDrr(p, 'D', 'C');},
|
||
0x52: function(p){ops.LDrr(p, 'D', 'D');},
|
||
0x53: function(p){ops.LDrr(p, 'D', 'E');},
|
||
0x54: function(p){ops.LDrr(p, 'D', 'H');},
|
||
0x55: function(p){ops.LDrr(p, 'D', 'L');},
|
||
0x56: function(p){ops.LDrrra(p, 'D', 'H', 'L');},
|
||
0x57: function(p){ops.LDrr(p, 'D', 'A');},
|
||
0x58: function(p){ops.LDrr(p, 'E', 'B');},
|
||
0x59: function(p){ops.LDrr(p, 'E', 'C');},
|
||
0x5A: function(p){ops.LDrr(p, 'E', 'D');},
|
||
0x5B: function(p){ops.LDrr(p, 'E', 'E');},
|
||
0x5C: function(p){ops.LDrr(p, 'E', 'H');},
|
||
0x5D: function(p){ops.LDrr(p, 'E', 'L');},
|
||
0x5E: function(p){ops.LDrrra(p, 'E', 'H', 'L');},
|
||
0x5F: function(p){ops.LDrr(p, 'E', 'A');},
|
||
|
||
0x60: function(p){ops.LDrr(p, 'H', 'B');},
|
||
0x61: function(p){ops.LDrr(p, 'H', 'C');},
|
||
0x62: function(p){ops.LDrr(p, 'H', 'D');},
|
||
0x63: function(p){ops.LDrr(p, 'H', 'E');},
|
||
0x64: function(p){ops.LDrr(p, 'H', 'H');},
|
||
0x65: function(p){ops.LDrr(p, 'H', 'L');},
|
||
0x66: function(p){ops.LDrrra(p, 'H', 'H', 'L');},
|
||
0x67: function(p){ops.LDrr(p, 'H', 'A');},
|
||
0x68: function(p){ops.LDrr(p, 'L', 'B');},
|
||
0x69: function(p){ops.LDrr(p, 'L', 'C');},
|
||
0x6A: function(p){ops.LDrr(p, 'L', 'D');},
|
||
0x6B: function(p){ops.LDrr(p, 'L', 'E');},
|
||
0x6C: function(p){ops.LDrr(p, 'L', 'H');},
|
||
0x6D: function(p){ops.LDrr(p, 'L', 'L');},
|
||
0x6E: function(p){ops.LDrrra(p, 'L', 'H', 'L');},
|
||
0x6F: function(p){ops.LDrr(p, 'L', 'A');},
|
||
|
||
0x70: function(p){ops.LDrrar(p, 'H', 'L', 'B');},
|
||
0x71: function(p){ops.LDrrar(p, 'H', 'L', 'C');},
|
||
0x72: function(p){ops.LDrrar(p, 'H', 'L', 'D');},
|
||
0x73: function(p){ops.LDrrar(p, 'H', 'L', 'E');},
|
||
0x74: function(p){ops.LDrrar(p, 'H', 'L', 'H');},
|
||
0x75: function(p){ops.LDrrar(p, 'H', 'L', 'L');},
|
||
0x76: function(p){ops.HALT(p);},
|
||
0x77: function(p){ops.LDrrar(p, 'H', 'L', 'A');},
|
||
0x78: function(p){ops.LDrr(p, 'A', 'B');},
|
||
0x79: function(p){ops.LDrr(p, 'A', 'C');},
|
||
0x7A: function(p){ops.LDrr(p, 'A', 'D');},
|
||
0x7B: function(p){ops.LDrr(p, 'A', 'E');},
|
||
0x7C: function(p){ops.LDrr(p, 'A', 'H');},
|
||
0x7D: function(p){ops.LDrr(p, 'A', 'L');},
|
||
0x7E: function(p){ops.LDrrra(p, 'A', 'H', 'L');},
|
||
0x7F: function(p){ops.LDrr(p, 'A', 'A');},
|
||
|
||
0x80: function(p){ops.ADDrr(p, 'A', 'B');},
|
||
0x81: function(p){ops.ADDrr(p, 'A', 'C');},
|
||
0x82: function(p){ops.ADDrr(p, 'A', 'D');},
|
||
0x83: function(p){ops.ADDrr(p, 'A', 'E');},
|
||
0x84: function(p){ops.ADDrr(p, 'A', 'H');},
|
||
0x85: function(p){ops.ADDrr(p, 'A', 'L');},
|
||
0x86: function(p){ops.ADDrrra(p, 'A', 'H', 'L');},
|
||
0x87: function(p){ops.ADDrr(p, 'A', 'A');},
|
||
0x88: function(p){ops.ADCrr(p, 'A', 'B');},
|
||
0x89: function(p){ops.ADCrr(p, 'A', 'C');},
|
||
0x8A: function(p){ops.ADCrr(p, 'A', 'D');},
|
||
0x8B: function(p){ops.ADCrr(p, 'A', 'E');},
|
||
0x8C: function(p){ops.ADCrr(p, 'A', 'H');},
|
||
0x8D: function(p){ops.ADCrr(p, 'A', 'L');},
|
||
0x8E: function(p){ops.ADCrrra(p, 'A', 'H', 'L');},
|
||
0x8F: function(p){ops.ADCrr(p, 'A', 'A');},
|
||
|
||
0x90: function(p){ops.SUBr(p, 'B');},
|
||
0x91: function(p){ops.SUBr(p, 'C');},
|
||
0x92: function(p){ops.SUBr(p, 'D');},
|
||
0x93: function(p){ops.SUBr(p, 'E');},
|
||
0x94: function(p){ops.SUBr(p, 'H');},
|
||
0x95: function(p){ops.SUBr(p, 'L');},
|
||
0x96: function(p){ops.SUBrra(p, 'H', 'L');},
|
||
0x97: function(p){ops.SUBr(p, 'A');},
|
||
0x98: function(p){ops.SBCr(p, 'B');},
|
||
0x99: function(p){ops.SBCr(p, 'C');},
|
||
0x9A: function(p){ops.SBCr(p, 'D');},
|
||
0x9B: function(p){ops.SBCr(p, 'E');},
|
||
0x9C: function(p){ops.SBCr(p, 'H');},
|
||
0x9D: function(p){ops.SBCr(p, 'L');},
|
||
0x9E: function(p){ops.SBCrra(p, 'H', 'L');},
|
||
0x9F: function(p){ops.SBCr(p, 'A');},
|
||
|
||
0xA0: function(p){ops.ANDr(p, 'B');},
|
||
0xA1: function(p){ops.ANDr(p, 'C');},
|
||
0xA2: function(p){ops.ANDr(p, 'D');},
|
||
0xA3: function(p){ops.ANDr(p, 'E');},
|
||
0xA4: function(p){ops.ANDr(p, 'H');},
|
||
0xA5: function(p){ops.ANDr(p, 'L');},
|
||
0xA6: function(p){ops.ANDrra(p, 'H', 'L');},
|
||
0xA7: function(p){ops.ANDr(p, 'A');},
|
||
0xA8: function(p){ops.XORr(p, 'B');},
|
||
0xA9: function(p){ops.XORr(p, 'C');},
|
||
0xAA: function(p){ops.XORr(p, 'D');},
|
||
0xAB: function(p){ops.XORr(p, 'E');},
|
||
0xAC: function(p){ops.XORr(p, 'H');},
|
||
0xAD: function(p){ops.XORr(p, 'L');},
|
||
0xAE: function(p){ops.XORrra(p, 'H', 'L');},
|
||
0xAF: function(p){ops.XORr(p, 'A');},
|
||
|
||
0xB0: function(p){ops.ORr(p, 'B');},
|
||
0xB1: function(p){ops.ORr(p, 'C');},
|
||
0xB2: function(p){ops.ORr(p, 'D');},
|
||
0xB3: function(p){ops.ORr(p, 'E');},
|
||
0xB4: function(p){ops.ORr(p, 'H');},
|
||
0xB5: function(p){ops.ORr(p, 'L');},
|
||
0xB6: function(p){ops.ORrra(p, 'H', 'L');},
|
||
0xB7: function(p){ops.ORr(p, 'A');},
|
||
0xB8: function(p){ops.CPr(p, 'B');},
|
||
0xB9: function(p){ops.CPr(p, 'C');},
|
||
0xBA: function(p){ops.CPr(p, 'D');},
|
||
0xBB: function(p){ops.CPr(p, 'E');},
|
||
0xBC: function(p){ops.CPr(p, 'H');},
|
||
0xBD: function(p){ops.CPr(p, 'L');},
|
||
0xBE: function(p){ops.CPrra(p, 'H', 'L');},
|
||
0xBF: function(p){ops.CPr(p, 'A');},
|
||
|
||
0xC0: function(p){ops.RETcc(p, 'NZ');},
|
||
0xC1: function(p){ops.POPrr(p, 'B', 'C');},
|
||
0xC2: function(p){ops.JPccnn(p, 'NZ');},
|
||
0xC3: function(p){ops.JPnn(p);},
|
||
0xC4: function(p){ops.CALLccnn(p, 'NZ');},
|
||
0xC5: function(p){ops.PUSHrr(p, 'B', 'C');},
|
||
0xC6: function(p){ops.ADDrn(p, 'A');},
|
||
0xC7: function(p){ops.RSTn(p, 0x00);},
|
||
0xC8: function(p){ops.RETcc(p, 'Z');},
|
||
0xC9: function(p){ops.RET(p);},
|
||
0xCA: function(p){ops.JPccnn(p, 'Z');},
|
||
0xCB: function(p){ops.CB(p);},
|
||
0xCC: function(p){ops.CALLccnn(p, 'Z');},
|
||
0xCD: function(p){ops.CALLnn(p);},
|
||
0xCE: function(p){ops.ADCrn(p, 'A');},
|
||
0xCF: function(p){ops.RSTn(p, 0x08);},
|
||
|
||
0xD0: function(p){ops.RETcc(p, 'NC');},
|
||
0xD1: function(p){ops.POPrr(p, 'D', 'E');},
|
||
0xD2: function(p){ops.JPccnn(p, 'NC');},
|
||
//0xD3 empty
|
||
0xD4: function(p){ops.CALLccnn(p, 'NC');},
|
||
0xD5: function(p){ops.PUSHrr(p, 'D', 'E');},
|
||
0xD6: function(p){ops.SUBn(p);},
|
||
0xD7: function(p){ops.RSTn(p, 0x10);},
|
||
0xD8: function(p){ops.RETcc(p, 'C');},
|
||
0xD9: function(p){ops.RETI(p);},
|
||
0xDA: function(p){ops.JPccnn(p, 'C');},
|
||
//0xDB empty
|
||
0xDC: function(p){ops.CALLccnn(p, 'C');},
|
||
//0xDD empty
|
||
0xDE: function(p){ops.SBCn(p);},
|
||
0xDF: function(p){ops.RSTn(p, 0x18);},
|
||
|
||
0xE0: function(p){ops.LDHnar(p, 'A');},
|
||
0xE1: function(p){ops.POPrr(p, 'H', 'L');},
|
||
0xE2: function(p){ops.LDrar(p, 'C', 'A');},
|
||
//0xE3 empty
|
||
//0xE4 empty
|
||
0xE5: function(p){ops.PUSHrr(p, 'H', 'L');},
|
||
0xE6: function(p){ops.ANDn(p);},
|
||
0xE7: function(p){ops.RSTn(p, 0x20);},
|
||
0xE8: function(p){ops.ADDspn(p);},
|
||
0xE9: function(p){ops.JPrr(p, 'H', 'L');},
|
||
0xEA: function(p){ops.LDnnar(p, 'A');},
|
||
//0xEB empty
|
||
//0xEC empty
|
||
//0xED empty
|
||
0xEE: function(p){ops.XORn(p);},
|
||
0xEF: function(p){ops.RSTn(p, 0x28);},
|
||
|
||
0xF0: function(p){ops.LDHrna(p, 'A');},
|
||
0xF1: function(p){ops.POPrr(p, 'A', 'F');},
|
||
0xF2: function(p){ops.LDrra(p, 'A', 'C');},
|
||
0xF3: function(p){ops.DI(p);},
|
||
//0xF4 empty
|
||
0xF5: function(p){ops.PUSHrr(p, 'A', 'F');},
|
||
0xF6: function(p){ops.ORn(p);},
|
||
0xF7: function(p){ops.RSTn(p, 0x30);},
|
||
0xF8: function(p){ops.LDrrspn(p, 'H', 'L');},
|
||
0xF9: function(p){ops.LDsprr(p, 'H', 'L');},
|
||
0xFA: function(p){ops.LDrnna(p, 'A');},
|
||
0xFB: function(p){ops.EI(p);},
|
||
//0xFC empty
|
||
//0xFD empty
|
||
0xFE: function(p){ops.CPn(p);},
|
||
0xFF: function(p){ops.RSTn(p, 0x38);}
|
||
};
|
||
|
||
var cbmap = {
|
||
0x00: function(p){ops.RLCr(p, 'B');},
|
||
0x01: function(p){ops.RLCr(p, 'C');},
|
||
0x02: function(p){ops.RLCr(p, 'D');},
|
||
0x03: function(p){ops.RLCr(p, 'E');},
|
||
0x04: function(p){ops.RLCr(p, 'H');},
|
||
0x05: function(p){ops.RLCr(p, 'L');},
|
||
0x06: function(p){ops.RLCrra(p, 'H', 'L');},
|
||
0x07: function(p){ops.RLCr(p, 'A');},
|
||
0x08: function(p){ops.RRCr(p, 'B');},
|
||
0x09: function(p){ops.RRCr(p, 'C');},
|
||
0x0A: function(p){ops.RRCr(p, 'D');},
|
||
0x0B: function(p){ops.RRCr(p, 'E');},
|
||
0x0C: function(p){ops.RRCr(p, 'H');},
|
||
0x0D: function(p){ops.RRCr(p, 'L');},
|
||
0x0E: function(p){ops.RRCrra(p, 'H', 'L');},
|
||
0x0F: function(p){ops.RRCr(p, 'A');},
|
||
|
||
0x10: function(p){ops.RLr(p, 'B');},
|
||
0x11: function(p){ops.RLr(p, 'C');},
|
||
0x12: function(p){ops.RLr(p, 'D');},
|
||
0x13: function(p){ops.RLr(p, 'E');},
|
||
0x14: function(p){ops.RLr(p, 'H');},
|
||
0x15: function(p){ops.RLr(p, 'L');},
|
||
0x16: function(p){ops.RLrra(p, 'H', 'L');},
|
||
0x17: function(p){ops.RLr(p, 'A');},
|
||
0x18: function(p){ops.RRr(p, 'B');},
|
||
0x19: function(p){ops.RRr(p, 'C');},
|
||
0x1A: function(p){ops.RRr(p, 'D');},
|
||
0x1B: function(p){ops.RRr(p, 'E');},
|
||
0x1C: function(p){ops.RRr(p, 'H');},
|
||
0x1D: function(p){ops.RRr(p, 'L');},
|
||
0x1E: function(p){ops.RRrra(p, 'H', 'L');},
|
||
0x1F: function(p){ops.RRr(p, 'A');},
|
||
|
||
0x20: function(p){ops.SLAr(p, 'B');},
|
||
0x21: function(p){ops.SLAr(p, 'C');},
|
||
0x22: function(p){ops.SLAr(p, 'D');},
|
||
0x23: function(p){ops.SLAr(p, 'E');},
|
||
0x24: function(p){ops.SLAr(p, 'H');},
|
||
0x25: function(p){ops.SLAr(p, 'L');},
|
||
0x26: function(p){ops.SLArra(p, 'H', 'L');},
|
||
0x27: function(p){ops.SLAr(p, 'A');},
|
||
0x28: function(p){ops.SRAr(p, 'B');},
|
||
0x29: function(p){ops.SRAr(p, 'C');},
|
||
0x2A: function(p){ops.SRAr(p, 'D');},
|
||
0x2B: function(p){ops.SRAr(p, 'E');},
|
||
0x2C: function(p){ops.SRAr(p, 'H');},
|
||
0x2D: function(p){ops.SRAr(p, 'L');},
|
||
0x2E: function(p){ops.SRArra(p, 'H', 'L');},
|
||
0x2F: function(p){ops.SRAr(p, 'A');},
|
||
|
||
0x30: function(p){ops.SWAPr(p, 'B');},
|
||
0x31: function(p){ops.SWAPr(p, 'C');},
|
||
0x32: function(p){ops.SWAPr(p, 'D');},
|
||
0x33: function(p){ops.SWAPr(p, 'E');},
|
||
0x34: function(p){ops.SWAPr(p, 'H');},
|
||
0x35: function(p){ops.SWAPr(p, 'L');},
|
||
0x36: function(p){ops.SWAPrra(p, 'H', 'L');},
|
||
0x37: function(p){ops.SWAPr(p, 'A');},
|
||
0x38: function(p){ops.SRLr(p, 'B');},
|
||
0x39: function(p){ops.SRLr(p, 'C');},
|
||
0x3A: function(p){ops.SRLr(p, 'D');},
|
||
0x3B: function(p){ops.SRLr(p, 'E');},
|
||
0x3C: function(p){ops.SRLr(p, 'H');},
|
||
0x3D: function(p){ops.SRLr(p, 'L');},
|
||
0x3E: function(p){ops.SRLrra(p, 'H', 'L');},
|
||
0x3F: function(p){ops.SRLr(p, 'A');},
|
||
|
||
0x40: function(p){ops.BITir(p, 0, 'B');},
|
||
0x41: function(p){ops.BITir(p, 0, 'C');},
|
||
0x42: function(p){ops.BITir(p, 0, 'D');},
|
||
0x43: function(p){ops.BITir(p, 0, 'E');},
|
||
0x44: function(p){ops.BITir(p, 0, 'H');},
|
||
0x45: function(p){ops.BITir(p, 0, 'L');},
|
||
0x46: function(p){ops.BITirra(p, 0, 'H', 'L');},
|
||
0x47: function(p){ops.BITir(p, 0, 'A');},
|
||
0x48: function(p){ops.BITir(p, 1, 'B');},
|
||
0x49: function(p){ops.BITir(p, 1, 'C');},
|
||
0x4A: function(p){ops.BITir(p, 1, 'D');},
|
||
0x4B: function(p){ops.BITir(p, 1, 'E');},
|
||
0x4C: function(p){ops.BITir(p, 1, 'H');},
|
||
0x4D: function(p){ops.BITir(p, 1, 'L');},
|
||
0x4E: function(p){ops.BITirra(p, 1, 'H', 'L');},
|
||
0x4F: function(p){ops.BITir(p, 1, 'A');},
|
||
|
||
0x50: function(p){ops.BITir(p, 2, 'B');},
|
||
0x51: function(p){ops.BITir(p, 2, 'C');},
|
||
0x52: function(p){ops.BITir(p, 2, 'D');},
|
||
0x53: function(p){ops.BITir(p, 2, 'E');},
|
||
0x54: function(p){ops.BITir(p, 2, 'H');},
|
||
0x55: function(p){ops.BITir(p, 2, 'L');},
|
||
0x56: function(p){ops.BITirra(p, 2, 'H', 'L');},
|
||
0x57: function(p){ops.BITir(p, 2, 'A');},
|
||
0x58: function(p){ops.BITir(p, 3, 'B');},
|
||
0x59: function(p){ops.BITir(p, 3, 'C');},
|
||
0x5A: function(p){ops.BITir(p, 3, 'D');},
|
||
0x5B: function(p){ops.BITir(p, 3, 'E');},
|
||
0x5C: function(p){ops.BITir(p, 3, 'H');},
|
||
0x5D: function(p){ops.BITir(p, 3, 'L');},
|
||
0x5E: function(p){ops.BITirra(p, 3, 'H', 'L');},
|
||
0x5F: function(p){ops.BITir(p, 3, 'A');},
|
||
|
||
0x60: function(p){ops.BITir(p, 4, 'B');},
|
||
0x61: function(p){ops.BITir(p, 4, 'C');},
|
||
0x62: function(p){ops.BITir(p, 4, 'D');},
|
||
0x63: function(p){ops.BITir(p, 4, 'E');},
|
||
0x64: function(p){ops.BITir(p, 4, 'H');},
|
||
0x65: function(p){ops.BITir(p, 4, 'L');},
|
||
0x66: function(p){ops.BITirra(p, 4, 'H', 'L');},
|
||
0x67: function(p){ops.BITir(p, 4, 'A');},
|
||
0x68: function(p){ops.BITir(p, 5, 'B');},
|
||
0x69: function(p){ops.BITir(p, 5, 'C');},
|
||
0x6A: function(p){ops.BITir(p, 5, 'D');},
|
||
0x6B: function(p){ops.BITir(p, 5, 'E');},
|
||
0x6C: function(p){ops.BITir(p, 5, 'H');},
|
||
0x6D: function(p){ops.BITir(p, 5, 'L');},
|
||
0x6E: function(p){ops.BITirra(p, 5, 'H', 'L');},
|
||
0x6F: function(p){ops.BITir(p, 5, 'A');},
|
||
|
||
0x70: function(p){ops.BITir(p, 6, 'B');},
|
||
0x71: function(p){ops.BITir(p, 6, 'C');},
|
||
0x72: function(p){ops.BITir(p, 6, 'D');},
|
||
0x73: function(p){ops.BITir(p, 6, 'E');},
|
||
0x74: function(p){ops.BITir(p, 6, 'H');},
|
||
0x75: function(p){ops.BITir(p, 6, 'L');},
|
||
0x76: function(p){ops.BITirra(p, 6, 'H', 'L');},
|
||
0x77: function(p){ops.BITir(p, 6, 'A');},
|
||
0x78: function(p){ops.BITir(p, 7, 'B');},
|
||
0x79: function(p){ops.BITir(p, 7, 'C');},
|
||
0x7A: function(p){ops.BITir(p, 7, 'D');},
|
||
0x7B: function(p){ops.BITir(p, 7, 'E');},
|
||
0x7C: function(p){ops.BITir(p, 7, 'H');},
|
||
0x7D: function(p){ops.BITir(p, 7, 'L');},
|
||
0x7E: function(p){ops.BITirra(p, 7, 'H', 'L');},
|
||
0x7F: function(p){ops.BITir(p, 7, 'A');},
|
||
|
||
0x80: function(p){ops.RESir(p, 0, 'B');},
|
||
0x81: function(p){ops.RESir(p, 0, 'C');},
|
||
0x82: function(p){ops.RESir(p, 0, 'D');},
|
||
0x83: function(p){ops.RESir(p, 0, 'E');},
|
||
0x84: function(p){ops.RESir(p, 0, 'H');},
|
||
0x85: function(p){ops.RESir(p, 0, 'L');},
|
||
0x86: function(p){ops.RESirra(p, 0, 'H', 'L');},
|
||
0x87: function(p){ops.RESir(p, 0, 'A');},
|
||
0x88: function(p){ops.RESir(p, 1, 'B');},
|
||
0x89: function(p){ops.RESir(p, 1, 'C');},
|
||
0x8A: function(p){ops.RESir(p, 1, 'D');},
|
||
0x8B: function(p){ops.RESir(p, 1, 'E');},
|
||
0x8C: function(p){ops.RESir(p, 1, 'H');},
|
||
0x8D: function(p){ops.RESir(p, 1, 'L');},
|
||
0x8E: function(p){ops.RESirra(p, 1, 'H', 'L');},
|
||
0x8F: function(p){ops.RESir(p, 1, 'A');},
|
||
|
||
0x90: function(p){ops.RESir(p, 2, 'B');},
|
||
0x91: function(p){ops.RESir(p, 2, 'C');},
|
||
0x92: function(p){ops.RESir(p, 2, 'D');},
|
||
0x93: function(p){ops.RESir(p, 2, 'E');},
|
||
0x94: function(p){ops.RESir(p, 2, 'H');},
|
||
0x95: function(p){ops.RESir(p, 2, 'L');},
|
||
0x96: function(p){ops.RESirra(p, 2, 'H', 'L');},
|
||
0x97: function(p){ops.RESir(p, 2, 'A');},
|
||
0x98: function(p){ops.RESir(p, 3, 'B');},
|
||
0x99: function(p){ops.RESir(p, 3, 'C');},
|
||
0x9A: function(p){ops.RESir(p, 3, 'D');},
|
||
0x9B: function(p){ops.RESir(p, 3, 'E');},
|
||
0x9C: function(p){ops.RESir(p, 3, 'H');},
|
||
0x9D: function(p){ops.RESir(p, 3, 'L');},
|
||
0x9E: function(p){ops.RESirra(p, 3, 'H', 'L');},
|
||
0x9F: function(p){ops.RESir(p, 3, 'A');},
|
||
|
||
0xA0: function(p){ops.RESir(p, 4, 'B');},
|
||
0xA1: function(p){ops.RESir(p, 4, 'C');},
|
||
0xA2: function(p){ops.RESir(p, 4, 'D');},
|
||
0xA3: function(p){ops.RESir(p, 4, 'E');},
|
||
0xA4: function(p){ops.RESir(p, 4, 'H');},
|
||
0xA5: function(p){ops.RESir(p, 4, 'L');},
|
||
0xA6: function(p){ops.RESirra(p, 4, 'H', 'L');},
|
||
0xA7: function(p){ops.RESir(p, 4, 'A');},
|
||
0xA8: function(p){ops.RESir(p, 5, 'B');},
|
||
0xA9: function(p){ops.RESir(p, 5, 'C');},
|
||
0xAA: function(p){ops.RESir(p, 5, 'D');},
|
||
0xAB: function(p){ops.RESir(p, 5, 'E');},
|
||
0xAC: function(p){ops.RESir(p, 5, 'H');},
|
||
0xAD: function(p){ops.RESir(p, 5, 'L');},
|
||
0xAE: function(p){ops.RESirra(p, 5, 'H', 'L');},
|
||
0xAF: function(p){ops.RESir(p, 5, 'A');},
|
||
|
||
0xB0: function(p){ops.RESir(p, 6, 'B');},
|
||
0xB1: function(p){ops.RESir(p, 6, 'C');},
|
||
0xB2: function(p){ops.RESir(p, 6, 'D');},
|
||
0xB3: function(p){ops.RESir(p, 6, 'E');},
|
||
0xB4: function(p){ops.RESir(p, 6, 'H');},
|
||
0xB5: function(p){ops.RESir(p, 6, 'L');},
|
||
0xB6: function(p){ops.RESirra(p, 6, 'H', 'L');},
|
||
0xB7: function(p){ops.RESir(p, 6, 'A');},
|
||
0xB8: function(p){ops.RESir(p, 7, 'B');},
|
||
0xB9: function(p){ops.RESir(p, 7, 'C');},
|
||
0xBA: function(p){ops.RESir(p, 7, 'D');},
|
||
0xBB: function(p){ops.RESir(p, 7, 'E');},
|
||
0xBC: function(p){ops.RESir(p, 7, 'H');},
|
||
0xBD: function(p){ops.RESir(p, 7, 'L');},
|
||
0xBE: function(p){ops.RESirra(p, 7, 'H', 'L');},
|
||
0xBF: function(p){ops.RESir(p, 7, 'A');},
|
||
|
||
0xC0: function(p){ops.SETir(p, 0, 'B');},
|
||
0xC1: function(p){ops.SETir(p, 0, 'C');},
|
||
0xC2: function(p){ops.SETir(p, 0, 'D');},
|
||
0xC3: function(p){ops.SETir(p, 0, 'E');},
|
||
0xC4: function(p){ops.SETir(p, 0, 'H');},
|
||
0xC5: function(p){ops.SETir(p, 0, 'L');},
|
||
0xC6: function(p){ops.SETirra(p, 0, 'H', 'L');},
|
||
0xC7: function(p){ops.SETir(p, 0, 'A');},
|
||
0xC8: function(p){ops.SETir(p, 1, 'B');},
|
||
0xC9: function(p){ops.SETir(p, 1, 'C');},
|
||
0xCA: function(p){ops.SETir(p, 1, 'D');},
|
||
0xCB: function(p){ops.SETir(p, 1, 'E');},
|
||
0xCC: function(p){ops.SETir(p, 1, 'H');},
|
||
0xCD: function(p){ops.SETir(p, 1, 'L');},
|
||
0xCE: function(p){ops.SETirra(p, 1, 'H', 'L');},
|
||
0xCF: function(p){ops.SETir(p, 1, 'A');},
|
||
|
||
0xD0: function(p){ops.SETir(p, 2, 'B');},
|
||
0xD1: function(p){ops.SETir(p, 2, 'C');},
|
||
0xD2: function(p){ops.SETir(p, 2, 'D');},
|
||
0xD3: function(p){ops.SETir(p, 2, 'E');},
|
||
0xD4: function(p){ops.SETir(p, 2, 'H');},
|
||
0xD5: function(p){ops.SETir(p, 2, 'L');},
|
||
0xD6: function(p){ops.SETirra(p, 2, 'H', 'L');},
|
||
0xD7: function(p){ops.SETir(p, 2, 'A');},
|
||
0xD8: function(p){ops.SETir(p, 3, 'B');},
|
||
0xD9: function(p){ops.SETir(p, 3, 'C');},
|
||
0xDA: function(p){ops.SETir(p, 3, 'D');},
|
||
0xDB: function(p){ops.SETir(p, 3, 'E');},
|
||
0xDC: function(p){ops.SETir(p, 3, 'H');},
|
||
0xDD: function(p){ops.SETir(p, 3, 'L');},
|
||
0xDE: function(p){ops.SETirra(p, 3, 'H', 'L');},
|
||
0xDF: function(p){ops.SETir(p, 3, 'A');},
|
||
|
||
0xE0: function(p){ops.SETir(p, 4, 'B');},
|
||
0xE1: function(p){ops.SETir(p, 4, 'C');},
|
||
0xE2: function(p){ops.SETir(p, 4, 'D');},
|
||
0xE3: function(p){ops.SETir(p, 4, 'E');},
|
||
0xE4: function(p){ops.SETir(p, 4, 'H');},
|
||
0xE5: function(p){ops.SETir(p, 4, 'L');},
|
||
0xE6: function(p){ops.SETirra(p, 4, 'H', 'L');},
|
||
0xE7: function(p){ops.SETir(p, 4, 'A');},
|
||
0xE8: function(p){ops.SETir(p, 5, 'B');},
|
||
0xE9: function(p){ops.SETir(p, 5, 'C');},
|
||
0xEA: function(p){ops.SETir(p, 5, 'D');},
|
||
0xEB: function(p){ops.SETir(p, 5, 'E');},
|
||
0xEC: function(p){ops.SETir(p, 5, 'H');},
|
||
0xED: function(p){ops.SETir(p, 5, 'L');},
|
||
0xEE: function(p){ops.SETirra(p, 5, 'H', 'L');},
|
||
0xEF: function(p){ops.SETir(p, 5, 'A');},
|
||
|
||
0xF0: function(p){ops.SETir(p, 6, 'B');},
|
||
0xF1: function(p){ops.SETir(p, 6, 'C');},
|
||
0xF2: function(p){ops.SETir(p, 6, 'D');},
|
||
0xF3: function(p){ops.SETir(p, 6, 'E');},
|
||
0xF4: function(p){ops.SETir(p, 6, 'H');},
|
||
0xF5: function(p){ops.SETir(p, 6, 'L');},
|
||
0xF6: function(p){ops.SETirra(p, 6, 'H', 'L');},
|
||
0xF7: function(p){ops.SETir(p, 6, 'A');},
|
||
0xF8: function(p){ops.SETir(p, 7, 'B');},
|
||
0xF9: function(p){ops.SETir(p, 7, 'C');},
|
||
0xFA: function(p){ops.SETir(p, 7, 'D');},
|
||
0xFB: function(p){ops.SETir(p, 7, 'E');},
|
||
0xFC: function(p){ops.SETir(p, 7, 'H');},
|
||
0xFD: function(p){ops.SETir(p, 7, 'L');},
|
||
0xFE: function(p){ops.SETirra(p, 7, 'H', 'L');},
|
||
0xFF: function(p){ops.SETir(p, 7, 'A');}
|
||
};
|
||
GameboyJS.opcodeMap = map;
|
||
GameboyJS.opcodeCbmap = cbmap;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
// A RomAjaxReader is able to load a file through an AJAX request
|
||
var RomAjaxReader = function() {
|
||
|
||
};
|
||
|
||
// The callback argument will be called when a file is successfully
|
||
// read, with the data as argument (Uint8Array)
|
||
RomAjaxReader.prototype.setCallback = function(onLoadCallback) {
|
||
this.callback = onLoadCallback;
|
||
};
|
||
|
||
// This function should be called by application code
|
||
// and will trigger the AJAX call itself and push data to the ROM object
|
||
RomAjaxReader.prototype.loadFromUrl = function(url) {
|
||
if (!url) {
|
||
throw 'No url has been set in order to load a ROM file.';
|
||
}
|
||
var cb = this.callback;
|
||
|
||
var xhr = new XMLHttpRequest();
|
||
xhr.open('GET', url, true);
|
||
xhr.responseType = "arraybuffer";
|
||
xhr.onload = function() {
|
||
var rom = new Uint8Array(xhr.response);
|
||
cb && cb(rom);
|
||
};
|
||
|
||
xhr.send();
|
||
};
|
||
|
||
GameboyJS.RomAjaxReader = RomAjaxReader;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
// A RomDropFileReader is able to load a drag and dropped file
|
||
var RomDropFileReader = function(el) {
|
||
this.dropElement = el;
|
||
if (!this.dropElement) {
|
||
throw 'The RomDropFileReader needs a drop zone.';
|
||
}
|
||
|
||
var self = this;
|
||
this.dropElement.addEventListener('dragenter', function(e) {
|
||
e.preventDefault();
|
||
e.target.classList.add('drag-active');
|
||
});
|
||
this.dropElement.addEventListener('dragleave', function(e) {
|
||
e.preventDefault();
|
||
e.target.classList.remove('drag-active');
|
||
});
|
||
this.dropElement.addEventListener('dragover', function(e) {
|
||
e.preventDefault();
|
||
});
|
||
this.dropElement.addEventListener('drop', function (e) {
|
||
e.target.classList.remove('drag-active');
|
||
if (e.dataTransfer.files.length == 0) {
|
||
return;
|
||
}
|
||
e.preventDefault();
|
||
self.loadFromFile(e.dataTransfer.files[0]);
|
||
});
|
||
};
|
||
|
||
// The callback argument will be called when a file is successfully
|
||
// read, with the data as argument (Uint8Array)
|
||
RomDropFileReader.prototype.setCallback = function(onLoadCallback) {
|
||
this.callback = onLoadCallback;
|
||
};
|
||
|
||
// The file loading logic is the same as the regular file reader
|
||
RomDropFileReader.prototype.loadFromFile = function(file) {
|
||
if (file === undefined) {
|
||
return;
|
||
}
|
||
var fr = new FileReader();
|
||
var cb = this.callback;
|
||
|
||
fr.onload = function() {
|
||
cb && cb(new Uint8Array(fr.result));
|
||
};
|
||
fr.onerror = function(e) {
|
||
console.log('Error reading the file', e.target.error.code)
|
||
};
|
||
fr.readAsArrayBuffer(file);
|
||
};
|
||
|
||
GameboyJS.RomDropFileReader = RomDropFileReader;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
// A RomFileReader is able to load a local file from an input element
|
||
//
|
||
// Expects to be provided a file input element,
|
||
// or will try to find one with the "file" DOM ID
|
||
var RomFileReader = function(el) {
|
||
this.domElement = el || document.getElementById('file');
|
||
if (!this.domElement) {
|
||
throw 'The RomFileReader needs a valid input element.';
|
||
}
|
||
|
||
var self = this;
|
||
this.domElement.addEventListener('change', function(e){
|
||
self.loadFromFile(e.target.files[0]);
|
||
});
|
||
};
|
||
|
||
// The callback argument will be called when a file is successfully
|
||
// read, with the data as argument (Uint8Array)
|
||
RomFileReader.prototype.setCallback = function(onLoadCallback) {
|
||
this.callback = onLoadCallback;
|
||
};
|
||
|
||
// Automatically called when the DOM input is provided with a file
|
||
RomFileReader.prototype.loadFromFile = function(file) {
|
||
if (file === undefined) {
|
||
return;
|
||
}
|
||
var fr = new FileReader();
|
||
var cb = this.callback;
|
||
|
||
fr.onload = function() {
|
||
cb && cb(new Uint8Array(fr.result));
|
||
};
|
||
fr.onerror = function(e) {
|
||
console.log('Error reading the file', e.target.error.code)
|
||
};
|
||
fr.readAsArrayBuffer(file);
|
||
};
|
||
|
||
GameboyJS.RomFileReader = RomFileReader;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
|
||
var Rom = function(gameboy, romReader) {
|
||
this.gameboy = gameboy;
|
||
if (romReader) {
|
||
this.addReader(romReader);
|
||
}
|
||
};
|
||
|
||
Rom.prototype.addReader = function(romReader) {
|
||
var self = this;
|
||
romReader.setCallback(function(data) {
|
||
if (!validate(data)) {
|
||
self.gameboy.error('The file is not a valid GameBoy ROM.');
|
||
return;
|
||
}
|
||
self.data = data;
|
||
self.gameboy.startRom(self);
|
||
});
|
||
};
|
||
|
||
// Validate the checksum of the cartridge header
|
||
function validate(data) {
|
||
var hash = 0;
|
||
for (var i = 0x134; i <= 0x14C; i++) {
|
||
hash = hash - data[i] - 1;
|
||
}
|
||
return (hash & 0xFF) == data[0x14D];
|
||
};
|
||
|
||
GameboyJS.Rom = Rom;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
// Handlers for the Serial port of the Gameboy
|
||
|
||
// The ConsoleSerial is an output-only serial port
|
||
// designed for debug purposes as some test roms output data on the serial port
|
||
//
|
||
// Will regularly output the received byte (converted to string) in the console logs
|
||
// This handler always push the value 0xFF as an input
|
||
var ConsoleSerial = {
|
||
current: '',
|
||
timeout: null,
|
||
out: function(data) {
|
||
ConsoleSerial.current += String.fromCharCode(data);
|
||
if (data == 10) {
|
||
ConsoleSerial.print();
|
||
} else {
|
||
clearTimeout(ConsoleSerial.timeout);
|
||
ConsoleSerial.timeout = setTimeout(ConsoleSerial.print, 500);
|
||
}
|
||
},
|
||
in: function() {
|
||
return 0xFF;
|
||
},
|
||
print: function() {
|
||
clearTimeout(ConsoleSerial.timeout);
|
||
console.log('serial: '+ConsoleSerial.current);
|
||
ConsoleSerial.current = '';
|
||
}
|
||
};
|
||
GameboyJS.ConsoleSerial = ConsoleSerial;
|
||
|
||
// A DummySerial outputs nothing and always inputs 0xFF
|
||
var DummySerial = {
|
||
out: function() {},
|
||
in: function() {
|
||
return 0xFF;
|
||
}
|
||
};
|
||
GameboyJS.DummySerial = DummySerial;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
// Audio Processing unit
|
||
// Listens the write accesses to the audio-reserved memory addresses
|
||
// and dispatches the data to the sound channels
|
||
var APU = function(memory) {
|
||
this.memory = memory;
|
||
this.enabled = false;
|
||
|
||
AudioContext = window.AudioContext || window.webkitAudioContext;
|
||
var audioContext = new AudioContext();
|
||
|
||
this.channel1 = new GameboyJS.Channel1(this, 1, audioContext);
|
||
this.channel2 = new GameboyJS.Channel1(this, 2, audioContext);
|
||
this.channel3 = new GameboyJS.Channel3(this, 3, audioContext);
|
||
this.channel4 = new GameboyJS.Channel4(this, 4, audioContext);
|
||
|
||
};
|
||
APU.prototype.connect = function() {
|
||
this.channel1.enable();
|
||
this.channel2.enable();
|
||
this.channel3.enable();
|
||
};
|
||
APU.prototype.disconnect = function() {
|
||
this.channel1.disable();
|
||
this.channel2.disable();
|
||
this.channel3.disable();
|
||
};
|
||
// Updates the states of each channel given the elapsed time
|
||
// (in instructions) since last update
|
||
APU.prototype.update = function(clockElapsed) {
|
||
if (this.enabled == false) return;
|
||
|
||
this.channel1.update(clockElapsed);
|
||
this.channel2.update(clockElapsed);
|
||
this.channel3.update(clockElapsed);
|
||
this.channel4.update(clockElapsed);
|
||
};
|
||
APU.prototype.setSoundFlag = function(channel, value) {
|
||
var mask = 0xFF - (1 << (channel - 1));
|
||
value = value << (channel - 1)
|
||
var byteValue = this.memory.rb(APU.registers.NR52);
|
||
byteValue &= mask;
|
||
byteValue |= value;
|
||
this.memory[APU.registers.NR52] = byteValue;
|
||
};
|
||
// Manage writes to audio registers
|
||
// Will update the channels depending on the address
|
||
APU.prototype.manageWrite = function(addr, value) {
|
||
if (this.enabled == false && addr < APU.registers.NR52) {
|
||
return;
|
||
}
|
||
this.memory[addr] = value;
|
||
|
||
switch (addr) {
|
||
// Channel 1 addresses
|
||
case 0xFF10:
|
||
this.channel1.clockSweep = 0;
|
||
this.channel1.sweepTime = ((value & 0x70) >> 4);
|
||
this.channel1.sweepSign = (value & 0x08) ? -1 : 1;
|
||
this.channel1.sweepShifts = (value & 0x07);
|
||
this.channel1.sweepCount = this.channel1.sweepShifts;
|
||
break;
|
||
case 0xFF11:
|
||
// todo : bits 6-7
|
||
this.channel1.setLength(value & 0x3F);
|
||
break;
|
||
case 0xFF12:
|
||
this.channel1.envelopeSign = (value & 0x08) ? 1 : -1;
|
||
var envelopeVolume = (value & 0xF0) >> 4;
|
||
this.channel1.setEnvelopeVolume(envelopeVolume);
|
||
this.channel1.envelopeStep = (value & 0x07);
|
||
break;
|
||
case 0xFF13:
|
||
var frequency = this.channel1.getFrequency();
|
||
frequency &= 0xF00;
|
||
frequency |= value;
|
||
this.channel1.setFrequency(frequency);
|
||
break;
|
||
case 0xFF14:
|
||
var frequency = this.channel1.getFrequency();
|
||
frequency &= 0xFF;
|
||
frequency |= (value & 7) << 8;
|
||
this.channel1.setFrequency(frequency);
|
||
this.channel1.lengthCheck = (value & 0x40) ? true : false;
|
||
if (value & 0x80) this.channel1.play();
|
||
break;
|
||
|
||
// Channel 2 addresses
|
||
case 0xFF16:
|
||
// todo : bits 6-7
|
||
this.channel2.setLength(value & 0x3F);
|
||
break;
|
||
case 0xFF17:
|
||
this.channel2.envelopeSign = (value & 0x08) ? 1 : -1;
|
||
var envelopeVolume = (value & 0xF0) >> 4;
|
||
this.channel2.setEnvelopeVolume(envelopeVolume);
|
||
this.channel2.envelopeStep = (value & 0x07);
|
||
break;
|
||
case 0xFF18:
|
||
var frequency = this.channel2.getFrequency();
|
||
frequency &= 0xF00;
|
||
frequency |= value;
|
||
this.channel2.setFrequency(frequency);
|
||
break;
|
||
case 0xFF19:
|
||
var frequency = this.channel2.getFrequency();
|
||
frequency &= 0xFF;
|
||
frequency |= (value & 7) << 8;
|
||
this.channel2.setFrequency(frequency);
|
||
this.channel2.lengthCheck = (value & 0x40) ? true : false;
|
||
if (value & 0x80) {
|
||
this.channel2.play();
|
||
}
|
||
break;
|
||
|
||
// Channel 3 addresses
|
||
case 0xFF1A:
|
||
// todo
|
||
break;
|
||
case 0xFF1B:
|
||
this.channel3.setLength(value);
|
||
break;
|
||
case 0xFF1C:
|
||
// todo
|
||
break;
|
||
case 0xFF1D:
|
||
var frequency = this.channel3.getFrequency();
|
||
frequency &= 0xF00;
|
||
frequency |= value;
|
||
this.channel3.setFrequency(frequency);
|
||
break;
|
||
case 0xFF1E:
|
||
var frequency = this.channel3.getFrequency();
|
||
frequency &= 0xFF;
|
||
frequency |= (value & 7) << 8;
|
||
this.channel3.setFrequency(frequency);
|
||
this.channel3.lengthCheck = (value & 0x40) ? true : false;
|
||
if (value & 0x80) {
|
||
this.channel3.play();
|
||
}
|
||
break;
|
||
|
||
// Channel 4 addresses
|
||
case 0xFF20:
|
||
this.channel4.setLength(value & 0x3F);
|
||
break;
|
||
case 0xFF21:
|
||
// todo
|
||
break;
|
||
case 0xFF22:
|
||
// todo
|
||
break;
|
||
case 0xFF23:
|
||
this.channel4.lengthCheck = (value & 0x40) ? true : false;
|
||
if (value & 0x80) {
|
||
this.channel4.play();
|
||
}
|
||
break;
|
||
|
||
// channel 3 wave bytes
|
||
case 0xFF30:case 0xFF31:case 0xFF32:case 0xFF33:case 0xFF34:case 0xFF35:case 0xFF36:case 0xFF37:
|
||
case 0xFF38:case 0xFF39:case 0xFF3A:case 0xFF3B:case 0xFF3C:case 0xFF3D:case 0xFF3E:case 0xFF3F:
|
||
var index = addr - 0xFF30;
|
||
this.channel3.setWaveBufferByte(index, value);
|
||
break;
|
||
|
||
// general audio switch
|
||
case 0xFF26:
|
||
value &= 0xF0;
|
||
this.memory[addr] = value;
|
||
this.enabled = (value & 0x80) == 0 ? false : true;
|
||
if (!this.enabled) {
|
||
for (var i = 0xFF10; i < 0xFF27; i++)
|
||
this.memory[i] = 0;
|
||
// todo stop sound
|
||
}
|
||
break;
|
||
}
|
||
};
|
||
|
||
APU.registers = {
|
||
NR10: 0xFF10,
|
||
NR11: 0xFF11,
|
||
NR12: 0xFF12,
|
||
NR13: 0xFF13,
|
||
NR14: 0xFF14,
|
||
|
||
NR21: 0xFF16,
|
||
NR22: 0xFF17,
|
||
NR23: 0xFF18,
|
||
NR24: 0xFF19,
|
||
|
||
NR30: 0xFF1A,
|
||
NR31: 0xFF1B,
|
||
NR32: 0xFF1C,
|
||
NR33: 0xFF1D,
|
||
NR34: 0xFF1E,
|
||
|
||
NR41: 0xFF20,
|
||
NR42: 0xFF21,
|
||
NR43: 0xFF22,
|
||
NR44: 0xFF23,
|
||
|
||
NR50: 0xFF24,
|
||
NR51: 0xFF25,
|
||
NR52: 0xFF26
|
||
};
|
||
GameboyJS.APU = APU;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
var Channel1 = function(apu, channelNumber, audioContext) {
|
||
this.apu = apu;
|
||
this.channelNumber = channelNumber;
|
||
this.playing = false;
|
||
|
||
this.soundLengthUnit = 0x4000; // 1 / 256 second of instructions
|
||
this.soundLength = 64; // defaults to 64 periods
|
||
this.lengthCheck = false;
|
||
|
||
this.sweepTime = 0; // from 0 to 7
|
||
this.sweepStepLength = 0x8000; // 1 / 128 seconds of instructions
|
||
this.sweepCount = 0;
|
||
this.sweepShifts = 0;
|
||
this.sweepSign = 1; // +1 / -1 for increase / decrease freq
|
||
|
||
this.frequency = 0;
|
||
|
||
this.envelopeStep = 0;
|
||
this.envelopeStepLength = 0x10000;// 1 / 64 seconds of instructions
|
||
this.envelopeCheck = false;
|
||
this.envelopeSign = 1;
|
||
|
||
this.clockLength = 0;
|
||
this.clockEnvelop = 0;
|
||
this.clockSweep = 0;
|
||
|
||
var gainNode = audioContext.createGain();
|
||
gainNode.gain.value = 0;
|
||
var oscillator = audioContext.createOscillator();
|
||
oscillator.type = 'square';
|
||
oscillator.frequency.value = 1000;
|
||
oscillator.connect(gainNode);
|
||
oscillator.start(0);
|
||
|
||
this.audioContext = audioContext;
|
||
this.gainNode = gainNode;
|
||
this.oscillator = oscillator;
|
||
};
|
||
|
||
Channel1.prototype.play = function() {
|
||
if (this.playing) return;
|
||
this.playing = true;
|
||
this.apu.setSoundFlag(this.channelNumber, 1);
|
||
this.gainNode.connect(this.audioContext.destination);
|
||
this.clockLength = 0;
|
||
this.clockEnvelop = 0;
|
||
this.clockSweep = 0;
|
||
if (this.sweepShifts > 0) this.checkFreqSweep();
|
||
};
|
||
Channel1.prototype.stop = function() {
|
||
this.playing = false;
|
||
this.apu.setSoundFlag(this.channelNumber, 0);
|
||
this.gainNode.disconnect();
|
||
};
|
||
Channel1.prototype.checkFreqSweep = function() {
|
||
var oldFreq = this.getFrequency();
|
||
var newFreq = oldFreq + this.sweepSign * (oldFreq >> this.sweepShifts);
|
||
if (newFreq > 0x7FF) {
|
||
newFreq = 0;
|
||
this.stop();
|
||
}
|
||
|
||
return newFreq;
|
||
};
|
||
Channel1.prototype.update = function(clockElapsed) {
|
||
this.clockEnvelop += clockElapsed;
|
||
this.clockSweep += clockElapsed;
|
||
|
||
if ((this.sweepCount || this.sweepTime) && this.clockSweep > (this.sweepStepLength * this.sweepTime)) {
|
||
this.clockSweep -= (this.sweepStepLength * this.sweepTime);
|
||
this.sweepCount--;
|
||
|
||
var newFreq = this.checkFreqSweep(); // process and check new freq
|
||
|
||
this.apu.memory[0xFF13] = newFreq & 0xFF;
|
||
this.apu.memory[0xFF14] &= 0xF8;
|
||
this.apu.memory[0xFF14] |= (newFreq & 0x700) >> 8;
|
||
this.setFrequency(newFreq);
|
||
|
||
this.checkFreqSweep(); // check again with new value
|
||
}
|
||
|
||
if (this.envelopeCheck && this.clockEnvelop > this.envelopeStepLength) {
|
||
this.clockEnvelop -= this.envelopeStepLength;
|
||
this.envelopeStep--;
|
||
this.setEnvelopeVolume(this.envelopeVolume + this.envelopeSign);
|
||
if (this.envelopeStep <= 0) {
|
||
this.envelopeCheck = false;
|
||
}
|
||
}
|
||
|
||
if (this.lengthCheck) {
|
||
this.clockLength += clockElapsed;
|
||
if (this.clockLength > this.soundLengthUnit) {
|
||
this.soundLength--;
|
||
this.clockLength -= this.soundLengthUnit;
|
||
if (this.soundLength == 0) {
|
||
this.setLength(0);
|
||
this.stop();
|
||
}
|
||
}
|
||
}
|
||
};
|
||
Channel1.prototype.setFrequency = function(value) {
|
||
this.frequency = value;
|
||
this.oscillator.frequency.value = 131072 / (2048 - this.frequency);
|
||
};
|
||
Channel1.prototype.getFrequency = function() {
|
||
return this.frequency;
|
||
};
|
||
Channel1.prototype.setLength = function(value) {
|
||
this.soundLength = 64 - (value & 0x3F);
|
||
};
|
||
Channel1.prototype.setEnvelopeVolume = function(volume) {
|
||
this.envelopeCheck = volume > 0 && volume < 16 ? true : false;
|
||
this.envelopeVolume = volume;
|
||
this.gainNode.gain.value = this.envelopeVolume * 1/100;
|
||
};
|
||
Channel1.prototype.disable = function() {
|
||
this.oscillator.disconnect();
|
||
};
|
||
Channel1.prototype.enable = function() {
|
||
this.oscillator.connect(this.gainNode);
|
||
};
|
||
GameboyJS.Channel1 = Channel1;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
var Channel3 = function(apu, channelNumber, audioContext) {
|
||
this.apu = apu;
|
||
this.channelNumber = channelNumber;
|
||
this.playing = false;
|
||
|
||
this.soundLength = 0;
|
||
this.soundLengthUnit = 0x4000; // 1 / 256 second of instructions
|
||
this.lengthCheck = false;
|
||
|
||
this.clockLength = 0;
|
||
|
||
this.buffer = new Float32Array(32);
|
||
|
||
var gainNode = audioContext.createGain();
|
||
gainNode.gain.value = 1;
|
||
this.gainNode = gainNode;
|
||
|
||
this.baseSpeed = 65536;
|
||
var waveBuffer = audioContext.createBuffer(1, 32, this.baseSpeed);
|
||
|
||
var bufferSource = audioContext.createBufferSource();
|
||
bufferSource.buffer = waveBuffer;
|
||
bufferSource.loop = true;
|
||
bufferSource.connect(gainNode);
|
||
bufferSource.start(0);
|
||
|
||
this.audioContext = audioContext;
|
||
this.waveBuffer = waveBuffer;
|
||
this.bufferSource = bufferSource;
|
||
|
||
};
|
||
Channel3.prototype.play = function() {
|
||
if (this.playing) return;
|
||
this.playing = true;
|
||
this.apu.setSoundFlag(this.channelNumber, 1);
|
||
this.waveBuffer.copyToChannel(this.buffer, 0, 0);
|
||
|
||
this.gainNode.connect(this.audioContext.destination);
|
||
this.clockLength = 0;
|
||
};
|
||
Channel3.prototype.stop = function() {
|
||
this.playing = false;
|
||
this.apu.setSoundFlag(this.channelNumber, 0);
|
||
this.gainNode.disconnect();
|
||
};
|
||
Channel3.prototype.update = function(clockElapsed) {
|
||
if (this.lengthCheck){
|
||
this.clockLength += clockElapsed;
|
||
if (this.clockLength > this.soundLengthUnit) {
|
||
this.soundLength--;
|
||
this.clockLength -= this.soundLengthUnit;
|
||
if (this.soundLength == 0) {
|
||
this.setLength(0);
|
||
this.stop();
|
||
}
|
||
}
|
||
}
|
||
};
|
||
Channel3.prototype.setFrequency = function(value) {
|
||
value = 65536 / (2048 - value);
|
||
this.bufferSource.playbackRate.value = value / this.baseSpeed;
|
||
};
|
||
Channel3.prototype.getFrequency = function() {
|
||
var freq = 2048 - 65536 / (this.bufferSource.playbackRate.value * this.baseSpeed);
|
||
return freq | 1;
|
||
};
|
||
Channel3.prototype.setLength = function(value) {
|
||
this.soundLength = 256 - value;
|
||
};
|
||
Channel3.prototype.setWaveBufferByte = function(index, value) {
|
||
var bufferIndex = index * 2;
|
||
|
||
this.buffer[bufferIndex] = (value >> 4) / 8 - 1; // value in buffer is in -1 -> 1
|
||
this.buffer[bufferIndex+1] = (value & 0x0F) / 8 - 1;
|
||
};
|
||
Channel3.prototype.disable = function() {
|
||
this.bufferSource.disconnect();
|
||
};
|
||
Channel3.prototype.enable = function() {
|
||
this.bufferSource.connect(this.gainNode);
|
||
};
|
||
GameboyJS.Channel3 = Channel3;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
var Channel4 = function(apu, channelNumber, audioContext) {
|
||
this.apu = apu;
|
||
this.channelNumber = channelNumber;
|
||
this.playing = false;
|
||
|
||
this.soundLengthUnit = 0x4000; // 1 / 256 second of instructions
|
||
this.soundLength = 64; // defaults to 64 periods
|
||
this.lengthCheck = false;
|
||
|
||
this.clockLength = 0;
|
||
|
||
this.audioContext = audioContext;
|
||
};
|
||
|
||
Channel4.prototype.play = function() {
|
||
if (this.playing) return;
|
||
this.playing = true;
|
||
this.apu.setSoundFlag(this.channelNumber, 1);
|
||
this.clockLength = 0;
|
||
};
|
||
Channel4.prototype.stop = function() {
|
||
this.playing = false;
|
||
this.apu.setSoundFlag(this.channelNumber, 0);
|
||
};
|
||
Channel4.prototype.update = function(clockElapsed) {
|
||
if (this.lengthCheck) {
|
||
this.clockLength += clockElapsed;
|
||
if (this.clockLength > this.soundLengthUnit) {
|
||
this.soundLength--;
|
||
this.clockLength -= this.soundLengthUnit;
|
||
if (this.soundLength == 0) {
|
||
this.setLength(0);
|
||
this.stop();
|
||
}
|
||
}
|
||
}
|
||
};
|
||
Channel4.prototype.setLength = function(value) {
|
||
this.soundLength = 64 - (value & 0x3F);
|
||
};
|
||
GameboyJS.Channel4 = Channel4;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
var Timer = function(cpu, memory) {
|
||
this.cpu = cpu;
|
||
this.memory = memory;
|
||
|
||
this.DIV = 0xFF04;
|
||
this.TIMA = 0xFF05;
|
||
this.TMA = 0xFF06;
|
||
this.TAC = 0xFF07;
|
||
|
||
this.mainTime = 0;
|
||
this.divTime = 0;
|
||
};
|
||
|
||
Timer.prototype.update = function(clockElapsed) {
|
||
this.updateDiv(clockElapsed);
|
||
this.updateTimer(clockElapsed);
|
||
};
|
||
|
||
Timer.prototype.updateTimer = function(clockElapsed) {
|
||
if (!(this.memory.rb(this.TAC) & 0x4)) {
|
||
return;
|
||
}
|
||
this.mainTime += clockElapsed;
|
||
|
||
var threshold = 64;
|
||
switch (this.memory.rb(this.TAC) & 3) {
|
||
case 0: threshold=64; break; // 4KHz
|
||
case 1: threshold=1; break; // 256KHz
|
||
case 2: threshold=4; break; // 64KHz
|
||
case 3: threshold=16; break; // 16KHz
|
||
}
|
||
threshold *= 16;
|
||
|
||
while (this.mainTime >= threshold) {
|
||
this.mainTime -= threshold;
|
||
|
||
this.memory.wb(this.TIMA, this.memory.rb(this.TIMA) + 1);
|
||
if (this.memory.rb(this.TIMA) > 0xFF) {
|
||
this.memory.wb(this.TIMA, this.memory.rb(this.TMA));
|
||
this.cpu.requestInterrupt(GameboyJS.CPU.INTERRUPTS.TIMER);
|
||
}
|
||
}
|
||
};
|
||
// Update the DIV register internal clock
|
||
// Increment it if the clock threshold is elapsed and
|
||
// reset it if its value overflows
|
||
Timer.prototype.updateDiv = function(clockElapsed) {
|
||
var divThreshold = 256; // DIV is 16KHz
|
||
this.divTime += clockElapsed;
|
||
if (this.divTime > divThreshold) {
|
||
this.divTime -= divThreshold;
|
||
var div = this.memory.rb(this.DIV) + 1;
|
||
this.memory.wb(this.DIV, div&0xFF);
|
||
}
|
||
};
|
||
|
||
Timer.prototype.resetDiv = function() {
|
||
this.divTime = 0;
|
||
this.memory[this.DIV] = 0; // direct write to avoid looping
|
||
};
|
||
GameboyJS.Timer = Timer;
|
||
}(GameboyJS || (GameboyJS = {})));
|
||
|
||
var GameboyJS;
|
||
(function (GameboyJS) {
|
||
"use strict";
|
||
|
||
// Utility functions
|
||
var Util = {
|
||
// Add to the first argument the properties of all other arguments
|
||
extend: function(target /*, source1, source2, etc. */) {
|
||
var sources = Array.prototype.slice.call(arguments);
|
||
for (var i in sources) {
|
||
var source = sources[i];
|
||
for (var name in source) {
|
||
target[name] = source[name];
|
||
}
|
||
}
|
||
|
||
return target;
|
||
},
|
||
testFlag: function(p, cc) {
|
||
var test=1;
|
||
var mask=0x10;
|
||
if (cc=='NZ'||cc=='NC') test=0;
|
||
if (cc=='NZ'||cc=='Z') mask=0x80;
|
||
return (test && p.r.F&mask) || (!test && !(p.r.F&mask));
|
||
},
|
||
getRegAddr: function(p, r1, r2) {return Util.makeword(p.r[r1], p.r[r2]);},
|
||
|
||
// make a 16 bits word from 2 bytes
|
||
makeword: function(b1, b2) {return (b1 << 8) + b2;},
|
||
|
||
// return the integer signed value of a given byte
|
||
getSignedValue: function(v) {return v & 0x80 ? v-256 : v;},
|
||
|
||
// extract a bit from a byte
|
||
readBit: function(byte, index) {
|
||
return (byte >> index) & 1;
|
||
}
|
||
};
|
||
|
||
GameboyJS.Util = Util;
|
||
}(GameboyJS || (GameboyJS = {})));
|