split baseplatform.ts into mame/wasm
This commit is contained in:
parent
e1b6a2397d
commit
ed41c1fb2a
|
@ -1,4 +1,6 @@
|
|||
|
||||
import { SampledAudioSink } from "./devices";
|
||||
|
||||
// from TSS
|
||||
declare var MasterChannel, AudioLooper, PsgDeviceChannel;
|
||||
|
||||
|
@ -511,8 +513,6 @@ export class SampledAudio {
|
|||
}
|
||||
}
|
||||
|
||||
import { SampledAudioSink } from "./devices";
|
||||
|
||||
interface TssChannel {
|
||||
setBufferLength(len : number) : void;
|
||||
setSampleRate(rate : number) : void;
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
|
||||
import { RAM, RasterVideo, KeyFlags, dumpRAM, AnimationTimer, setKeyboardFromMap, padBytes, ControllerPoller, EmuHalt } from "./emu";
|
||||
import { hex, printFlags, invertMap, getBasePlatform, byteToASCII } from "./util";
|
||||
import { RasterVideo, dumpRAM, AnimationTimer, ControllerPoller } from "./emu";
|
||||
import { hex, printFlags, invertMap, byteToASCII } from "./util";
|
||||
import { CodeAnalyzer } from "./analysis";
|
||||
import { Segment, FileData } from "./workertypes";
|
||||
import { disassemble6502 } from "./cpu/disasm6502";
|
||||
import { disassembleZ80 } from "./cpu/disasmz80";
|
||||
import { Z80 } from "./cpu/ZilogZ80";
|
||||
|
||||
import { Bus, Resettable, FrameBased, VideoSource, SampledAudioSource, AcceptsROM, AcceptsBIOS, AcceptsKeyInput, SavesState, SavesInputState, HasCPU, HasSerialIO, SerialIOInterface, AcceptsJoyInput } from "./devices";
|
||||
import { Probeable, RasterFrameBased, AcceptsPaddleInput } from "./devices";
|
||||
import { SampledAudio } from "./audio";
|
||||
import { ProbeRecorder } from "./recorder";
|
||||
import { BaseWASMMachine } from "./wasmplatform";
|
||||
|
||||
///
|
||||
|
||||
declare var jt, CPU6809;
|
||||
|
||||
export interface OpcodeMetadata {
|
||||
|
@ -654,384 +662,6 @@ export abstract class Base6809Platform extends BaseZ80Platform {
|
|||
}
|
||||
}
|
||||
|
||||
/// MAME SUPPORT
|
||||
|
||||
declare var FS, ENV, Module; // mame emscripten
|
||||
|
||||
export abstract class BaseMAMEPlatform {
|
||||
|
||||
loaded : boolean = false;
|
||||
preinitted : boolean = false;
|
||||
started : boolean = false;
|
||||
romfn : string;
|
||||
romdata : Uint8Array;
|
||||
romtype : string = 'cart';
|
||||
video;
|
||||
running = false;
|
||||
initluavars : boolean = false;
|
||||
luadebugscript : string;
|
||||
js_lua_string;
|
||||
onBreakpointHit;
|
||||
mainElement : HTMLElement;
|
||||
timer : AnimationTimer;
|
||||
|
||||
constructor(mainElement) {
|
||||
this.mainElement = mainElement;
|
||||
this.timer = new AnimationTimer(20, this.poll.bind(this));
|
||||
}
|
||||
|
||||
// http://docs.mamedev.org/techspecs/luaengine.html
|
||||
luacall(s:string) : string {
|
||||
if (!this.js_lua_string) this.js_lua_string = Module.cwrap('_Z13js_lua_stringPKc', 'string', ['string']);
|
||||
return this.js_lua_string(s || "");
|
||||
}
|
||||
|
||||
_pause() {
|
||||
this.running = false;
|
||||
this.timer.stop();
|
||||
}
|
||||
pause() {
|
||||
if (this.loaded && this.running) {
|
||||
this.luacall('emu.pause()');
|
||||
this._pause();
|
||||
}
|
||||
}
|
||||
|
||||
_resume() {
|
||||
this.luacall('emu.unpause()');
|
||||
this.running = true;
|
||||
this.timer.start();
|
||||
}
|
||||
resume() {
|
||||
if (this.loaded && !this.running) { // TODO
|
||||
this._resume();
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
if (this.loaded) {
|
||||
this.luacall('manager:machine():soft_reset()');
|
||||
this.running = true;
|
||||
this.initluavars = false;
|
||||
}
|
||||
}
|
||||
|
||||
isRunning() {
|
||||
return this.running;
|
||||
}
|
||||
|
||||
bufferConsoleOutput(s) {
|
||||
if (typeof s !== 'string') return;
|
||||
console.log(s);
|
||||
}
|
||||
|
||||
startModule(mainElement, opts) {
|
||||
this.started = true;
|
||||
var romfn = this.romfn = this.romfn || opts.romfn;
|
||||
var romdata = this.romdata = this.romdata || opts.romdata || new RAM(opts.romsize).mem;
|
||||
var romtype = this.romtype = this.romtype || opts.romtype;
|
||||
// create canvas
|
||||
var video = this.video = new RasterVideo(this.mainElement, opts.width, opts.height);
|
||||
video.create();
|
||||
$(video.canvas).attr('id','canvas');
|
||||
// load asm.js module
|
||||
console.log("loading", opts.jsfile);
|
||||
var modargs = [opts.driver,
|
||||
'-debug',
|
||||
'-debugger', 'none',
|
||||
'-verbose', '-window', '-nokeepaspect',
|
||||
'-resolution', video.canvas.width+'x'+video.canvas.height
|
||||
];
|
||||
if (romfn) {
|
||||
modargs.push('-'+romtype, romfn);
|
||||
}
|
||||
if (opts.extraargs) {
|
||||
modargs = modargs.concat(opts.extraargs);
|
||||
}
|
||||
console.log(modargs);
|
||||
window['JSMESS'] = {};
|
||||
window['Module'] = {
|
||||
arguments: modargs,
|
||||
screenIsReadOnly: true,
|
||||
print: this.bufferConsoleOutput,
|
||||
canvas:video.canvas,
|
||||
doNotCaptureKeyboard:true,
|
||||
keyboardListeningElement:video.canvas,
|
||||
preInit: () => {
|
||||
console.log("loading FS");
|
||||
ENV.SDL_EMSCRIPTEN_KEYBOARD_ELEMENT = 'canvas';
|
||||
if (opts.cfgfile) {
|
||||
FS.mkdir('/cfg');
|
||||
FS.writeFile('/cfg/' + opts.cfgfile, opts.cfgdata, {encoding:'utf8'});
|
||||
}
|
||||
if (opts.biosfile) {
|
||||
FS.mkdir('/roms');
|
||||
FS.mkdir('/roms/' + opts.driver);
|
||||
FS.writeFile('/roms/' + opts.biosfile, opts.biosdata, {encoding:'binary'});
|
||||
}
|
||||
FS.mkdir('/emulator');
|
||||
if (romfn) {
|
||||
FS.writeFile(romfn, romdata, {encoding:'binary'});
|
||||
}
|
||||
//FS.writeFile('/debug.ini', 'debugger none\n', {encoding:'utf8'});
|
||||
if (opts.preInit) {
|
||||
opts.preInit(self);
|
||||
}
|
||||
this.preinitted = true;
|
||||
},
|
||||
preRun: [
|
||||
() => {
|
||||
$(video.canvas).click((e) =>{
|
||||
video.canvas.focus();
|
||||
});
|
||||
this.loaded = true;
|
||||
console.log("about to run...");
|
||||
}
|
||||
]
|
||||
};
|
||||
// preload files
|
||||
// TODO: ensure loaded
|
||||
var fetch_cfg, fetch_lua;
|
||||
var fetch_bios = $.Deferred();
|
||||
var fetch_wasm = $.Deferred();
|
||||
// fetch config file
|
||||
if (opts.cfgfile) {
|
||||
fetch_cfg = $.get('mame/cfg/' + opts.cfgfile, (data) => {
|
||||
opts.cfgdata = data;
|
||||
console.log("loaded " + opts.cfgfile);
|
||||
}, 'text');
|
||||
}
|
||||
// fetch BIOS file
|
||||
if (opts.biosfile) {
|
||||
var oReq1 = new XMLHttpRequest();
|
||||
oReq1.open("GET", 'mame/roms/' + opts.biosfile, true);
|
||||
oReq1.responseType = "arraybuffer";
|
||||
oReq1.onload = (oEvent) => {
|
||||
opts.biosdata = new Uint8Array(oReq1.response);
|
||||
console.log("loaded " + opts.biosfile); // + " (" + oEvent.total + " bytes)");
|
||||
fetch_bios.resolve();
|
||||
};
|
||||
oReq1.ontimeout = function (oEvent) {
|
||||
throw Error("Timeout loading " + opts.biosfile);
|
||||
}
|
||||
oReq1.send();
|
||||
} else {
|
||||
fetch_bios.resolve();
|
||||
}
|
||||
// load debugger Lua script
|
||||
fetch_lua = $.get('mame/debugger.lua', (data) => {
|
||||
this.luadebugscript = data;
|
||||
console.log("loaded debugger.lua");
|
||||
}, 'text');
|
||||
// load WASM
|
||||
{
|
||||
var oReq2 = new XMLHttpRequest();
|
||||
oReq2.open("GET", 'mame/' + opts.jsfile.replace('.js','.wasm'), true);
|
||||
oReq2.responseType = "arraybuffer";
|
||||
oReq2.onload = (oEvent) => {
|
||||
console.log("loaded WASM file");
|
||||
window['Module'].wasmBinary = new Uint8Array(oReq2.response);
|
||||
fetch_wasm.resolve();
|
||||
};
|
||||
oReq2.ontimeout = function (oEvent) {
|
||||
throw Error("Timeout loading " + opts.jsfile);
|
||||
}
|
||||
oReq2.send();
|
||||
}
|
||||
// start loading script
|
||||
$.when(fetch_lua, fetch_cfg, fetch_bios, fetch_wasm).done( () => {
|
||||
var script = document.createElement('script');
|
||||
script.src = 'mame/' + opts.jsfile;
|
||||
document.getElementsByTagName('head')[0].appendChild(script);
|
||||
console.log("created script element");
|
||||
});
|
||||
// for debugging via browser console
|
||||
window['mamelua'] = (s:string) => {
|
||||
this.initlua();
|
||||
return [s, this.luacall(s)];
|
||||
};
|
||||
}
|
||||
|
||||
loadROMFile(data) {
|
||||
this.romdata = data;
|
||||
if (this.preinitted && this.romfn) {
|
||||
FS.writeFile(this.romfn, data, {encoding:'binary'});
|
||||
}
|
||||
}
|
||||
|
||||
loadRegion(region, data) {
|
||||
if (this.loaded && data.length > 0) {
|
||||
//this.luacall('cart=manager:machine().images["cart"]\nprint(cart:filename())\ncart:load("' + region + '")\n');
|
||||
var s = 'rgn = manager:machine():memory().regions["' + region + '"]\n';
|
||||
//s += 'print(rgn.size)\n';
|
||||
for (var i=0; i<data.length; i+=4) {
|
||||
var v = data[i] + (data[i+1]<<8) + (data[i+2]<<16) + (data[i+3]<<24);
|
||||
s += 'rgn:write_u32(' + i + ',' + v + ')\n'; // TODO: endian?
|
||||
}
|
||||
this.luacall(s);
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// DEBUGGING SUPPORT
|
||||
|
||||
initlua() {
|
||||
if (!this.initluavars) {
|
||||
this.luacall(this.luadebugscript);
|
||||
this.luacall('mamedbg.init()')
|
||||
this.initluavars = true;
|
||||
}
|
||||
}
|
||||
|
||||
readAddress(a:number) : number {
|
||||
this.initlua();
|
||||
return parseInt(this.luacall('return mem:read_u8(' + a + ')'));
|
||||
}
|
||||
|
||||
getCPUReg(reg:string) {
|
||||
if (!this.loaded) return 0; // TODO
|
||||
this.initlua();
|
||||
return parseInt(this.luacall('return cpu.state.'+reg+'.value'));
|
||||
}
|
||||
|
||||
grabState(expr:string) {
|
||||
this.initlua();
|
||||
return {
|
||||
c:this.getCPUState(),
|
||||
buf:this.luacall("return string.tohex(" + expr + ")")
|
||||
}
|
||||
}
|
||||
|
||||
saveState() {
|
||||
return this.grabState("manager:machine():buffer_save()");
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
this.initlua();
|
||||
return this.luacall("manager:machine():buffer_load(string.fromhex('" + state.buf + "'))");
|
||||
}
|
||||
|
||||
poll() {
|
||||
if (this.onBreakpointHit && this.luacall("return tostring(mamedbg.is_stopped())") == 'true') {
|
||||
this._pause();
|
||||
//this.luacall("manager:machine():buffer_load(lastBreakState)");
|
||||
var state = this.grabState("lastBreakState");
|
||||
this.onBreakpointHit(state);
|
||||
}
|
||||
}
|
||||
clearDebug() {
|
||||
this.onBreakpointHit = null;
|
||||
if (this.loaded) {
|
||||
this.initlua();
|
||||
this.luacall('mamedbg.reset()');
|
||||
}
|
||||
}
|
||||
getDebugCallback() {
|
||||
return this.onBreakpointHit;// TODO?
|
||||
}
|
||||
setupDebug(callback) {
|
||||
this.onBreakpointHit = callback;
|
||||
}
|
||||
debugcmd(s) {
|
||||
this.initlua()
|
||||
this.luacall(s);
|
||||
this._resume();
|
||||
}
|
||||
runToPC(pc) {
|
||||
this.debugcmd('mamedbg.runTo(' + pc + ')');
|
||||
}
|
||||
runToVsync() {
|
||||
this.debugcmd('mamedbg.runToVsync()');
|
||||
}
|
||||
runUntilReturn() {
|
||||
this.debugcmd('mamedbg.runUntilReturn()');
|
||||
}
|
||||
// TODO
|
||||
runEval() {
|
||||
this.reset();
|
||||
this.step();
|
||||
}
|
||||
step() {
|
||||
this.debugcmd('mamedbg.step()');
|
||||
}
|
||||
getDebugCategories() {
|
||||
return ['CPU'];
|
||||
}
|
||||
getDebugInfo(category:string, state:EmuState) : string {
|
||||
switch (category) {
|
||||
case 'CPU': return this.cpuStateToLongString(state.c);
|
||||
}
|
||||
}
|
||||
getDebugTree() {
|
||||
this.initlua();
|
||||
var devices = JSON.parse(this.luacall(`return table.tojson(manager:machine().devices)`));
|
||||
var images = JSON.parse(this.luacall(`return table.tojson(manager:machine().images)`));
|
||||
var regions = JSON.parse(this.luacall(`return table.tojson(manager:machine():memory().regions)`));
|
||||
return {
|
||||
devices: devices,
|
||||
images: images,
|
||||
regions: regions,
|
||||
}
|
||||
}
|
||||
|
||||
abstract cpuStateToLongString(c) : string;
|
||||
abstract getCPUState() : any;
|
||||
}
|
||||
|
||||
export abstract class BaseMAME6502Platform extends BaseMAMEPlatform {
|
||||
getPC() : number {
|
||||
return this.getCPUReg('PC');
|
||||
}
|
||||
getSP() : number {
|
||||
return this.getCPUReg('SP');
|
||||
}
|
||||
isStable() { return true; }
|
||||
getCPUState() {
|
||||
return {
|
||||
PC:this.getPC(),
|
||||
SP:this.getSP(),
|
||||
A:this.getCPUReg('A'),
|
||||
X:this.getCPUReg('X'),
|
||||
Y:this.getCPUReg('Y'),
|
||||
flags:this.getCPUReg('P'),
|
||||
};
|
||||
}
|
||||
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
||||
return disassemble6502(pc, read(pc), read(pc+1), read(pc+2));
|
||||
}
|
||||
cpuStateToLongString(c) {
|
||||
return cpuStateToLongString_6502(c);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class BaseMAMEZ80Platform extends BaseMAMEPlatform {
|
||||
getPC() : number {
|
||||
return this.getCPUReg('PC');
|
||||
}
|
||||
getSP() : number {
|
||||
return this.getCPUReg('SP');
|
||||
}
|
||||
isStable() { return true; }
|
||||
getCPUState() {
|
||||
return {
|
||||
PC:this.getPC(),
|
||||
SP:this.getSP(),
|
||||
AF:this.getCPUReg('AF'),
|
||||
BC:this.getCPUReg('BC'),
|
||||
DE:this.getCPUReg('DE'),
|
||||
HL:this.getCPUReg('HL'),
|
||||
IX:this.getCPUReg('IX'),
|
||||
IY:this.getCPUReg('IY'),
|
||||
IR:this.getCPUReg('R') + (this.getCPUReg('I') << 8),
|
||||
};
|
||||
}
|
||||
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
||||
return disassembleZ80(pc, read(pc), read(pc+1), read(pc+2), read(pc+3));
|
||||
}
|
||||
cpuStateToLongString(c) {
|
||||
return cpuStateToLongString_Z80(c);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: how to get stack_end?
|
||||
export function dumpStackToString(platform:Platform, mem:Uint8Array|number[], start:number, end:number, sp:number, jsrop:number) : string {
|
||||
|
@ -1082,11 +712,6 @@ export function lookupSymbol(platform:Platform, addr:number, extra:boolean) {
|
|||
|
||||
/// new Machine platform adapters
|
||||
|
||||
import { Bus, Resettable, FrameBased, VideoSource, SampledAudioSource, AcceptsROM, AcceptsBIOS, AcceptsKeyInput, SavesState, SavesInputState, HasCPU, TrapCondition, CPU, HasSerialIO, SerialIOInterface, SerialEvent, AcceptsJoyInput } from "./devices";
|
||||
import { Probeable, RasterFrameBased, AcceptsPaddleInput, SampledAudioSink, ProbeAll, NullProbe } from "./devices";
|
||||
import { SampledAudio } from "./audio";
|
||||
import { ProbeRecorder } from "./recorder";
|
||||
|
||||
export interface Machine extends Bus, Resettable, FrameBased, AcceptsROM, HasCPU, SavesState<EmuState>, SavesInputState<any> {
|
||||
}
|
||||
|
||||
|
@ -1347,275 +972,7 @@ export abstract class BaseZ80MachinePlatform<T extends Machine> extends BaseMach
|
|||
|
||||
}
|
||||
|
||||
// WASM Support
|
||||
// TODO: detangle from c64
|
||||
|
||||
export abstract class BaseWASMMachine {
|
||||
prefix : string;
|
||||
instance : WebAssembly.Instance;
|
||||
exports : any;
|
||||
sys : number;
|
||||
pixel_dest : Uint32Array;
|
||||
pixel_src : Uint32Array;
|
||||
stateptr : number;
|
||||
statearr : Uint8Array;
|
||||
cpustateptr : number;
|
||||
cpustatearr : Uint8Array;
|
||||
ctrlstateptr : number;
|
||||
ctrlstatearr : Uint8Array;
|
||||
cpu : CPU;
|
||||
romptr : number;
|
||||
romlen : number;
|
||||
romarr : Uint8Array;
|
||||
biosptr : number;
|
||||
biosarr : Uint8Array;
|
||||
audio : SampledAudioSink;
|
||||
audioarr : Float32Array;
|
||||
probe : ProbeAll;
|
||||
maxROMSize : number = 0x40000;
|
||||
|
||||
abstract getCPUState() : CpuState;
|
||||
abstract saveState() : EmuState;
|
||||
abstract loadState(state: EmuState);
|
||||
|
||||
constructor(prefix: string) {
|
||||
this.prefix = prefix;
|
||||
var self = this;
|
||||
this.cpu = {
|
||||
getPC: self.getPC.bind(self),
|
||||
getSP: self.getSP.bind(self),
|
||||
isStable: self.isStable.bind(self),
|
||||
reset: self.reset.bind(self),
|
||||
saveState: () => {
|
||||
return self.getCPUState();
|
||||
},
|
||||
loadState: () => {
|
||||
console.log("loadState not implemented")
|
||||
},
|
||||
connectMemoryBus() {
|
||||
console.log("connectMemoryBus not implemented")
|
||||
},
|
||||
}
|
||||
}
|
||||
getImports(wmod: WebAssembly.Module) {
|
||||
return {};
|
||||
}
|
||||
async fetchWASM() {
|
||||
var wasmResponse = await fetch('res/'+this.prefix+'.wasm');
|
||||
var wasmBinary = await wasmResponse.arrayBuffer();
|
||||
var wasmCompiled = await WebAssembly.compile(wasmBinary);
|
||||
var wasmResult = await WebAssembly.instantiate(wasmCompiled, this.getImports(wasmCompiled));
|
||||
this.instance = wasmResult;
|
||||
this.exports = wasmResult.exports;
|
||||
}
|
||||
async fetchBIOS() {
|
||||
var biosResponse = await fetch('res/'+this.prefix+'.bios');
|
||||
var biosBinary = await biosResponse.arrayBuffer();
|
||||
this.biosptr = this.exports.malloc(biosBinary.byteLength);
|
||||
this.biosarr = new Uint8Array(this.exports.memory.buffer, this.biosptr, biosBinary.byteLength);
|
||||
this.loadBIOS(new Uint8Array(biosBinary));
|
||||
}
|
||||
async initWASM() {
|
||||
// init machine instance
|
||||
this.sys = this.exports.machine_init(this.biosptr);
|
||||
let statesize = this.exports.machine_get_state_size();
|
||||
this.stateptr = this.exports.malloc(statesize);
|
||||
let ctrlstatesize = this.exports.machine_get_controls_state_size();
|
||||
this.ctrlstateptr = this.exports.malloc(ctrlstatesize);
|
||||
let cpustatesize = this.exports.machine_get_cpu_state_size();
|
||||
this.cpustateptr = this.exports.malloc(cpustatesize);
|
||||
this.romptr = this.exports.malloc(this.maxROMSize);
|
||||
// create state buffers
|
||||
// must do this after allocating memory (and everytime we grow memory?)
|
||||
this.statearr = new Uint8Array(this.exports.memory.buffer, this.stateptr, statesize);
|
||||
this.ctrlstatearr = new Uint8Array(this.exports.memory.buffer, this.ctrlstateptr, ctrlstatesize);
|
||||
this.cpustatearr = new Uint8Array(this.exports.memory.buffer, this.cpustateptr, cpustatesize);
|
||||
// create audio buffer
|
||||
let sampbufsize = 4096*4;
|
||||
this.audioarr = new Float32Array(this.exports.memory.buffer, this.exports.machine_get_sample_buffer(), sampbufsize);
|
||||
// create ROM buffer
|
||||
this.romarr = new Uint8Array(this.exports.memory.buffer, this.romptr, this.maxROMSize);
|
||||
// enable c64 joystick map to arrow keys (TODO)
|
||||
//this.exports.c64_set_joystick_type(this.sys, 1);
|
||||
console.log('machine_init', this.sys, statesize, ctrlstatesize, cpustatesize, sampbufsize);
|
||||
}
|
||||
async loadWASM() {
|
||||
await this.fetchWASM();
|
||||
this.exports.memory.grow(64); // TODO: need more when probing?
|
||||
await this.fetchBIOS();
|
||||
await this.initWASM();
|
||||
}
|
||||
getPC() : number {
|
||||
return this.exports.machine_cpu_get_pc(this.sys);
|
||||
}
|
||||
getSP() : number {
|
||||
return this.exports.machine_cpu_get_sp(this.sys);
|
||||
}
|
||||
isStable() : boolean {
|
||||
return this.exports.machine_cpu_is_stable(this.sys);
|
||||
}
|
||||
loadROM(rom: Uint8Array) {
|
||||
if (rom.length > this.maxROMSize) throw new EmuHalt(`Rom size too big: ${rom.length} bytes`);
|
||||
this.romarr.set(rom);
|
||||
this.romlen = rom.length;
|
||||
console.log('load rom', rom.length, 'bytes');
|
||||
this.reset(); // TODO?
|
||||
}
|
||||
// TODO: can't load after machine_init
|
||||
loadBIOS(srcArray: Uint8Array) {
|
||||
this.biosarr.set(srcArray);
|
||||
}
|
||||
reset() {
|
||||
this.exports.machine_reset(this.sys);
|
||||
}
|
||||
/* TODO: we don't need this because c64_exec does this?
|
||||
pollControls() {
|
||||
this.exports.machine_start_frame(this.sys);
|
||||
}
|
||||
*/
|
||||
read(address: number) : number {
|
||||
return this.exports.machine_mem_read(this.sys, address & 0xffff);
|
||||
}
|
||||
readConst(address: number) : number {
|
||||
return this.exports.machine_mem_read(this.sys, address & 0xffff);
|
||||
}
|
||||
write(address: number, value: number) : void {
|
||||
this.exports.machine_mem_write(this.sys, address & 0xffff, value & 0xff);
|
||||
}
|
||||
getAudioParams() {
|
||||
return {sampleRate:44100, stereo:false};
|
||||
}
|
||||
connectVideo(pixels:Uint32Array) : void {
|
||||
this.pixel_dest = pixels;
|
||||
var pixbuf = this.exports.machine_get_pixel_buffer(this.sys); // save video pointer
|
||||
this.pixel_src = new Uint32Array(this.exports.memory.buffer, pixbuf, pixels.length);
|
||||
console.log('connectVideo', pixbuf, pixels.length);
|
||||
}
|
||||
syncVideo() {
|
||||
if (this.exports.machine_update_video) {
|
||||
this.exports.machine_update_video(this.sys);
|
||||
}
|
||||
if (this.pixel_dest != null) {
|
||||
this.pixel_dest.set(this.pixel_src);
|
||||
}
|
||||
}
|
||||
// assume controls buffer is smaller than cpu buffer
|
||||
saveControlsState() : any {
|
||||
//console.log(1, this.romptr, this.romlen, this.ctrlstateptr, this.romarr.slice(0,4), this.ctrlstatearr.slice(0,4));
|
||||
this.exports.machine_save_controls_state(this.sys, this.ctrlstateptr);
|
||||
//console.log(2, this.romptr, this.romlen, this.ctrlstateptr, this.romarr.slice(0,4), this.ctrlstatearr.slice(0,4));
|
||||
return { controls:this.ctrlstatearr.slice(0) }
|
||||
}
|
||||
loadControlsState(state) : void {
|
||||
this.ctrlstatearr.set(state.controls);
|
||||
this.exports.machine_load_controls_state(this.sys, this.ctrlstateptr);
|
||||
}
|
||||
connectAudio(audio : SampledAudioSink) : void {
|
||||
this.audio = audio;
|
||||
}
|
||||
syncAudio() {
|
||||
if (this.audio != null) {
|
||||
var n = this.exports.machine_get_sample_count();
|
||||
for (var i=0; i<n; i++) {
|
||||
this.audio.feedSample(this.audioarr[i], 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: tick might advance 1 instruction
|
||||
advanceFrameClock(trap, cpf:number) : number {
|
||||
var i : number;
|
||||
if (trap) {
|
||||
for (i=0; i<cpf; i++) {
|
||||
if (trap()) {
|
||||
break;
|
||||
}
|
||||
this.exports.machine_tick(this.sys);
|
||||
}
|
||||
} else {
|
||||
this.exports.machine_exec(this.sys, cpf);
|
||||
i = cpf;
|
||||
}
|
||||
this.syncVideo();
|
||||
this.syncAudio();
|
||||
return i;
|
||||
}
|
||||
copyProbeData() {
|
||||
if (this.probe && !(this.probe instanceof NullProbe)) {
|
||||
var datalen = this.exports.machine_get_probe_buffer_size();
|
||||
var dataaddr = this.exports.machine_get_probe_buffer_address();
|
||||
// TODO: more efficient way to put into probe
|
||||
var databuf = new Uint32Array(this.exports.memory.buffer, dataaddr, datalen);
|
||||
this.probe.logNewFrame(); // TODO: machine should do this
|
||||
this.probe.addLogBuffer(databuf);
|
||||
}
|
||||
}
|
||||
connectProbe(probe: ProbeAll): void {
|
||||
this.probe = probe;
|
||||
}
|
||||
getDebugTree() {
|
||||
return this.saveState();
|
||||
}
|
||||
}
|
||||
|
||||
import { loadScript } from "../common/util";
|
||||
import { WasmFs } from "@wasmer/wasmfs";
|
||||
|
||||
let stub = function() { console.log(arguments); return 0 }
|
||||
|
||||
export abstract class BaseWASIMachine extends BaseWASMMachine {
|
||||
m_wasi;
|
||||
wasiInstance;
|
||||
wasmFs : WasmFs;
|
||||
|
||||
constructor(prefix: string) {
|
||||
super(prefix);
|
||||
}
|
||||
getImports(wmod: WebAssembly.Module) {
|
||||
var imports = this.wasiInstance.getImports(wmod);
|
||||
// TODO: eliminate these imports
|
||||
imports.env = {
|
||||
system: stub,
|
||||
__sys_mkdir: stub,
|
||||
__sys_chmod: stub,
|
||||
__sys_stat64: stub,
|
||||
__sys_unlink: stub,
|
||||
__sys_rename: stub,
|
||||
__sys_getdents64: stub,
|
||||
__sys_getcwd: stub,
|
||||
__sys_rmdir: stub,
|
||||
emscripten_thread_sleep: stub,
|
||||
}
|
||||
return imports;
|
||||
}
|
||||
stdoutWrite(buffer) {
|
||||
console.log('>>>', buffer.toString());
|
||||
return buffer.length;
|
||||
}
|
||||
async loadWASM() {
|
||||
await loadScript('node_modules/@wasmer/wasi/lib/index.iife.js'); //TODO: require('@wasmer/wasi');
|
||||
await loadScript('node_modules/@wasmer/wasmfs/lib/index.iife.js'); //TODO: require('@wasmer/wasi');
|
||||
let WASI = window['WASI'];
|
||||
let WasmFs = window['WasmFs'];
|
||||
this.wasmFs = new WasmFs.WasmFs();
|
||||
let bindings = WASI.WASI.defaultBindings;
|
||||
bindings.fs = this.wasmFs.fs;
|
||||
bindings.fs.mkdirSync('/tmp');
|
||||
bindings.path = bindings.path.default;
|
||||
this.wasiInstance = new WASI.WASI({
|
||||
preopenDirectories: {'/tmp':'/tmp'},
|
||||
env: {},
|
||||
args: [],
|
||||
bindings: bindings
|
||||
});
|
||||
this.wasmFs.volume.fds[1].write = this.stdoutWrite.bind(this);
|
||||
this.wasmFs.volume.fds[2].write = this.stdoutWrite.bind(this);
|
||||
await this.fetchWASM();
|
||||
this.wasiInstance.start(this.instance);
|
||||
await this.initWASM();
|
||||
}
|
||||
}
|
||||
|
||||
/////
|
||||
///
|
||||
|
||||
class SerialIOVisualizer {
|
||||
|
||||
|
|
|
@ -0,0 +1,384 @@
|
|||
|
||||
/// MAME SUPPORT
|
||||
|
||||
import { EmuState, DisasmLine, cpuStateToLongString_6502, cpuStateToLongString_Z80 } from "./baseplatform";
|
||||
import { disassemble6502 } from "./cpu/disasm6502";
|
||||
import { disassembleZ80 } from "./cpu/disasmz80";
|
||||
import { AnimationTimer, RAM, RasterVideo } from "./emu";
|
||||
|
||||
declare var FS, ENV, Module; // mame emscripten
|
||||
|
||||
export abstract class BaseMAMEPlatform {
|
||||
|
||||
loaded : boolean = false;
|
||||
preinitted : boolean = false;
|
||||
started : boolean = false;
|
||||
romfn : string;
|
||||
romdata : Uint8Array;
|
||||
romtype : string = 'cart';
|
||||
video;
|
||||
running = false;
|
||||
initluavars : boolean = false;
|
||||
luadebugscript : string;
|
||||
js_lua_string;
|
||||
onBreakpointHit;
|
||||
mainElement : HTMLElement;
|
||||
timer : AnimationTimer;
|
||||
|
||||
constructor(mainElement) {
|
||||
this.mainElement = mainElement;
|
||||
this.timer = new AnimationTimer(20, this.poll.bind(this));
|
||||
}
|
||||
|
||||
// http://docs.mamedev.org/techspecs/luaengine.html
|
||||
luacall(s:string) : string {
|
||||
if (!this.js_lua_string) this.js_lua_string = Module.cwrap('_Z13js_lua_stringPKc', 'string', ['string']);
|
||||
return this.js_lua_string(s || "");
|
||||
}
|
||||
|
||||
_pause() {
|
||||
this.running = false;
|
||||
this.timer.stop();
|
||||
}
|
||||
pause() {
|
||||
if (this.loaded && this.running) {
|
||||
this.luacall('emu.pause()');
|
||||
this._pause();
|
||||
}
|
||||
}
|
||||
|
||||
_resume() {
|
||||
this.luacall('emu.unpause()');
|
||||
this.running = true;
|
||||
this.timer.start();
|
||||
}
|
||||
resume() {
|
||||
if (this.loaded && !this.running) { // TODO
|
||||
this._resume();
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
if (this.loaded) {
|
||||
this.luacall('manager:machine():soft_reset()');
|
||||
this.running = true;
|
||||
this.initluavars = false;
|
||||
}
|
||||
}
|
||||
|
||||
isRunning() {
|
||||
return this.running;
|
||||
}
|
||||
|
||||
bufferConsoleOutput(s) {
|
||||
if (typeof s !== 'string') return;
|
||||
console.log(s);
|
||||
}
|
||||
|
||||
startModule(mainElement, opts) {
|
||||
this.started = true;
|
||||
var romfn = this.romfn = this.romfn || opts.romfn;
|
||||
var romdata = this.romdata = this.romdata || opts.romdata || new RAM(opts.romsize).mem;
|
||||
var romtype = this.romtype = this.romtype || opts.romtype;
|
||||
// create canvas
|
||||
var video = this.video = new RasterVideo(this.mainElement, opts.width, opts.height);
|
||||
video.create();
|
||||
$(video.canvas).attr('id','canvas');
|
||||
// load asm.js module
|
||||
console.log("loading", opts.jsfile);
|
||||
var modargs = [opts.driver,
|
||||
'-debug',
|
||||
'-debugger', 'none',
|
||||
'-verbose', '-window', '-nokeepaspect',
|
||||
'-resolution', video.canvas.width+'x'+video.canvas.height
|
||||
];
|
||||
if (romfn) {
|
||||
modargs.push('-'+romtype, romfn);
|
||||
}
|
||||
if (opts.extraargs) {
|
||||
modargs = modargs.concat(opts.extraargs);
|
||||
}
|
||||
console.log(modargs);
|
||||
window['JSMESS'] = {};
|
||||
window['Module'] = {
|
||||
arguments: modargs,
|
||||
screenIsReadOnly: true,
|
||||
print: this.bufferConsoleOutput,
|
||||
canvas:video.canvas,
|
||||
doNotCaptureKeyboard:true,
|
||||
keyboardListeningElement:video.canvas,
|
||||
preInit: () => {
|
||||
console.log("loading FS");
|
||||
ENV.SDL_EMSCRIPTEN_KEYBOARD_ELEMENT = 'canvas';
|
||||
if (opts.cfgfile) {
|
||||
FS.mkdir('/cfg');
|
||||
FS.writeFile('/cfg/' + opts.cfgfile, opts.cfgdata, {encoding:'utf8'});
|
||||
}
|
||||
if (opts.biosfile) {
|
||||
FS.mkdir('/roms');
|
||||
FS.mkdir('/roms/' + opts.driver);
|
||||
FS.writeFile('/roms/' + opts.biosfile, opts.biosdata, {encoding:'binary'});
|
||||
}
|
||||
FS.mkdir('/emulator');
|
||||
if (romfn) {
|
||||
FS.writeFile(romfn, romdata, {encoding:'binary'});
|
||||
}
|
||||
//FS.writeFile('/debug.ini', 'debugger none\n', {encoding:'utf8'});
|
||||
if (opts.preInit) {
|
||||
opts.preInit(self);
|
||||
}
|
||||
this.preinitted = true;
|
||||
},
|
||||
preRun: [
|
||||
() => {
|
||||
$(video.canvas).click((e) =>{
|
||||
video.canvas.focus();
|
||||
});
|
||||
this.loaded = true;
|
||||
console.log("about to run...");
|
||||
}
|
||||
]
|
||||
};
|
||||
// preload files
|
||||
// TODO: ensure loaded
|
||||
var fetch_cfg, fetch_lua;
|
||||
var fetch_bios = $.Deferred();
|
||||
var fetch_wasm = $.Deferred();
|
||||
// fetch config file
|
||||
if (opts.cfgfile) {
|
||||
fetch_cfg = $.get('mame/cfg/' + opts.cfgfile, (data) => {
|
||||
opts.cfgdata = data;
|
||||
console.log("loaded " + opts.cfgfile);
|
||||
}, 'text');
|
||||
}
|
||||
// fetch BIOS file
|
||||
if (opts.biosfile) {
|
||||
var oReq1 = new XMLHttpRequest();
|
||||
oReq1.open("GET", 'mame/roms/' + opts.biosfile, true);
|
||||
oReq1.responseType = "arraybuffer";
|
||||
oReq1.onload = (oEvent) => {
|
||||
opts.biosdata = new Uint8Array(oReq1.response);
|
||||
console.log("loaded " + opts.biosfile); // + " (" + oEvent.total + " bytes)");
|
||||
fetch_bios.resolve();
|
||||
};
|
||||
oReq1.ontimeout = function (oEvent) {
|
||||
throw Error("Timeout loading " + opts.biosfile);
|
||||
}
|
||||
oReq1.send();
|
||||
} else {
|
||||
fetch_bios.resolve();
|
||||
}
|
||||
// load debugger Lua script
|
||||
fetch_lua = $.get('mame/debugger.lua', (data) => {
|
||||
this.luadebugscript = data;
|
||||
console.log("loaded debugger.lua");
|
||||
}, 'text');
|
||||
// load WASM
|
||||
{
|
||||
var oReq2 = new XMLHttpRequest();
|
||||
oReq2.open("GET", 'mame/' + opts.jsfile.replace('.js','.wasm'), true);
|
||||
oReq2.responseType = "arraybuffer";
|
||||
oReq2.onload = (oEvent) => {
|
||||
console.log("loaded WASM file");
|
||||
window['Module'].wasmBinary = new Uint8Array(oReq2.response);
|
||||
fetch_wasm.resolve();
|
||||
};
|
||||
oReq2.ontimeout = function (oEvent) {
|
||||
throw Error("Timeout loading " + opts.jsfile);
|
||||
}
|
||||
oReq2.send();
|
||||
}
|
||||
// start loading script
|
||||
$.when(fetch_lua, fetch_cfg, fetch_bios, fetch_wasm).done( () => {
|
||||
var script = document.createElement('script');
|
||||
script.src = 'mame/' + opts.jsfile;
|
||||
document.getElementsByTagName('head')[0].appendChild(script);
|
||||
console.log("created script element");
|
||||
});
|
||||
// for debugging via browser console
|
||||
window['mamelua'] = (s:string) => {
|
||||
this.initlua();
|
||||
return [s, this.luacall(s)];
|
||||
};
|
||||
}
|
||||
|
||||
loadROMFile(data) {
|
||||
this.romdata = data;
|
||||
if (this.preinitted && this.romfn) {
|
||||
FS.writeFile(this.romfn, data, {encoding:'binary'});
|
||||
}
|
||||
}
|
||||
|
||||
loadRegion(region, data) {
|
||||
if (this.loaded && data.length > 0) {
|
||||
//this.luacall('cart=manager:machine().images["cart"]\nprint(cart:filename())\ncart:load("' + region + '")\n');
|
||||
var s = 'rgn = manager:machine():memory().regions["' + region + '"]\n';
|
||||
//s += 'print(rgn.size)\n';
|
||||
for (var i=0; i<data.length; i+=4) {
|
||||
var v = data[i] + (data[i+1]<<8) + (data[i+2]<<16) + (data[i+3]<<24);
|
||||
s += 'rgn:write_u32(' + i + ',' + v + ')\n'; // TODO: endian?
|
||||
}
|
||||
this.luacall(s);
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// DEBUGGING SUPPORT
|
||||
|
||||
initlua() {
|
||||
if (!this.initluavars) {
|
||||
this.luacall(this.luadebugscript);
|
||||
this.luacall('mamedbg.init()')
|
||||
this.initluavars = true;
|
||||
}
|
||||
}
|
||||
|
||||
readAddress(a:number) : number {
|
||||
this.initlua();
|
||||
return parseInt(this.luacall('return mem:read_u8(' + a + ')'));
|
||||
}
|
||||
|
||||
getCPUReg(reg:string) {
|
||||
if (!this.loaded) return 0; // TODO
|
||||
this.initlua();
|
||||
return parseInt(this.luacall('return cpu.state.'+reg+'.value'));
|
||||
}
|
||||
|
||||
grabState(expr:string) {
|
||||
this.initlua();
|
||||
return {
|
||||
c:this.getCPUState(),
|
||||
buf:this.luacall("return string.tohex(" + expr + ")")
|
||||
}
|
||||
}
|
||||
|
||||
saveState() {
|
||||
return this.grabState("manager:machine():buffer_save()");
|
||||
}
|
||||
|
||||
loadState(state) {
|
||||
this.initlua();
|
||||
return this.luacall("manager:machine():buffer_load(string.fromhex('" + state.buf + "'))");
|
||||
}
|
||||
|
||||
poll() {
|
||||
if (this.onBreakpointHit && this.luacall("return tostring(mamedbg.is_stopped())") == 'true') {
|
||||
this._pause();
|
||||
//this.luacall("manager:machine():buffer_load(lastBreakState)");
|
||||
var state = this.grabState("lastBreakState");
|
||||
this.onBreakpointHit(state);
|
||||
}
|
||||
}
|
||||
clearDebug() {
|
||||
this.onBreakpointHit = null;
|
||||
if (this.loaded) {
|
||||
this.initlua();
|
||||
this.luacall('mamedbg.reset()');
|
||||
}
|
||||
}
|
||||
getDebugCallback() {
|
||||
return this.onBreakpointHit;// TODO?
|
||||
}
|
||||
setupDebug(callback) {
|
||||
this.onBreakpointHit = callback;
|
||||
}
|
||||
debugcmd(s) {
|
||||
this.initlua()
|
||||
this.luacall(s);
|
||||
this._resume();
|
||||
}
|
||||
runToPC(pc) {
|
||||
this.debugcmd('mamedbg.runTo(' + pc + ')');
|
||||
}
|
||||
runToVsync() {
|
||||
this.debugcmd('mamedbg.runToVsync()');
|
||||
}
|
||||
runUntilReturn() {
|
||||
this.debugcmd('mamedbg.runUntilReturn()');
|
||||
}
|
||||
// TODO
|
||||
runEval() {
|
||||
this.reset();
|
||||
this.step();
|
||||
}
|
||||
step() {
|
||||
this.debugcmd('mamedbg.step()');
|
||||
}
|
||||
getDebugCategories() {
|
||||
return ['CPU'];
|
||||
}
|
||||
getDebugInfo(category:string, state:EmuState) : string {
|
||||
switch (category) {
|
||||
case 'CPU': return this.cpuStateToLongString(state.c);
|
||||
}
|
||||
}
|
||||
getDebugTree() {
|
||||
this.initlua();
|
||||
var devices = JSON.parse(this.luacall(`return table.tojson(manager:machine().devices)`));
|
||||
var images = JSON.parse(this.luacall(`return table.tojson(manager:machine().images)`));
|
||||
var regions = JSON.parse(this.luacall(`return table.tojson(manager:machine():memory().regions)`));
|
||||
return {
|
||||
devices: devices,
|
||||
images: images,
|
||||
regions: regions,
|
||||
}
|
||||
}
|
||||
|
||||
abstract cpuStateToLongString(c) : string;
|
||||
abstract getCPUState() : any;
|
||||
}
|
||||
|
||||
export abstract class BaseMAME6502Platform extends BaseMAMEPlatform {
|
||||
getPC() : number {
|
||||
return this.getCPUReg('PC');
|
||||
}
|
||||
getSP() : number {
|
||||
return this.getCPUReg('SP');
|
||||
}
|
||||
isStable() { return true; }
|
||||
getCPUState() {
|
||||
return {
|
||||
PC:this.getPC(),
|
||||
SP:this.getSP(),
|
||||
A:this.getCPUReg('A'),
|
||||
X:this.getCPUReg('X'),
|
||||
Y:this.getCPUReg('Y'),
|
||||
flags:this.getCPUReg('P'),
|
||||
};
|
||||
}
|
||||
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
||||
return disassemble6502(pc, read(pc), read(pc+1), read(pc+2));
|
||||
}
|
||||
cpuStateToLongString(c) {
|
||||
return cpuStateToLongString_6502(c);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class BaseMAMEZ80Platform extends BaseMAMEPlatform {
|
||||
getPC() : number {
|
||||
return this.getCPUReg('PC');
|
||||
}
|
||||
getSP() : number {
|
||||
return this.getCPUReg('SP');
|
||||
}
|
||||
isStable() { return true; }
|
||||
getCPUState() {
|
||||
return {
|
||||
PC:this.getPC(),
|
||||
SP:this.getSP(),
|
||||
AF:this.getCPUReg('AF'),
|
||||
BC:this.getCPUReg('BC'),
|
||||
DE:this.getCPUReg('DE'),
|
||||
HL:this.getCPUReg('HL'),
|
||||
IX:this.getCPUReg('IX'),
|
||||
IY:this.getCPUReg('IY'),
|
||||
IR:this.getCPUReg('R') + (this.getCPUReg('I') << 8),
|
||||
};
|
||||
}
|
||||
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
||||
return disassembleZ80(pc, read(pc), read(pc+1), read(pc+2), read(pc+3));
|
||||
}
|
||||
cpuStateToLongString(c) {
|
||||
return cpuStateToLongString_Z80(c);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,271 @@
|
|||
|
||||
import { loadScript } from "../common/util";
|
||||
import { WasmFs } from "@wasmer/wasmfs";
|
||||
import { CpuState, EmuState } from "./baseplatform";
|
||||
import { CPU, SampledAudioSink, ProbeAll, NullProbe } from "./devices";
|
||||
import { EmuHalt } from "./emu";
|
||||
|
||||
// WASM Support
|
||||
// TODO: detangle from c64
|
||||
|
||||
export abstract class BaseWASMMachine {
|
||||
prefix : string;
|
||||
instance : WebAssembly.Instance;
|
||||
exports : any;
|
||||
sys : number;
|
||||
pixel_dest : Uint32Array;
|
||||
pixel_src : Uint32Array;
|
||||
stateptr : number;
|
||||
statearr : Uint8Array;
|
||||
cpustateptr : number;
|
||||
cpustatearr : Uint8Array;
|
||||
ctrlstateptr : number;
|
||||
ctrlstatearr : Uint8Array;
|
||||
cpu : CPU;
|
||||
romptr : number;
|
||||
romlen : number;
|
||||
romarr : Uint8Array;
|
||||
biosptr : number;
|
||||
biosarr : Uint8Array;
|
||||
audio : SampledAudioSink;
|
||||
audioarr : Float32Array;
|
||||
probe : ProbeAll;
|
||||
maxROMSize : number = 0x40000;
|
||||
|
||||
abstract getCPUState() : CpuState;
|
||||
abstract saveState() : EmuState;
|
||||
abstract loadState(state: EmuState);
|
||||
|
||||
constructor(prefix: string) {
|
||||
this.prefix = prefix;
|
||||
var self = this;
|
||||
this.cpu = {
|
||||
getPC: self.getPC.bind(self),
|
||||
getSP: self.getSP.bind(self),
|
||||
isStable: self.isStable.bind(self),
|
||||
reset: self.reset.bind(self),
|
||||
saveState: () => {
|
||||
return self.getCPUState();
|
||||
},
|
||||
loadState: () => {
|
||||
console.log("loadState not implemented")
|
||||
},
|
||||
connectMemoryBus() {
|
||||
console.log("connectMemoryBus not implemented")
|
||||
},
|
||||
}
|
||||
}
|
||||
getImports(wmod: WebAssembly.Module) {
|
||||
return {};
|
||||
}
|
||||
async fetchWASM() {
|
||||
var wasmResponse = await fetch('res/'+this.prefix+'.wasm');
|
||||
var wasmBinary = await wasmResponse.arrayBuffer();
|
||||
var wasmCompiled = await WebAssembly.compile(wasmBinary);
|
||||
var wasmResult = await WebAssembly.instantiate(wasmCompiled, this.getImports(wasmCompiled));
|
||||
this.instance = wasmResult;
|
||||
this.exports = wasmResult.exports;
|
||||
}
|
||||
async fetchBIOS() {
|
||||
var biosResponse = await fetch('res/'+this.prefix+'.bios');
|
||||
var biosBinary = await biosResponse.arrayBuffer();
|
||||
this.biosptr = this.exports.malloc(biosBinary.byteLength);
|
||||
this.biosarr = new Uint8Array(this.exports.memory.buffer, this.biosptr, biosBinary.byteLength);
|
||||
this.loadBIOS(new Uint8Array(biosBinary));
|
||||
}
|
||||
async initWASM() {
|
||||
// init machine instance
|
||||
this.sys = this.exports.machine_init(this.biosptr);
|
||||
let statesize = this.exports.machine_get_state_size();
|
||||
this.stateptr = this.exports.malloc(statesize);
|
||||
let ctrlstatesize = this.exports.machine_get_controls_state_size();
|
||||
this.ctrlstateptr = this.exports.malloc(ctrlstatesize);
|
||||
let cpustatesize = this.exports.machine_get_cpu_state_size();
|
||||
this.cpustateptr = this.exports.malloc(cpustatesize);
|
||||
this.romptr = this.exports.malloc(this.maxROMSize);
|
||||
// create state buffers
|
||||
// must do this after allocating memory (and everytime we grow memory?)
|
||||
this.statearr = new Uint8Array(this.exports.memory.buffer, this.stateptr, statesize);
|
||||
this.ctrlstatearr = new Uint8Array(this.exports.memory.buffer, this.ctrlstateptr, ctrlstatesize);
|
||||
this.cpustatearr = new Uint8Array(this.exports.memory.buffer, this.cpustateptr, cpustatesize);
|
||||
// create audio buffer
|
||||
let sampbufsize = 4096*4;
|
||||
this.audioarr = new Float32Array(this.exports.memory.buffer, this.exports.machine_get_sample_buffer(), sampbufsize);
|
||||
// create ROM buffer
|
||||
this.romarr = new Uint8Array(this.exports.memory.buffer, this.romptr, this.maxROMSize);
|
||||
// enable c64 joystick map to arrow keys (TODO)
|
||||
//this.exports.c64_set_joystick_type(this.sys, 1);
|
||||
console.log('machine_init', this.sys, statesize, ctrlstatesize, cpustatesize, sampbufsize);
|
||||
}
|
||||
async loadWASM() {
|
||||
await this.fetchWASM();
|
||||
this.exports.memory.grow(64); // TODO: need more when probing?
|
||||
await this.fetchBIOS();
|
||||
await this.initWASM();
|
||||
}
|
||||
getPC() : number {
|
||||
return this.exports.machine_cpu_get_pc(this.sys);
|
||||
}
|
||||
getSP() : number {
|
||||
return this.exports.machine_cpu_get_sp(this.sys);
|
||||
}
|
||||
isStable() : boolean {
|
||||
return this.exports.machine_cpu_is_stable(this.sys);
|
||||
}
|
||||
loadROM(rom: Uint8Array) {
|
||||
if (rom.length > this.maxROMSize) throw new EmuHalt(`Rom size too big: ${rom.length} bytes`);
|
||||
this.romarr.set(rom);
|
||||
this.romlen = rom.length;
|
||||
console.log('load rom', rom.length, 'bytes');
|
||||
this.reset(); // TODO?
|
||||
}
|
||||
// TODO: can't load after machine_init
|
||||
loadBIOS(srcArray: Uint8Array) {
|
||||
this.biosarr.set(srcArray);
|
||||
}
|
||||
reset() {
|
||||
this.exports.machine_reset(this.sys);
|
||||
}
|
||||
/* TODO: we don't need this because c64_exec does this?
|
||||
pollControls() {
|
||||
this.exports.machine_start_frame(this.sys);
|
||||
}
|
||||
*/
|
||||
read(address: number) : number {
|
||||
return this.exports.machine_mem_read(this.sys, address & 0xffff);
|
||||
}
|
||||
readConst(address: number) : number {
|
||||
return this.exports.machine_mem_read(this.sys, address & 0xffff);
|
||||
}
|
||||
write(address: number, value: number) : void {
|
||||
this.exports.machine_mem_write(this.sys, address & 0xffff, value & 0xff);
|
||||
}
|
||||
getAudioParams() {
|
||||
return {sampleRate:44100, stereo:false};
|
||||
}
|
||||
connectVideo(pixels:Uint32Array) : void {
|
||||
this.pixel_dest = pixels;
|
||||
var pixbuf = this.exports.machine_get_pixel_buffer(this.sys); // save video pointer
|
||||
this.pixel_src = new Uint32Array(this.exports.memory.buffer, pixbuf, pixels.length);
|
||||
console.log('connectVideo', pixbuf, pixels.length);
|
||||
}
|
||||
syncVideo() {
|
||||
if (this.exports.machine_update_video) {
|
||||
this.exports.machine_update_video(this.sys);
|
||||
}
|
||||
if (this.pixel_dest != null) {
|
||||
this.pixel_dest.set(this.pixel_src);
|
||||
}
|
||||
}
|
||||
// assume controls buffer is smaller than cpu buffer
|
||||
saveControlsState() : any {
|
||||
//console.log(1, this.romptr, this.romlen, this.ctrlstateptr, this.romarr.slice(0,4), this.ctrlstatearr.slice(0,4));
|
||||
this.exports.machine_save_controls_state(this.sys, this.ctrlstateptr);
|
||||
//console.log(2, this.romptr, this.romlen, this.ctrlstateptr, this.romarr.slice(0,4), this.ctrlstatearr.slice(0,4));
|
||||
return { controls:this.ctrlstatearr.slice(0) }
|
||||
}
|
||||
loadControlsState(state) : void {
|
||||
this.ctrlstatearr.set(state.controls);
|
||||
this.exports.machine_load_controls_state(this.sys, this.ctrlstateptr);
|
||||
}
|
||||
connectAudio(audio : SampledAudioSink) : void {
|
||||
this.audio = audio;
|
||||
}
|
||||
syncAudio() {
|
||||
if (this.audio != null) {
|
||||
var n = this.exports.machine_get_sample_count();
|
||||
for (var i=0; i<n; i++) {
|
||||
this.audio.feedSample(this.audioarr[i], 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: tick might advance 1 instruction
|
||||
advanceFrameClock(trap, cpf:number) : number {
|
||||
var i : number;
|
||||
if (trap) {
|
||||
for (i=0; i<cpf; i++) {
|
||||
if (trap()) {
|
||||
break;
|
||||
}
|
||||
this.exports.machine_tick(this.sys);
|
||||
}
|
||||
} else {
|
||||
this.exports.machine_exec(this.sys, cpf);
|
||||
i = cpf;
|
||||
}
|
||||
this.syncVideo();
|
||||
this.syncAudio();
|
||||
return i;
|
||||
}
|
||||
copyProbeData() {
|
||||
if (this.probe && !(this.probe instanceof NullProbe)) {
|
||||
var datalen = this.exports.machine_get_probe_buffer_size();
|
||||
var dataaddr = this.exports.machine_get_probe_buffer_address();
|
||||
// TODO: more efficient way to put into probe
|
||||
var databuf = new Uint32Array(this.exports.memory.buffer, dataaddr, datalen);
|
||||
this.probe.logNewFrame(); // TODO: machine should do this
|
||||
this.probe.addLogBuffer(databuf);
|
||||
}
|
||||
}
|
||||
connectProbe(probe: ProbeAll): void {
|
||||
this.probe = probe;
|
||||
}
|
||||
getDebugTree() {
|
||||
return this.saveState();
|
||||
}
|
||||
}
|
||||
|
||||
let stub = function() { console.log(arguments); return 0 }
|
||||
|
||||
export abstract class BaseWASIMachine extends BaseWASMMachine {
|
||||
m_wasi;
|
||||
wasiInstance;
|
||||
wasmFs : WasmFs;
|
||||
|
||||
constructor(prefix: string) {
|
||||
super(prefix);
|
||||
}
|
||||
getImports(wmod: WebAssembly.Module) {
|
||||
var imports = this.wasiInstance.getImports(wmod);
|
||||
// TODO: eliminate these imports
|
||||
imports.env = {
|
||||
system: stub,
|
||||
__sys_mkdir: stub,
|
||||
__sys_chmod: stub,
|
||||
__sys_stat64: stub,
|
||||
__sys_unlink: stub,
|
||||
__sys_rename: stub,
|
||||
__sys_getdents64: stub,
|
||||
__sys_getcwd: stub,
|
||||
__sys_rmdir: stub,
|
||||
emscripten_thread_sleep: stub,
|
||||
}
|
||||
return imports;
|
||||
}
|
||||
stdoutWrite(buffer) {
|
||||
console.log('>>>', buffer.toString());
|
||||
return buffer.length;
|
||||
}
|
||||
async loadWASM() {
|
||||
await loadScript('node_modules/@wasmer/wasi/lib/index.iife.js'); //TODO: require('@wasmer/wasi');
|
||||
await loadScript('node_modules/@wasmer/wasmfs/lib/index.iife.js'); //TODO: require('@wasmer/wasi');
|
||||
let WASI = window['WASI'];
|
||||
let WasmFs = window['WasmFs'];
|
||||
this.wasmFs = new WasmFs.WasmFs();
|
||||
let bindings = WASI.WASI.defaultBindings;
|
||||
bindings.fs = this.wasmFs.fs;
|
||||
bindings.fs.mkdirSync('/tmp');
|
||||
bindings.path = bindings.path.default;
|
||||
this.wasiInstance = new WASI.WASI({
|
||||
preopenDirectories: {'/tmp':'/tmp'},
|
||||
env: {},
|
||||
args: [],
|
||||
bindings: bindings
|
||||
});
|
||||
this.wasmFs.volume.fds[1].write = this.stdoutWrite.bind(this);
|
||||
this.wasmFs.volume.fds[2].write = this.stdoutWrite.bind(this);
|
||||
await this.fetchWASM();
|
||||
this.wasiInstance.start(this.instance);
|
||||
await this.initWASM();
|
||||
}
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
import { BaseWASIMachine, Machine } from "../common/baseplatform";
|
||||
import { Machine } from "../common/baseplatform";
|
||||
import { AcceptsKeyInput, AcceptsPaddleInput, AcceptsROM, FrameBased, Probeable, RasterFrameBased, TrapCondition, VideoSource } from "../common/devices";
|
||||
import { KeyFlags } from "../common/emu";
|
||||
import { hex } from "../common/util";
|
||||
import { BaseWASIMachine } from "../common/wasmplatform";
|
||||
|
||||
export class Atari8_WASMMachine extends BaseWASIMachine
|
||||
implements Machine, Probeable, VideoSource, AcceptsROM, FrameBased, AcceptsKeyInput, AcceptsPaddleInput {
|
||||
|
|
|
@ -12,8 +12,9 @@ import { lzgmini, stringToByteArray, hex, rgb2bgr } from "../common/util";
|
|||
|
||||
//// WASM Machine
|
||||
|
||||
import { Machine, BaseWASMMachine } from "../common/baseplatform";
|
||||
import { Machine } from "../common/baseplatform";
|
||||
import { TrapCondition } from "../common/devices";
|
||||
import { BaseWASMMachine } from "../common/wasmplatform";
|
||||
|
||||
export class C64_WASMMachine extends BaseWASMMachine implements Machine, Probeable {
|
||||
|
||||
|
|
|
@ -6,8 +6,9 @@ import { lzgmini, stringToByteArray, hex, rgb2bgr } from "../common/util";
|
|||
|
||||
//// WASM Machine
|
||||
|
||||
import { Machine, BaseWASMMachine } from "../common/baseplatform";
|
||||
import { Machine } from "../common/baseplatform";
|
||||
import { TrapCondition } from "../common/devices";
|
||||
import { BaseWASMMachine } from "../common/wasmplatform";
|
||||
|
||||
export class ZX_WASMMachine extends BaseWASMMachine implements Machine {
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
|
||||
import { Platform, getOpcodeMetadata_6502, getToolForFilename_6502, BaseMAME6502Platform } from "../common/baseplatform";
|
||||
import { Platform, getOpcodeMetadata_6502, getToolForFilename_6502 } from "../common/baseplatform";
|
||||
import { PLATFORMS } from "../common/emu";
|
||||
import { AppleII } from "../machine/apple2";
|
||||
import { Base6502MachinePlatform } from "../common/baseplatform";
|
||||
import { CodeAnalyzer_apple2 } from "../common/analysis";
|
||||
import { BaseMAME6502Platform } from "../common/mameplatform";
|
||||
|
||||
const APPLE2_PRESETS = [
|
||||
{id:'sieve.c', name:'Sieve'},
|
||||
|
@ -55,10 +59,6 @@ class Apple2MAMEPlatform extends BaseMAME6502Platform implements Platform {
|
|||
|
||||
///
|
||||
|
||||
import { AppleII } from "../machine/apple2";
|
||||
import { Base6502MachinePlatform } from "../common/baseplatform";
|
||||
import { CodeAnalyzer_apple2 } from "../common/analysis";
|
||||
|
||||
class NewApple2Platform extends Base6502MachinePlatform<AppleII> implements Platform {
|
||||
|
||||
newMachine() { return new AppleII(); }
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
"use strict";
|
||||
|
||||
import { Platform, getOpcodeMetadata_6502, getToolForFilename_6502, Base6502MachinePlatform, BaseMAME6502Platform } from "../common/baseplatform";
|
||||
import { Platform, getOpcodeMetadata_6502, getToolForFilename_6502, Base6502MachinePlatform } from "../common/baseplatform";
|
||||
import { PLATFORMS, Keys, makeKeycodeMap } from "../common/emu";
|
||||
import { BaseMAME6502Platform } from "../common/mameplatform";
|
||||
import { Atari8_WASMMachine } from "../machine/atari8";
|
||||
|
||||
declare var jt; // for 6502
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
|
||||
import { C64_WASMMachine } from "../machine/c64";
|
||||
import { Platform, Base6502MachinePlatform, getToolForFilename_6502, getOpcodeMetadata_6502, BaseMAME6502Platform } from "../common/baseplatform";
|
||||
import { Platform, Base6502MachinePlatform, getToolForFilename_6502, getOpcodeMetadata_6502 } from "../common/baseplatform";
|
||||
import { PLATFORMS } from "../common/emu";
|
||||
import { BaseMAME6502Platform } from "../common/mameplatform";
|
||||
|
||||
const C64_PRESETS = [
|
||||
{id:'hello.dasm', name:'Hello World (ASM)'},
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
|
||||
import { ColecoVision } from "../machine/coleco";
|
||||
import { Platform, BaseZ80MachinePlatform, getToolForFilename_z80, BaseMAMEZ80Platform } from "../common/baseplatform";
|
||||
import { Platform, BaseZ80MachinePlatform, getToolForFilename_z80 } from "../common/baseplatform";
|
||||
import { PLATFORMS } from "../common/emu";
|
||||
import { BaseMAMEZ80Platform } from "../common/mameplatform";
|
||||
|
||||
export var ColecoVision_PRESETS = [
|
||||
{ id: 'text.c', name: 'Text Mode' },
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
|
||||
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 { Platform, Base6502Platform, getOpcodeMetadata_6502, getToolForFilename_6502 } from "../common/baseplatform";
|
||||
import { PLATFORMS, setKeyboardFromMap, AnimationTimer, RasterVideo, Keys, makeKeycodeMap, KeyFlags, EmuHalt, ControllerPoller } from "../common/emu";
|
||||
import { hex, 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";
|
||||
import Mousetrap = require('mousetrap');
|
||||
import jsnes = require('../../jsnes');
|
||||
import { BaseMAME6502Platform } from "../common/mameplatform";
|
||||
|
||||
const JSNES_PRESETS = [
|
||||
{id:'hello.c', name:'Hello World'},
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
"use strict";
|
||||
|
||||
import { Platform, BasePlatform, cpuStateToLongString_6502, EmuRecorder, dumpStackToString, DisasmLine, CpuState, DebugCondition, BaseMAME6502Platform } from "../common/baseplatform";
|
||||
import { PLATFORMS, RAM, newAddressDecoder, dumpRAM, EmuHalt } from "../common/emu";
|
||||
import { hex, lpad, tobin, byte2signed } from "../common/util";
|
||||
import { Platform, BasePlatform, cpuStateToLongString_6502, dumpStackToString, DisasmLine, CpuState } from "../common/baseplatform";
|
||||
import { PLATFORMS, dumpRAM, EmuHalt } from "../common/emu";
|
||||
import { hex, lpad, tobin } from "../common/util";
|
||||
import { CodeAnalyzer_vcs } from "../common/analysis";
|
||||
import { disassemble6502 } from "../common/cpu/disasm6502";
|
||||
import { ProbeRecorder } from "../common/recorder";
|
||||
import { NullProbe, Probeable, ProbeAll } from "../common/devices";
|
||||
import { NullProbe, ProbeAll } from "../common/devices";
|
||||
import { BaseMAME6502Platform } from "../common/mameplatform";
|
||||
|
||||
declare var Javatari : any;
|
||||
declare var jt : any; // 6502
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
import type { WorkerResult, WorkerFileUpdate, WorkerBuildStep, WorkerMessage, WorkerError, Dependency, SourceLine, CodeListing, CodeListingMap, Segment, WorkerOutput, SourceLocation } from "../common/workertypes";
|
||||
import { getBasePlatform, getRootBasePlatform, hex } from "../common/util";
|
||||
import { Assembler } from "./assembler";
|
||||
import * as vxmlparser from '../common/hdl/vxmlparser';
|
||||
import * as basic_compiler from '../common/basic/compiler';
|
||||
|
||||
interface EmscriptenModule {
|
||||
callMain: (args: string[]) => void;
|
||||
|
@ -1898,9 +1900,6 @@ function compileInlineASM(code:string, platform, options, errors, asmlines) {
|
|||
return code;
|
||||
}
|
||||
|
||||
import * as hdltypes from '../common/hdl/hdltypes';
|
||||
import * as vxmlparser from '../common/hdl/vxmlparser';
|
||||
|
||||
function compileVerilator(step:BuildStep) {
|
||||
loadNative("verilator_bin");
|
||||
var platform = step.platform || 'verilog';
|
||||
|
@ -2936,8 +2935,6 @@ function compileFastBasic(step:BuildStep) {
|
|||
};
|
||||
}
|
||||
|
||||
import * as basic_compiler from '../common/basic/compiler';
|
||||
|
||||
function compileBASIC(step:BuildStep) {
|
||||
var jsonpath = step.path + ".json";
|
||||
gatherFiles(step);
|
||||
|
|
Loading…
Reference in New Issue