8bitworkshop/src/platform/nes.ts

532 lines
16 KiB
TypeScript
Raw Normal View History

2021-07-31 14:52:25 +00:00
import { Platform, Base6502Platform, getOpcodeMetadata_6502, cpuStateToLongString_6502, getToolForFilename_6502, dumpStackToString, BaseMAME6502Platform } from "../common/baseplatform";
import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, dumpRAM, KeyFlags, EmuHalt, ControllerPoller } from "../common/emu";
import { hex, lpad, lzgmini, byteArrayToString } from "../common/util";
import { CodeAnalyzer_nes } from "../common/analysis";
import { SampleAudio } from "../common/audio";
import { ProbeRecorder } from "../common/recorder";
import { NullProbe, Probeable, ProbeAll } from "../common/devices";
declare var jsnes : any;
declare var Mousetrap;
2018-08-23 12:49:14 +00:00
const JSNES_PRESETS = [
2019-04-19 14:00:01 +00:00
{id:'hello.c', name:'Hello World'},
2019-05-16 14:08:01 +00:00
{id:'attributes.c', name:'Attribute Table'},
2019-05-16 14:08:09 +00:00
{id:'scroll.c', name:'Scrolling'},
{id:'sprites.c', name:'Sprites'},
{id:'metasprites.c', name:'Metasprites'},
2019-02-22 17:27:09 +00:00
{id:'flicker.c', name:'Flickering Sprites'},
{id:'metacursor.c', name:'Controllers'},
2019-06-03 14:08:29 +00:00
{id:'vrambuffer.c', name:'VRAM Buffer'},
2019-02-26 15:56:51 +00:00
{id:'statusbar.c', name:'Split Status Bar'},
2019-07-04 19:22:42 +00:00
{id:'siegegame.c', name:'Siege Game'},
{id:'tint.c', name:'Color Emphasis'},
{id:'rletitle.c', name:'Title Screen RLE'},
2019-03-16 00:34:17 +00:00
{id:'aputest.c', name:'Sound Tester'},
{id:'music.c', name:'Music Player'},
2019-07-26 01:23:47 +00:00
{id:'horizscroll.c', name:'Offscreen Scrolling'},
2019-07-04 19:22:42 +00:00
{id:'monobitmap.c', name:'Monochrome Bitmap'},
2019-05-22 15:45:05 +00:00
{id:'fami.c', name:'Famitone Demo'},
{id:'shoot2.c', name:'Solarian Game'},
2019-07-29 02:57:16 +00:00
{id:'climber.c', name:'Climber Game'},
{id:'bankswitch.c', name:'Bank Switching'},
{id:'irq.c', name:'IRQ Scanline Counter'},
{id:'ex0.dasm', name:'Initialization (ASM)'},
{id:'ex1.dasm', name:'Hello World (ASM)'},
{id:'ex2.dasm', name:'Scrolling Demo (ASM)'},
{id:'ex3.dasm', name:'Sprite Demo (ASM)'},
{id:'ex4.dasm', name:'Controller Demo (ASM)'},
{id:'musicdemo.dasm', name:'Famitone Demo (ASM)'},
{id:'xyscroll.dasm', name:'XY Split Scrolling (ASM)'},
2019-05-13 22:36:25 +00:00
// {id:'scrollrt.dasm', name:'Line-by-line Scrolling (ASM)'},
{id:'road.dasm', name:'3-D Road Demo (ASM)'},
{id:'chase/game.c', name:'Shiru\'s Chase Game'},
2021-03-04 14:20:00 +00:00
{id:'hello.wiz', name:'Hello (Wiz)'},
];
/// JSNES
2018-08-23 12:49:14 +00:00
const JSNES_KEYCODE_MAP = makeKeycodeMap([
[Keys.A, 0, 0],
[Keys.B, 0, 1],
[Keys.SELECT, 0, 2],
[Keys.START, 0, 3],
[Keys.UP, 0, 4],
[Keys.DOWN, 0, 5],
[Keys.LEFT, 0, 6],
[Keys.RIGHT, 0, 7],
[Keys.P2_A, 1, 0],
[Keys.P2_B, 1, 1],
[Keys.P2_SELECT, 1, 2],
[Keys.P2_START, 1, 3],
[Keys.P2_UP, 1, 4],
[Keys.P2_DOWN, 1, 5],
[Keys.P2_LEFT, 1, 6],
[Keys.P2_RIGHT, 1, 7],
2018-07-27 17:39:09 +00:00
]);
2019-08-27 00:48:14 +00:00
class JSNESPlatform extends Base6502Platform implements Platform, Probeable {
2018-07-27 17:39:09 +00:00
2019-04-07 01:47:42 +00:00
mainElement;
nes;
video;
audio;
timer;
2019-06-07 17:03:30 +00:00
poller : ControllerPoller;
2019-04-07 01:47:42 +00:00
audioFrequency = 44030; //44100
frameindex = 0;
ntvideo;
ntlastbuf;
2019-08-27 00:48:14 +00:00
machine = { cpuCyclesPerLine: 114 }; // TODO: hack for width of probe scope
2019-04-07 01:47:42 +00:00
constructor(mainElement) {
super();
this.mainElement = mainElement;
}
2018-08-23 12:49:14 +00:00
getPresets() { return JSNES_PRESETS; }
2018-08-23 12:49:14 +00:00
start() {
this.debugPCDelta = 1;
2019-04-07 01:47:42 +00:00
var debugbar = $("<div>").appendTo(this.mainElement);
this.audio = new SampleAudio(this.audioFrequency);
this.video = new RasterVideo(this.mainElement,256,224,{overscan:true});
this.video.create();
2019-02-12 00:01:17 +00:00
// debugging view
2019-04-07 01:47:42 +00:00
this.ntvideo = new RasterVideo(this.mainElement,512,480,{overscan:false});
this.ntvideo.create();
$(this.ntvideo.canvas).hide();
this.ntlastbuf = new Uint32Array(0x1000);
Mousetrap.bind('ctrl+shift+alt+n', () => {
$(this.video.canvas).toggle()
$(this.ntvideo.canvas).toggle()
});
2019-04-07 01:47:42 +00:00
// toggle buttons (TODO)
2019-04-29 02:18:44 +00:00
/*
2019-04-07 01:47:42 +00:00
$('<button>').text("Video").appendTo(debugbar).click(() => { $(this.video.canvas).toggle() });
$('<button>').text("Nametable").appendTo(debugbar).click(() => { $(this.ntvideo.canvas).toggle() });
2019-04-29 02:18:44 +00:00
*/
2019-04-07 01:47:42 +00:00
var idata = this.video.getFrameData();
this.nes = new jsnes.NES({
2019-02-12 00:01:17 +00:00
onFrame: (frameBuffer : number[]) => {
2018-07-27 17:39:09 +00:00
for (var i=0; i<frameBuffer.length; i++)
idata[i] = frameBuffer[i] | 0xff000000;
2019-04-07 01:47:42 +00:00
this.video.updateFrame();
this.frameindex++;
2019-02-12 00:01:17 +00:00
this.updateDebugViews();
2018-07-27 17:39:09 +00:00
},
2019-02-12 00:01:17 +00:00
onAudioSample: (left:number, right:number) => {
2019-04-07 01:47:42 +00:00
if (this.frameindex < 10)
this.audio.feedSample(0, 1); // avoid popping at powerup
2018-08-02 21:47:10 +00:00
else
2019-07-04 19:22:42 +00:00
this.audio.feedSample((left+right)*0.5, 1);
2018-07-27 17:39:09 +00:00
},
onStatusUpdate: function(s) {
console.log(s);
},
//TODO: onBatteryRamWrite
2018-07-27 17:39:09 +00:00
});
2019-04-15 23:37:11 +00:00
//this.nes.ppu.showSpr0Hit = true;
2019-04-07 01:47:42 +00:00
//this.nes.ppu.clipToTvSize = false;
this.nes.stop = () => {
this.haltAndCatchFire("Illegal instruction");
2020-10-24 00:34:19 +00:00
throw new EmuHalt("CPU STOPPED"); //TODO: haltEmulation()
2018-07-27 17:39:09 +00:00
};
// insert debug hook
2019-04-07 01:47:42 +00:00
this.nes.cpu._emulate = this.nes.cpu.emulate;
this.nes.cpu.emulate = () => {
2020-07-11 18:46:47 +00:00
this.probe.logExecute(this.nes.cpu.REG_PC+1, this.nes.cpu.REG_SP);
2019-04-07 01:47:42 +00:00
var cycles = this.nes.cpu._emulate();
this.evalDebugCondition();
2019-08-27 00:48:14 +00:00
this.probe.logClocks(cycles);
2019-11-17 16:48:08 +00:00
return cycles > 0 ? cycles : 1;
2018-07-27 17:39:09 +00:00
}
2019-04-07 01:47:42 +00:00
this.timer = new AnimationTimer(60, this.nextFrame.bind(this));
2018-07-27 17:39:09 +00:00
// set keyboard map
2019-06-07 17:03:30 +00:00
this.poller = setKeyboardFromMap(this.video, [], JSNES_KEYCODE_MAP, (o,key,code,flags) => {
if (flags & KeyFlags.KeyDown)
2019-04-07 01:47:42 +00:00
this.nes.buttonDown(o.index+1, o.mask); // controller, button
else if (flags & KeyFlags.KeyUp)
2019-04-07 01:47:42 +00:00
this.nes.buttonUp(o.index+1, o.mask); // controller, button
});
2019-03-21 16:13:27 +00:00
//var s = ''; nes.ppu.palTable.curTable.forEach((rgb) => { s += "0x"+hex(rgb,6)+", "; }); console.log(s);
}
2019-06-07 17:03:30 +00:00
pollControls() { this.poller.poll(); }
advance(novideo : boolean) : number {
2019-04-07 01:47:42 +00:00
this.nes.frame();
return 29780; //TODO
2019-02-12 00:01:17 +00:00
}
2019-02-12 00:01:17 +00:00
updateDebugViews() {
2019-02-12 14:54:32 +00:00
// don't update if view is hidden
2019-04-07 01:47:42 +00:00
if (! $(this.ntvideo.canvas).is(":visible"))
2019-02-12 14:54:32 +00:00
return;
2019-02-12 00:01:17 +00:00
var a = 0;
var attraddr = 0;
2019-04-07 01:47:42 +00:00
var idata = this.ntvideo.getFrameData();
var baseTile = this.nes.ppu.regS === 0 ? 0 : 256;
2019-02-12 00:01:17 +00:00
for (var row=0; row<60; row++) {
for (var col=0; col<64; col++) {
a = 0x2000 + (col&31) + ((row%30)*32);
if (col >= 32) a += 0x400;
if (row >= 30) a += 0x800;
2019-04-07 01:47:42 +00:00
var name = this.nes.ppu.mirroredLoad(a) + baseTile;
var t = this.nes.ppu.ptTile[name];
2019-02-12 00:01:17 +00:00
attraddr = (a & 0x2c00) | 0x3c0 | (a & 0x0C00) | ((a >> 4) & 0x38) | ((a >> 2) & 0x07);
2019-04-07 01:47:42 +00:00
var attr = this.nes.ppu.mirroredLoad(attraddr);
2019-02-22 17:27:09 +00:00
var tag = name ^ (attr<<9) ^ 0x80000000;
2019-04-07 01:47:42 +00:00
if (tag != this.ntlastbuf[a & 0xfff]) {
this.ntlastbuf[a & 0xfff] = tag;
2019-02-12 00:01:17 +00:00
var i = row*64*8*8 + col*8;
var j = 0;
var attrshift = (col&2) + ((a&0x40)>>4);
var coloradd = ((attr >> attrshift) & 3) << 2;
for (var y=0; y<8; y++) {
for (var x=0; x<8; x++) {
2019-02-12 14:54:32 +00:00
var color = t.pix[j++];
if (color) color += coloradd;
2019-04-07 01:47:42 +00:00
var rgb = this.nes.ppu.imgPalette[color];
2019-02-12 14:54:32 +00:00
idata[i++] = rgb | 0xff000000;
2019-02-12 00:01:17 +00:00
}
i += 64*8-8;
}
}
}
}
2019-04-07 01:47:42 +00:00
this.ntvideo.updateFrame();
2019-02-12 00:01:17 +00:00
}
2018-08-23 12:49:14 +00:00
loadROM(title, data) {
2019-03-14 01:05:08 +00:00
var romstr = byteArrayToString(data);
2019-04-07 01:47:42 +00:00
this.nes.loadROM(romstr);
this.frameindex = 0;
this.installIntercepts();
}
installIntercepts() {
2019-08-27 00:48:14 +00:00
// intercept bus calls, unless we did it already
var mmap = this.nes.mmap;
if (!mmap.haveProxied) {
var oldload = mmap.load.bind(mmap);
var oldwrite = mmap.write.bind(mmap);
2019-08-28 19:21:33 +00:00
var oldregLoad = mmap.regLoad.bind(mmap);
var oldregWrite = mmap.regWrite.bind(mmap);
var lastioaddr = -1;
mmap.load = (addr) => {
2019-08-27 00:48:14 +00:00
var val = oldload(addr);
2019-08-28 19:21:33 +00:00
if (addr != lastioaddr) this.probe.logRead(addr, val);
2019-08-27 00:48:14 +00:00
return val;
}
mmap.write = (addr, val) => {
2019-08-28 19:21:33 +00:00
if (addr != lastioaddr) this.probe.logWrite(addr, val);
2019-08-27 00:48:14 +00:00
oldwrite(addr, val);
}
2019-08-28 19:21:33 +00:00
// try not to read/write then IOread/IOwrite at same time
mmap.regLoad = (addr) => {
2019-08-27 00:48:14 +00:00
var val = oldregLoad(addr);
this.probe.logIORead(addr, val);
2019-08-28 19:21:33 +00:00
lastioaddr = addr;
2019-08-27 00:48:14 +00:00
return val;
}
mmap.regWrite = (addr, val) => {
2019-08-27 00:48:14 +00:00
this.probe.logIOWrite(addr, val);
2019-08-28 19:21:33 +00:00
lastioaddr = addr;
2019-08-27 00:48:14 +00:00
oldregWrite(addr, val);
}
mmap.haveProxied = true;
}
var ppu = this.nes.ppu;
if (!ppu.haveProxied) {
var old_endScanline = ppu.endScanline.bind(ppu);
var old_startFrame = ppu.startFrame.bind(ppu);
var old_writeMem = ppu.writeMem.bind(ppu);
ppu.endScanline = () => {
old_endScanline();
this.probe.logNewScanline();
}
ppu.startFrame = () => {
old_startFrame();
this.probe.logNewFrame();
}
ppu.writeMem = (a,v) => {
old_writeMem(a,v);
this.probe.logVRAMWrite(a,v);
}
2019-08-28 01:34:52 +00:00
ppu.haveProxied = true;
2019-08-27 00:48:14 +00:00
}
}
2018-08-23 12:49:14 +00:00
newCodeAnalyzer() {
return new CodeAnalyzer_nes(this);
}
2018-08-23 12:49:14 +00:00
getOriginPC() { // TODO: is actually NMI
return (this.readAddress(0xfffa) | (this.readAddress(0xfffb) << 8)) & 0xffff;
}
getDefaultExtension() { return ".c"; }
getROMExtension() { return ".nes"; }
2018-08-23 12:49:14 +00:00
reset() {
2019-04-07 01:47:42 +00:00
//this.nes.cpu.reset(); // doesn't work right, crashes
this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);
this.installIntercepts();
2018-07-27 17:39:09 +00:00
}
2018-08-23 12:49:14 +00:00
isRunning() {
2019-04-07 01:47:42 +00:00
return this.timer.isRunning();
2018-07-27 17:39:09 +00:00
}
2018-08-23 12:49:14 +00:00
pause() {
2019-04-07 01:47:42 +00:00
this.timer.stop();
this.audio.stop();
2018-07-27 17:39:09 +00:00
}
2018-08-23 12:49:14 +00:00
resume() {
2019-04-07 01:47:42 +00:00
this.timer.start();
this.audio.start();
2018-07-27 17:39:09 +00:00
}
2018-08-23 12:49:14 +00:00
runToVsync() {
2019-04-07 01:47:42 +00:00
var frame0 = this.frameindex;
this.runEval((c) => { return this.frameindex>frame0; });
}
getRasterScanline() : number {
2019-04-07 01:47:42 +00:00
return this.nes.ppu.scanline;
}
2018-08-02 21:47:10 +00:00
2018-08-23 12:49:14 +00:00
getCPUState() {
2019-04-07 01:47:42 +00:00
var c = this.nes.cpu.toJSON();
2018-07-27 17:39:09 +00:00
this.copy6502REGvars(c);
return c;
}
2018-08-22 14:09:07 +00:00
// TODO don't need to save ROM?
2018-08-23 12:49:14 +00:00
saveState() {
2019-04-07 01:47:42 +00:00
//var s = $.extend(true, {}, this.nes);
var s;
if (this.nes.mmap) {
s = this.nes.toJSON();
} else {
console.log("no nes.mmap!");
s = { cpu: this.nes.cpu.toJSON(), ppu: this.nes.ppu.toJSON() };
}
2018-07-27 17:39:09 +00:00
s.c = s.cpu;
this.copy6502REGvars(s.c);
s.b = s.cpu.mem = s.cpu.mem.slice(0);
2018-07-27 17:39:09 +00:00
s.ppu.vramMem = s.ppu.vramMem.slice(0);
s.ppu.spriteMem = s.ppu.spriteMem.slice(0);
2018-08-23 12:49:14 +00:00
s.ctrl = this.saveControlsState();
2018-07-27 17:39:09 +00:00
return s;
}
2018-08-23 12:49:14 +00:00
loadState(state) {
this.unfixPC(state.cpu);
2019-04-07 01:47:42 +00:00
this.nes.fromJSON(state);
this.fixPC(state.cpu);
2019-04-07 01:47:42 +00:00
//this.nes.cpu.fromJSON(state.cpu);
//this.nes.mmap.fromJSON(state.mmap);
//this.nes.ppu.fromJSON(state.ppu);
this.nes.cpu.mem = state.cpu.mem.slice(0);
this.nes.ppu.vramMem = state.ppu.vramMem.slice(0);
this.nes.ppu.spriteMem = state.ppu.spriteMem.slice(0);
2018-08-23 12:49:14 +00:00
this.loadControlsState(state.ctrl);
2019-04-07 01:47:42 +00:00
//$.extend(this.nes, state);
this.installIntercepts();
2018-07-27 17:39:09 +00:00
}
2018-08-23 12:49:14 +00:00
saveControlsState() {
return {
2019-04-07 01:47:42 +00:00
c1: this.nes.controllers[1].state.slice(0),
c2: this.nes.controllers[2].state.slice(0),
2018-08-23 12:49:14 +00:00
};
}
loadControlsState(state) {
2019-04-07 01:47:42 +00:00
this.nes.controllers[1].state = state.c1;
this.nes.controllers[2].state = state.c2;
2018-08-23 12:49:14 +00:00
}
readAddress(addr) {
return this.nes.cpu.mem[addr];
2018-07-27 17:39:09 +00:00
}
readVRAMAddress(addr : number) : number {
return this.nes.ppu.vramMem[addr];
}
2018-08-23 12:49:14 +00:00
copy6502REGvars(c) {
2018-07-27 17:39:09 +00:00
c.T = 0;
c.PC = c.REG_PC;
this.fixPC(c);
2018-07-27 17:39:09 +00:00
c.A = c.REG_ACC;
c.X = c.REG_X;
c.Y = c.REG_Y;
2018-07-30 15:51:57 +00:00
c.SP = c.REG_SP & 0xff;
2018-07-27 17:39:09 +00:00
c.Z = c.F_ZERO;
c.N = c.F_SIGN;
c.V = c.F_OVERFLOW;
c.D = c.F_DECIMAL;
c.C = c.F_CARRY;
2018-07-30 15:51:57 +00:00
c.I = c.F_INTERRUPT;
2018-07-27 17:39:09 +00:00
c.R = 1;
c.o = this.readAddress(c.PC+1);
return c;
}
2018-08-23 12:49:14 +00:00
getDebugCategories() {
2019-04-07 01:47:42 +00:00
return super.getDebugCategories().concat(['PPU','Mapper']);
2018-07-30 15:51:57 +00:00
}
2018-08-23 12:49:14 +00:00
getDebugInfo(category, state) {
2018-07-30 15:51:57 +00:00
switch (category) {
case 'PPU': return this.ppuStateToLongString(state.ppu, state.b);
case 'Mapper': return this.mapperStateToLongString(state.mmap, state.b);
2018-08-29 17:43:46 +00:00
default: return super.getDebugInfo(category, state);
2018-07-30 15:51:57 +00:00
}
}
2018-08-23 12:49:14 +00:00
ppuStateToLongString(ppu, mem) {
2018-07-30 15:51:57 +00:00
var s = '';
var PPUFLAGS = [
["f_nmiOnVblank","NMI_ON_VBLANK"],
["f_spVisibility","SPRITES"],
["f_spClipping","NO_CLIP_SPRITES"],
2018-07-30 15:51:57 +00:00
["f_dispType","MONOCHROME"],
["f_bgVisibility","BACKGROUND"],
["f_bgClipping","NO_CLIP_BACKGROUND"],
2018-07-30 15:51:57 +00:00
];
for (var i=0; i<PPUFLAGS.length; i++) {
var flag = PPUFLAGS[i];
2018-08-02 17:08:37 +00:00
s += (ppu[flag[0]] ? flag[1] : "-") + " ";
2018-07-30 15:51:57 +00:00
if (i==2 || i==5) s += "\n";
}
2018-08-02 21:47:10 +00:00
var status = mem[0x2002];
s += "\n Status ";
s += (status & 0x80) ? "VBLANK " : "- ";
s += (status & 0x40) ? "SPRITE0HIT " : "- ";
2018-07-30 15:51:57 +00:00
s += "\n";
2018-08-16 21:00:22 +00:00
if (ppu.f_color)
s += " Tint " + ((ppu.f_color&1)?"RED ":"") + ((ppu.f_color&2)?"BLUE ":"") + ((ppu.f_color&4)?"GREEN ":"") + "\n";
2018-07-30 15:51:57 +00:00
if (ppu.f_spVisibility) {
s += "SprSize " + (ppu.f_spriteSize ? "8x16" : "8x8") + "\n";
s += "SprBase $" + (ppu.f_spPatternTable ? "1000" : "0000") + "\n";
}
if (ppu.f_bgVisibility) {
s += " BgBase $" + (ppu.f_bgPatternTable ? "1000" : "0000") + "\n";
s += " NTBase $" + hex(ppu.f_nTblAddress*0x400+0x2000) + "\n";
s += "AddrInc " + (ppu.f_addrInc ? "32" : "1") + "\n";
}
2018-08-02 21:47:10 +00:00
var scrollX = ppu.regFH + ppu.regHT*8;
var scrollY = ppu.regFV + ppu.regVT*8;
s += "ScrollX $" + hex(scrollX) + " (" + ppu.regHT + " * 8 + " + ppu.regFH + " = " + scrollX + ")\n";
s += "ScrollY $" + hex(scrollY) + " (" + ppu.regVT + " * 8 + " + ppu.regFV + " = " + scrollY + ")\n";
s += "\n";
s += " Scan Y: " + ppu.scanline + " X: " + ppu.curX + "\n";
s += "VramCur" + (ppu.firstWrite?" ":"?") + "$" + hex(ppu.vramAddress,4) + "\n";
s += "VramTmp $" + hex(ppu.vramTmpAddress,4) + "\n";
2018-08-02 21:47:10 +00:00
/*
2018-07-30 15:51:57 +00:00
var PPUREGS = [
'cntFV',
'cntV',
'cntH',
'cntVT',
'cntHT',
'regV',
'regH',
'regS',
];
s += "\n";
for (var i=0; i<PPUREGS.length; i++) {
var reg = PPUREGS[i];
s += lpad(reg.toUpperCase(),7) + " $" + hex(ppu[reg]) + " (" + ppu[reg] + ")\n";
}
2018-08-02 21:47:10 +00:00
*/
2018-07-30 15:51:57 +00:00
return s;
}
mapperStateToLongString(mmap, mem) {
//console.log(mmap, mem);
var s = "";
2019-04-07 01:47:42 +00:00
if (this.nes.rom) {
s += "Mapper " + this.nes.rom.mapperType + "\n";
}
if (mmap.irqCounter !== undefined) {
s += "\nIRQ Counter: " + mmap.irqCounter;
s += "\n IRQ Latch: " + mmap.irqLatchValue;
s += "\n IRQ Reload: " + mmap.irqReload;
s += "\n IRQ Enable: " + mmap.irqEnable;
s += "\n PRG Select: " + mmap.prgAddressSelect;
s += "\n CHR Select: " + mmap.chrAddressSelect;
}
s += "\n";
return s;
}
2019-05-11 13:54:09 +00:00
getToolForFilename = (fn:string) : string => {
2019-08-15 16:33:43 +00:00
//if (fn.endsWith(".asm")) return "ca65"; // .asm uses ca65
2019-08-16 01:25:08 +00:00
if (fn.endsWith(".nesasm")) return "nesasm";
else return getToolForFilename_6502(fn);
2019-05-11 13:54:09 +00:00
}
2019-08-27 00:48:14 +00:00
// probing
nullProbe = new NullProbe();
probe : ProbeAll = this.nullProbe;
startProbing?() : ProbeRecorder {
var rec = new ProbeRecorder(this);
this.connectProbe(rec);
return rec;
}
stopProbing?() : void {
this.connectProbe(null);
}
connectProbe(probe:ProbeAll) {
this.probe = probe || this.nullProbe;
}
2019-08-27 16:12:56 +00:00
getMemoryMap = function() { return { main:[
//{name:'Work RAM',start:0x0,size:0x800,type:'ram'},
{name:'OAM Buffer',start:0x200,size:0x100,type:'ram'},
{name:'PPU Registers',start:0x2000,last:0x2008,size:0x2000,type:'io'},
{name:'APU Registers',start:0x4000,last:0x4020,size:0x2000,type:'io'},
{name:'Cartridge RAM',start:0x6000,size:0x2000,type:'ram'},
] } };
2020-06-10 15:25:42 +00:00
showHelp(tool:string, ident:string) {
window.open("https://8bitworkshop.com/docs/platforms/nes/", "_help"); // TODO
2020-06-10 15:25:42 +00:00
}
}
/// MAME support
2021-07-31 14:52:25 +00:00
class NESMAMEPlatform extends BaseMAME6502Platform implements Platform {
2018-08-23 12:49:14 +00:00
start() {
}
loadROM(title, data) {
if (!this.started) {
this.startModule(this.mainElement, {
2020-07-29 20:21:28 +00:00
jsfile:'mame8bitws.js',
//cfgfile:'nes.cfg',
driver:'nes',
width:256,
height:240,
romfn:'/emulator/cart.nes',
romdata:new Uint8Array(data),
preInit:function(_self) {
},
});
} else {
// look at iNES header for PRG and CHR ROM lengths
2019-04-07 01:47:42 +00:00
var prgromlen = data[4] * 0x4000;
var chrromlen = data[5] * 0x2000;
this.loadROMFile(data);
this.loadRegion(":nes_slot:cart:prg_rom", data.slice(0x10, 0x10+prgromlen));
this.loadRegion(":nes_slot:cart:chr_rom", data.slice(0x10+prgromlen, 0x10+prgromlen+chrromlen));
}
}
getPresets() { return JSNES_PRESETS; }
2018-08-23 12:49:14 +00:00
getToolForFilename = getToolForFilename_6502;
2019-05-11 13:54:09 +00:00
getOpcodeMetadata = getOpcodeMetadata_6502;
2018-08-23 12:49:14 +00:00
getDefaultExtension() { return ".c"; };
2017-05-21 21:34:57 +00:00
}
///
2019-04-07 01:47:42 +00:00
PLATFORMS['nes'] = JSNESPlatform;
PLATFORMS['nes-asm'] = JSNESPlatform;
PLATFORMS['nes.mame'] = NESMAMEPlatform;