From ea03fe25ad3170d24cdb3c1f18a3c6b05ffbfc9e Mon Sep 17 00:00:00 2001 From: "Satoshi N. M" Date: Fri, 10 Nov 2017 21:51:38 -0800 Subject: [PATCH 1/8] Refactor this.nes.mmap.load() into this.loadFromCartridge() --- src/cpu.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/cpu.js b/src/cpu.js index c5a012d9..63804aa2 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,15 @@ CPU.prototype = { return cycleCount; }, + loadFromCartridge: function(addr) { + return this.nes.mmap.load(addr); + }, + load: function(addr) { if (addr < 0x2000) { return this.mem[addr & 0x7ff]; } else { - return this.nes.mmap.load(addr); + return this.loadFromCartridge(addr); } }, @@ -1047,7 +1051,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); } }, @@ -1083,7 +1087,7 @@ CPU.prototype = { pull: function() { this.REG_SP++; this.REG_SP = 0x0100 | (this.REG_SP & 0xff); - return this.nes.mmap.load(this.REG_SP); + return this.loadFromCartridge(this.REG_SP); }, pageCrossed: function(addr1, addr2) { @@ -1095,7 +1099,7 @@ CPU.prototype = { }, doNonMaskableInterrupt: function(status) { - if ((this.nes.mmap.load(0x2000) & 128) !== 0) { + if ((this.loadFromCartridge(0x2000) & 128) !== 0) { // Check whether VBlank Interrupts are enabled this.REG_PC_NEW++; @@ -1105,14 +1109,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 +1129,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--; }, From e031dafbeab303d8d1bb37fbaad085b136dd4fd0 Mon Sep 17 00:00:00 2001 From: "Satoshi N. M" Date: Fri, 10 Nov 2017 21:53:21 -0800 Subject: [PATCH 2/8] Hardcode test for SMB infinite lives GG code (SXIOPO) --- src/cpu.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cpu.js b/src/cpu.js index 63804aa2..f2686274 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -1036,6 +1036,7 @@ CPU.prototype = { }, loadFromCartridge: function(addr) { + if (addr === (0x11d9 | 0x8000)) return 0xad; return this.nes.mmap.load(addr); }, From a11137aa4aadc3b3731fe3585f22470715ee2891 Mon Sep 17 00:00:00 2001 From: "Satoshi N. M" Date: Fri, 10 Nov 2017 22:18:51 -0800 Subject: [PATCH 3/8] Add Game Genie module with decoding/encoding support Based on https://github.com/satoshinm/nes-game-genie --- src/cpu.js | 9 ++++-- src/gg.js | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/nes.js | 3 ++ 3 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 src/gg.js diff --git a/src/cpu.js b/src/cpu.js index f2686274..9363ead3 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -1036,8 +1036,13 @@ CPU.prototype = { }, loadFromCartridge: function(addr) { - if (addr === (0x11d9 | 0x8000)) return 0xad; - return this.nes.mmap.load(addr); + var value = this.nes.mmap.load(addr); + + if (this.nes.gg.enabled) { + value = this.nes.gg.applyCodes(addr, value); + } + + return value; }, load: function(addr) { diff --git a/src/gg.js b/src/gg.js new file mode 100644 index 00000000..aa9f517f --- /dev/null +++ b/src/gg.js @@ -0,0 +1,90 @@ + +var GG = function(nes) { + this.nes = nes; + 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); +} + +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) { + var digits = code.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 }; + }, + + 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..26319c5a 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 GG = require("./gg"); var ROM = require("./rom"); var NES = function(opts) { @@ -34,6 +35,8 @@ var NES = function(opts) { this.cpu = new CPU(this); this.ppu = new PPU(this); this.papu = new PAPU(this); + this.gg = new GG(this); + this.gg.addCode('SXIOPO'); this.mmap = null; // set in loadROM() this.controllers = { 1: new Controller(), From 887230bdd52042a879d9c9c31450eb07cbec3926 Mon Sep 17 00:00:00 2001 From: "Satoshi N. M" Date: Fri, 10 Nov 2017 22:40:46 -0800 Subject: [PATCH 4/8] Remove sample code --- src/nes.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/nes.js b/src/nes.js index 26319c5a..7821285b 100644 --- a/src/nes.js +++ b/src/nes.js @@ -36,7 +36,6 @@ var NES = function(opts) { this.ppu = new PPU(this); this.papu = new PAPU(this); this.gg = new GG(this); - this.gg.addCode('SXIOPO'); this.mmap = null; // set in loadROM() this.controllers = { 1: new Controller(), From c18d859f4905c41111604cebc6ddf696b830b020 Mon Sep 17 00:00:00 2001 From: "Satoshi N. M" Date: Fri, 10 Nov 2017 22:46:45 -0800 Subject: [PATCH 5/8] GG can't modify SP or NMI, no need to bounce through loadFromCartridge() --- src/cpu.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cpu.js b/src/cpu.js index 9363ead3..5605d5e1 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -1093,7 +1093,7 @@ CPU.prototype = { pull: function() { this.REG_SP++; this.REG_SP = 0x0100 | (this.REG_SP & 0xff); - return this.loadFromCartridge(this.REG_SP); + return this.nes.mmap.load(this.REG_SP); }, pageCrossed: function(addr1, addr2) { @@ -1105,7 +1105,7 @@ CPU.prototype = { }, doNonMaskableInterrupt: function(status) { - if ((this.loadFromCartridge(0x2000) & 128) !== 0) { + if ((this.nes.mmap.load(0x2000) & 128) !== 0) { // Check whether VBlank Interrupts are enabled this.REG_PC_NEW++; From 5e9e307205a888a48923d6d7d56aa42ee5782696 Mon Sep 17 00:00:00 2001 From: "Satoshi N. M" Date: Sat, 18 Nov 2017 13:58:36 -0800 Subject: [PATCH 6/8] Add support for decoding hex codes (e.g. 11d9:ad) --- src/gg.js | 38 ++++++++++++++++++++++++++++++++++++-- src/nes.js | 2 ++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/gg.js b/src/gg.js index aa9f517f..83e308e7 100644 --- a/src/gg.js +++ b/src/gg.js @@ -15,6 +15,12 @@ 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; @@ -42,14 +48,16 @@ GG.prototype = { }, decode: function(code) { - var digits = code.split('').map(toDigit); + 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) { + if (digits.length === 8) { value += (digits[7] & 8); key = ((digits[6] & 8) << 4) + ((digits[7] & 7) << 4) + (digits[5] & 8) + (digits[6] & 7); } else { @@ -61,6 +69,32 @@ GG.prototype = { 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); diff --git a/src/nes.js b/src/nes.js index 7821285b..1e0306c1 100644 --- a/src/nes.js +++ b/src/nes.js @@ -36,6 +36,8 @@ var NES = function(opts) { this.ppu = new PPU(this); this.papu = new PAPU(this); this.gg = new GG(this); + //this.gg.addCode('11d9:ad'); // example of hex code + //this.gg.addCode('SXIOPO'); // example of Game Genie code this.mmap = null; // set in loadROM() this.controllers = { 1: new Controller(), From 62f451333ad8f8d2a8f87d2ffc0096fe474f9998 Mon Sep 17 00:00:00 2001 From: "Satoshi N. M" Date: Sat, 18 Nov 2017 13:58:47 -0800 Subject: [PATCH 7/8] Remove code examples --- src/nes.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/nes.js b/src/nes.js index 1e0306c1..7821285b 100644 --- a/src/nes.js +++ b/src/nes.js @@ -36,8 +36,6 @@ var NES = function(opts) { this.ppu = new PPU(this); this.papu = new PAPU(this); this.gg = new GG(this); - //this.gg.addCode('11d9:ad'); // example of hex code - //this.gg.addCode('SXIOPO'); // example of Game Genie code this.mmap = null; // set in loadROM() this.controllers = { 1: new Controller(), From 2f3eef3311826ea151f8cd1692e8171b10976fd3 Mon Sep 17 00:00:00 2001 From: "Satoshi N. M" Date: Tue, 28 Nov 2017 17:12:49 -0800 Subject: [PATCH 8/8] Expand gg to GameGenie --- src/cpu.js | 4 ++-- src/gg.js | 3 +-- src/nes.js | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/cpu.js b/src/cpu.js index 5605d5e1..db9519cc 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -1038,8 +1038,8 @@ CPU.prototype = { loadFromCartridge: function(addr) { var value = this.nes.mmap.load(addr); - if (this.nes.gg.enabled) { - value = this.nes.gg.applyCodes(addr, value); + if (this.nes.gameGenie.enabled) { + value = this.nes.gameGenie.applyCodes(addr, value); } return value; diff --git a/src/gg.js b/src/gg.js index 83e308e7..ef48b83e 100644 --- a/src/gg.js +++ b/src/gg.js @@ -1,6 +1,5 @@ -var GG = function(nes) { - this.nes = nes; +var GG = function() { this.patches = []; this.enabled = true; }; diff --git a/src/nes.js b/src/nes.js index 7821285b..5bdd763f 100644 --- a/src/nes.js +++ b/src/nes.js @@ -2,7 +2,7 @@ var CPU = require("./cpu"); var Controller = require("./controller"); var PPU = require("./ppu"); var PAPU = require("./papu"); -var GG = require("./gg"); +var GameGenie = require("./gg"); var ROM = require("./rom"); var NES = function(opts) { @@ -35,7 +35,7 @@ var NES = function(opts) { this.cpu = new CPU(this); this.ppu = new PPU(this); this.papu = new PAPU(this); - this.gg = new GG(this); + this.gameGenie = new GameGenie(); this.mmap = null; // set in loadROM() this.controllers = { 1: new Controller(),