diff --git a/src/cpu.js b/src/cpu.js index c5a012d9..db9519cc 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -143,7 +143,7 @@ CPU.prototype = { this.irqRequested = false; } - var opinf = this.opdata[this.nes.mmap.load(this.REG_PC + 1)]; + var opinf = this.opdata[this.loadFromCartridge(this.REG_PC + 1)]; var cycleCount = opinf >> 24; var cycleAdd = 0; @@ -264,8 +264,8 @@ CPU.prototype = { (this.mem[(addr & 0xff00) | (((addr & 0xff) + 1) & 0xff)] << 8); // Read from address given in op } else { addr = - this.nes.mmap.load(addr) + - (this.nes.mmap.load( + this.loadFromCartridge(addr) + + (this.loadFromCartridge( (addr & 0xff00) | (((addr & 0xff) + 1) & 0xff) ) << 8); @@ -1035,11 +1035,21 @@ CPU.prototype = { return cycleCount; }, + loadFromCartridge: function(addr) { + var value = this.nes.mmap.load(addr); + + if (this.nes.gameGenie.enabled) { + value = this.nes.gameGenie.applyCodes(addr, value); + } + + return value; + }, + load: function(addr) { if (addr < 0x2000) { return this.mem[addr & 0x7ff]; } else { - return this.nes.mmap.load(addr); + return this.loadFromCartridge(addr); } }, @@ -1047,7 +1057,7 @@ CPU.prototype = { if (addr < 0x1fff) { return this.mem[addr & 0x7ff] | (this.mem[(addr + 1) & 0x7ff] << 8); } else { - return this.nes.mmap.load(addr) | (this.nes.mmap.load(addr + 1) << 8); + return this.loadFromCartridge(addr) | (this.loadFromCartridge(addr + 1) << 8); } }, @@ -1105,14 +1115,14 @@ CPU.prototype = { this.push(status); this.REG_PC_NEW = - this.nes.mmap.load(0xfffa) | (this.nes.mmap.load(0xfffb) << 8); + this.loadFromCartridge(0xfffa) | (this.loadFromCartridge(0xfffb) << 8); this.REG_PC_NEW--; } }, doResetInterrupt: function() { this.REG_PC_NEW = - this.nes.mmap.load(0xfffc) | (this.nes.mmap.load(0xfffd) << 8); + this.loadFromCartridge(0xfffc) | (this.loadFromCartridge(0xfffd) << 8); this.REG_PC_NEW--; }, @@ -1125,7 +1135,7 @@ CPU.prototype = { this.F_BRK_NEW = 0; this.REG_PC_NEW = - this.nes.mmap.load(0xfffe) | (this.nes.mmap.load(0xffff) << 8); + this.loadFromCartridge(0xfffe) | (this.loadFromCartridge(0xffff) << 8); this.REG_PC_NEW--; }, diff --git a/src/gg.js b/src/gg.js new file mode 100644 index 00000000..ef48b83e --- /dev/null +++ b/src/gg.js @@ -0,0 +1,123 @@ + +var GG = function() { + this.patches = []; + this.enabled = true; +}; + +var LETTER_VALUES = 'APZLGITYEOXUKSVN'; + +function toDigit(letter) { + return LETTER_VALUES.indexOf(letter); +} + +function toLetter(digit) { + return LETTER_VALUES.substr(digit, 1); +} + +function toHex(n, width) { + var s = n.toString(16); + return '0000'.substring(0, width - s.length) + s; +} + + +GG.prototype = { + setEnabled: function(enabled) { + this.enabled = enabled; + }, + + addCode: function(code) { + this.patches.push(this.decode(code)); + }, + + addPatch: function(addr, value, key) { + this.patches.push({addr: addr, value: value, key: key}); + }, + + applyCodes: function(addr, value) { + if (!this.enabled) return value; + + for (var i = 0; i < this.patches.length; ++i) { // TODO: optimize data structure? + if (this.patches[i].addr === (addr & 0x7fff)) { + if (this.patches[i].key === undefined || this.patches[i].key === value) { + return this.patches[i].value; + } + } + } + return value; + }, + + decode: function(code) { + if (code.indexOf(':') !== -1) return this.decodeHex(code); + + var digits = code.toUpperCase().split('').map(toDigit); + + var value = ((digits[0] & 8) << 4) + ((digits[1] & 7) << 4) + (digits[0] & 7); + var addr = ((digits[3] & 7) << 12) + ((digits[4] & 8) << 8) + ((digits[5] & 7) << 8) + + ((digits[1] & 8) << 4) + ((digits[2] & 7) << 4) + (digits[3] & 8) + (digits[4] & 7); + var key; + + if (digits.length === 8) { + value += (digits[7] & 8); + key = ((digits[6] & 8) << 4) + ((digits[7] & 7) << 4) + (digits[5] & 8) + (digits[6] & 7); + } else { + value += (digits[5] & 8); + } + + var wantskey = !!(digits[2] >> 3); + + return { value: value, addr: addr, wantskey: wantskey, key: key }; + }, + + encodeHex: function(addr, value, key, wantskey) { + var s = toHex(addr, 4) + ':' + toHex(value, 2); + + if (key !== undefined || wantskey) { + s += '?'; + } + + if (key !== undefined) { + s += toHex(key, 2); + } + + return s; + }, + + decodeHex: function(s) { + var match = s.match(/([0-9a-fA-F]+):([0-9a-fA-F]+)(\?[0-9a-fA-F]*)?/); + if (!match) return null; + + var addr = parseInt(match[1], 16); + var value = parseInt(match[2], 16); + var wantskey = match[3] !== undefined; + var key = (match[3] !== undefined && match[3].length > 1) ? parseInt(match[3].substring(1), 16) : undefined; + + return { value: value, addr: addr, wantskey: wantskey, key: key }; + }, + + encode: function(addr, value, key, wantskey) { + var digits = Array(6); + + digits[0] = (value & 7) + ((value >> 4) & 8); + digits[1] = ((value >> 4) & 7) + ((addr >> 4) & 8); + digits[2] = ((addr >> 4) & 7); + digits[3] = (addr >> 12) + (addr & 8); + digits[4] = (addr & 7) + ((addr >> 8) & 8); + digits[5] = ((addr >> 8) & 7); + + if (key === undefined) { + digits[5] += value & 8; + if (wantskey) digits[2] += 8; + } else { + digits[2] += 8; + digits[5] += key & 8; + digits[6] = (key & 7) + ((key >> 4) & 8); + digits[7] = ((key >> 4) & 7) + (value & 8); + } + + var code = digits.map(toLetter).join(''); + + return code; + }, +}; + +module.exports = GG; diff --git a/src/nes.js b/src/nes.js index eedc6209..5bdd763f 100644 --- a/src/nes.js +++ b/src/nes.js @@ -2,6 +2,7 @@ var CPU = require("./cpu"); var Controller = require("./controller"); var PPU = require("./ppu"); var PAPU = require("./papu"); +var GameGenie = require("./gg"); var ROM = require("./rom"); var NES = function(opts) { @@ -34,6 +35,7 @@ var NES = function(opts) { this.cpu = new CPU(this); this.ppu = new PPU(this); this.papu = new PAPU(this); + this.gameGenie = new GameGenie(); this.mmap = null; // set in loadROM() this.controllers = { 1: new Controller(),