2022-11-15 10:07:41 +01:00

398 lines
14 KiB
JavaScript

function Debugger(nes, ctx) {
this.nes = nes;
this.ctx = ctx;
this.breakpoints = [];
this.bpFired = false;
this.frames = 0;
this.updateLine = 120;
this.updateAnd = 3;
this.selectedView = 0;
this.ramScroll = 0;
this.disScroll = 0;
this.nes.onread = (a, v) => this.onread(a, v);
this.nes.onwrite = (a, v) => this.onwrite(a, v);
this.ramCdl = new Uint8Array(0x8000); // addresses $0-$7fff
this.romCdl = undefined; // gets created by loadrom, addresses $8000-$ffff
// load rom, do reset, set up cdl
this.loadRom = function(rom) {
if(this.nes.loadRom(rom)) {
this.nes.reset(true);
// reset breakpoints
this.breakpoints = [];
this.updateBreakpointList();
// clear ram cdl
for(let i = 0; i < 0x8000; i++) {
this.ramCdl[i] = 0;
}
// set up rom cdl
let prgSize = this.nes.mapper.h.banks * 0x4000;
this.romCdl = new Uint8Array(prgSize);
for(let i = 0; i < prgSize; i++) {
this.romCdl[i] = 0;
}
return true;
} else {
return false;
}
}
this.cycle = function() {
this.nes.cycle();
if(this.nes.cpu.cyclesLeft === 0 && !this.nes.inDma && (this.nes.cycles % 3) === 0) {
// we are about to execute a new instruction, mark in CDL and handle execute-breakpoints
let adr = this.nes.cpu.br[0];
for(let breakpoint of this.breakpoints) {
if(breakpoint.adr === adr && breakpoint.t === 2) {
log(`Hit breakpoint: executed at ${this.nes.getWordRep(adr)}`);
this.bpFired = true;
}
}
// adr < $8000: set ram cdl, else set rom cdl
if(adr < 0x8000) {
this.ramCdl[adr] = 1;
} else {
// get the address in rom that is mapped here
let prgAdr = this.nes.mapper.getRomAdr(adr);
this.romCdl[prgAdr] = 1;
}
}
let b = this.bpFired;
this.bpFired = false;
return b;
}
// runs a single instruction
this.runInstruction = function() {
do {
this.cycle();
} while(!(this.nes.cpu.cyclesLeft === 0 && !this.nes.inDma && (this.nes.cycles % 3) === 0));
this.updateDebugView();
// log(`${this.nes.getWordRep(this.nes.cpu.br[0])}: ${this.instrStr(this.nes.cpu.br[0])}`);
}
// returns false if we ran the whole frame, true if we broke at a breakpoint
// in which case the emulator should pause itself
this.runFrame = function() {
this.frames++;
let b;
do {
b = this.cycle();
if(b) {
// breakpoint hit
this.updateDebugView();
return true;
}
if(this.nes.ppu.line === this.updateLine && this.nes.ppu.dot === 0 && (this.frames & this.updateAnd) === 0) {
this.updateDebugView();
}
} while(!(this.nes.ppu.dot === 0 && this.nes.ppu.line === 240));
return false;
}
this.addBreakpoint = function(adr, type) {
this.breakpoints.push({adr: adr, t: type});
this.updateBreakpointList();
}
this.setView = function(view) {
this.selectedView = view;
if(view === 2 || view === 3) {
// ramview, disassembly
el("dtextoutput").style.display = "block";
el("doutput").style.display = "none";
} else {
el("dtextoutput").style.display = "none";
el("doutput").style.display = "block";
}
if(view === 3) {
// set scroll to be around PC
this.disScroll = this.nes.cpu.br[0] - 16;
}
this.updateDebugView();
}
this.changeScrollPos = function(add) {
if(this.selectedView === 2) {
let old = this.ramScroll;
let r = this.ramScroll + add;
r = r < 0 ? 0 : r;
r = r > 0xfe0 ? 0xfe0 : r;
this.ramScroll = r;
if(r !== old) {
this.updateDebugView();
}
} else {
let old = this.disScroll;
let r = this.disScroll + add;
r = r < 0 ? 0 : r;
this.disScroll = r;
if(r !== old) {
this.updateDebugView();
}
}
}
this.onread = function(adr, val) {
for(let breakpoint of this.breakpoints) {
if(breakpoint.adr === adr && breakpoint.t === 0) {
log(`Hit breakpoint: read ${this.nes.getByteRep(val)} at ${this.nes.getWordRep(adr)}`);
this.bpFired = true;
}
}
}
this.onwrite = function(adr, val) {
for(let breakpoint of this.breakpoints) {
if(breakpoint.adr === adr && breakpoint.t === 1) {
log(`Hit breakpoint: wrote ${this.nes.getByteRep(val)} to ${this.nes.getWordRep(adr)}`);
this.bpFired = true;
}
}
// a write to a sub $8000 address invalidates any cdl there
if(adr < 0x8000) {
this.ramCdl[adr] = 0;
}
}
this.updateBreakpointList = function() {
// clear list
let bl = el("breakpoints");
while(bl.firstChild) {
bl.removeChild(bl.firstChild);
}
let i = 0;
for(let bp of this.breakpoints) {
let index = i;
i++;
let str = "read ";
if(bp.t === 1) {
str = "write ";
} else if(bp.t === 2) {
str = "execute ";
}
tNode = document.createTextNode(`${str}: ${this.nes.getWordRep(bp.adr)}\n`);
bl.appendChild(tNode);
let button = document.createElement("button");
button.textContent = "Remove";
button.onclick = () => {
this.breakpoints.splice(index, 1);
this.updateBreakpointList();
};
bl.appendChild(button);
bl.appendChild(document.createElement("br"));
}
}
this.updateDebugView = function() {
if(this.selectedView === 0) {
this.drawPatternsPals();
} else if(this.selectedView === 1) {
this.drawNametables();
} else if(this.selectedView === 2) {
this.drawRam();
} else {
this.drawDissasembly();
}
// update cpu/ppu state
let flagStr = `${this.nes.cpu.n ? "N" : "n"}${this.nes.cpu.v ? "V" : "v"}--${this.nes.cpu.d ? "D" : "d"}${this.nes.cpu.i ? "I" : "i"}${this.nes.cpu.z ? "Z" : "z"}${this.nes.cpu.c ? "C" : "c"}`;
let cpuStr = `A: ${this.nes.getByteRep(this.nes.cpu.r[0])}; X: ${this.nes.getByteRep(this.nes.cpu.r[1])}, Y: ${this.nes.getByteRep(this.nes.cpu.r[2])}, PC: ${this.nes.getWordRep(this.nes.cpu.br[0])}, SP: ${this.nes.getByteRep(this.nes.cpu.r[3])}, ${flagStr}`;
el("cpustate").textContent = cpuStr;
let line = ("00" + this.nes.ppu.line).slice(-3);
let dot = ("00" + this.nes.ppu.dot).slice(-3);
let ppuStr = `line: ${line}, dot: ${dot}, v: ${this.nes.getWordRep(this.nes.ppu.v)}, t: ${this.nes.getWordRep(this.nes.ppu.t)}, x: ${this.nes.ppu.x}, w: ${this.nes.ppu.w}`;
el("ppustate").textContent = ppuStr;
}
this.drawRam = function() {
let ev = el("dtextoutput");
ev.textContent = "";
let ramBasePos = this.ramScroll;
for(let r = ramBasePos; r < ramBasePos + 0x20; r++) {
let str = `${this.nes.getWordRep(r * 16)}: `;
for(let c = 0; c < 16; c++) {
str += `${this.nes.getByteRep(this.nes.peak(r * 16 + c))} `;
}
ev.textContent += str + "\n";
}
}
this.drawDissasembly = function() {
let ev = el("dtextoutput");
ev.textContent = "";
let adr = this.disScroll;
let lines = 0;
let firstData = true;
while(adr < 0x10000) {
let op = this.nes.peak(adr);
let length = this.opLengths[this.nes.cpu.addressingModes[op]];
let isOpcode;
if(adr < 0x8000) {
isOpcode = this.ramCdl[adr];
} else {
let prgAdr = this.nes.mapper.getRomAdr(adr);
isOpcode = this.romCdl[prgAdr];
}
let pcP = adr === this.nes.cpu.br[0] ? ">" : " ";
if(isOpcode) {
ev.textContent += `${pcP} ${this.nes.getWordRep(adr)}: ${this.instrStr(adr)}\n`;
adr += length;
firstData = true;
lines++;
} else {
// ev.textContent += `${pcP} ${this.nes.getWordRep(adr)}: .db $${this.nes.getByteRep(op)}\n`;
// adr++;
if(firstData) {
ev.textContent += ` ${this.nes.getWordRep(adr)}: -- UNIDENTIFIED BLOCK --\n`;
firstData = false;
lines++;
}
adr++;
}
if(lines === 32) {
break;
}
}
for(let i = lines; i < 32; i++) {
ev.textContent += "\n";
}
}
this.drawNametables = function() {
let imgData = this.ctx.createImageData(512, 480);
for(let x = 0; x < 64; x++) {
for(let y = 0; y < 60; y++) {
ry = y + (y >= 30 ? 2 : 0);
let tileNumAdr = 0x2000 + (ry > 31 ? 0x800 : 0) + (x > 31 ? 0x400 : 0);
tileNumAdr += ((ry & 0x1f) << 5) + (x & 0x1f);
let tileNum = this.nes.mapper.ppuPeak(tileNumAdr);
let attAdr = 0x23c0 + (ry > 31 ? 0x800 : 0) + (x > 31 ? 0x400 : 0);
attAdr += ((ry & 0x1c) << 1) + ((x & 0x1c) >> 2);
let atr = this.nes.mapper.ppuPeak(attAdr);
if((ry & 0x2) > 0) {
// bottom half
atr >>= 4;
}
atr &= 0xf;
if((x & 0x2) > 0) {
// right half
atr >>= 2;
}
atr &= 0x3;
this.drawTile(imgData, x * 8, y * 8, tileNum + (this.nes.ppu.bgPatternBase === 0 ? 0 : 256), atr);
}
}
this.ctx.putImageData(imgData, 0, 0);
}
this.drawPatternsPals = function() {
let imgData = this.ctx.createImageData(512, 480);
// left table
for(let x = 0; x < 16; x++) {
for(let y = 0; y < 16; y++) {
this.drawTile(imgData, x * 8, y * 8, y * 16 + x, 0);
}
}
// right table
for(let x = 0; x < 16; x++) {
for(let y = 0; y < 16; y++) {
this.drawTile(imgData, 128 + (x * 8), y * 8, 256 + (y * 16 + x), 0);
}
}
this.ctx.putImageData(imgData, 0, 0);
// draw palette
for(let i = 0; i < 16; i++) {
let col = this.nes.ppu.nesPal[this.nes.ppu.readPalette(i) & 0x3f];
ctx.fillStyle = `rgba(${col[0]}, ${col[1]}, ${col[2]}, 1)`;
ctx.fillRect(i * 16, 128, 16, 16);
col = this.nes.ppu.nesPal[this.nes.ppu.readPalette(i + 16) & 0x3f];
ctx.fillStyle = `rgba(${col[0]}, ${col[1]}, ${col[2]}, 1)`;
ctx.fillRect(i * 16, 144, 16, 16);
}
}
this.drawTile = function(imgData, x, y, num, col) {
for(let i = 0; i < 8; i++) {
// for each row
let lp = this.nes.mapper.ppuPeak(num * 16 + i);
let hp = this.nes.mapper.ppuPeak(num * 16 + i + 8);
for(let j = 0; j < 8; j++) {
// for each pixel of the row
// extract the pixel
let shift = 7 - j;
let pixel = (lp >> shift) & 1;
pixel |= ((hp >> shift) & 1) << 1;
// get the palette index
let pind = pixel === 0 ? 0 : col * 4 + pixel;
let color = this.nes.ppu.nesPal[this.nes.ppu.readPalette(pind) & 0x3f];
// put in in the imgData
let index = ((y + i) * imgData.width + (x + j)) * 4;
imgData.data[index] = color[0]; // r
imgData.data[index + 1] = color[1]; // g
imgData.data[index + 2] = color[2]; // b
imgData.data[index + 3] = 255; // a
}
}
}
this.instrStr = function(adr) {
let pc = adr;
let opcode = this.nes.peak(pc);
let i1 = this.nes.peak((pc + 1) & 0xffff);
let i2 = i1 | (this.nes.peak((pc + 2) & 0xffff) << 8);
let adrMode = this.nes.cpu.addressingModes[opcode];
let opName = this.opNames[opcode];
let relVal = i1 > 0x7f ? i1 - 0x100 : i1;
relVal += pc + 2;
switch(adrMode) {
case 0: return `${opName}`;
case 1: return `${opName} #$${this.nes.getByteRep(i1)}`;
case 2: return `${opName} $${this.nes.getByteRep(i1)}`;
case 3: return `${opName} $${this.nes.getByteRep(i1)},x`;
case 4: return `${opName} $${this.nes.getByteRep(i1)},y`;
case 5: return `${opName} ($${this.nes.getByteRep(i1)},x)`;
case 6: return `${opName} ($${this.nes.getByteRep(i1)}),y`;
case 7: return `${opName} $${this.nes.getWordRep(i2)}`;
case 8: return `${opName} $${this.nes.getWordRep(i2)},x`;
case 9: return `${opName} $${this.nes.getWordRep(i2)},y`;
case 10: return `?`; // apparently this ended up being skipped?
case 11: return `${opName} ($${this.nes.getWordRep(i2)})`;
case 12: return `${opName} $${this.nes.getWordRep(relVal)}`;
case 13: return `${opName} ($${this.nes.getByteRep(i1)}),y`;
case 14: return `${opName} $${this.nes.getWordRep(i2)},x`;
case 15: return `${opName} $${this.nes.getWordRep(i2)},y`;
}
}
this.opLengths = [1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 0, 3, 2, 2, 3, 3];
this.opNames = [
"brk", "ora", "kil", "slo", "nop", "ora", "asl", "slo", "php", "ora", "asl", "anc", "nop", "ora", "asl", "slo", //0x
"bpl", "ora", "kil", "slo", "nop", "ora", "asl", "slo", "clc", "ora", "nop", "slo", "nop", "ora", "asl", "slo", //1x
"jsr", "and", "kil", "rla", "bit", "and", "rol", "rla", "plp", "and", "rol", "anc", "bit", "and", "rol", "rla", //2x
"bmi", "and", "kil", "rla", "nop", "and", "rol", "rla", "sec", "and", "nop", "rla", "nop", "and", "rol", "rla", //3x
"rti", "eor", "kil", "sre", "nop", "eor", "lsr", "sre", "pha", "eor", "lsr", "alr", "jmp", "eor", "lsr", "sre", //4x
"bvc", "eor", "kil", "sre", "nop", "eor", "lsr", "sre", "cli", "eor", "nop", "sre", "nop", "eor", "lsr", "sre", //5x
"rts", "adc", "kil", "rra", "nop", "adc", "ror", "rra", "pla", "adc", "ror", "arr", "jmp", "adc", "ror", "rra", //6x
"bvs", "adc", "kil", "rra", "nop", "adc", "ror", "rra", "sei", "adc", "nop", "rra", "nop", "adc", "ror", "rra", //7x
"nop", "sta", "nop", "sax", "sty", "sta", "stx", "sax", "dey", "nop", "txa", "uni", "sty", "sta", "stx", "sax", //8x
"bcc", "sta", "kil", "uni", "sty", "sta", "stx", "sax", "tya", "sta", "txs", "uni", "uni", "sta", "uni", "uni", //9x
"ldy", "lda", "ldx", "lax", "ldy", "lda", "ldx", "lax", "tay", "lda", "tax", "uni", "ldy", "lda", "ldx", "lax", //ax
"bcs", "lda", "kil", "lax", "ldy", "lda", "ldx", "lax", "clv", "lda", "tsx", "uni", "ldy", "lda", "ldx", "lax", //bx
"cpy", "cmp", "nop", "dcp", "cpy", "cmp", "dec", "dcp", "iny", "cmp", "dex", "axs", "cpy", "cmp", "dec", "dcp", //cx
"bne", "cmp", "kil", "dcp", "nop", "cmp", "dec", "dcp", "cld", "cmp", "nop", "dcp", "nop", "cmp", "dec", "dcp", //dx
"cpx", "sbc", "nop", "isc", "cpx", "sbc", "inc", "isc", "inx", "sbc", "nop", "sbc", "cpx", "sbc", "inc", "isc", //ex
"beq", "sbc", "kil", "isc", "nop", "sbc", "inc", "isc", "sed", "sbc", "nop", "isc", "nop", "sbc", "inc", "isc", //fx
];
}