2018-07-08 04:58:11 +00:00
|
|
|
|
2019-06-07 17:03:30 +00:00
|
|
|
import { RAM, RasterVideo, dumpRAM, AnimationTimer, setKeyboardFromMap, padBytes, ControllerPoller } from "./emu";
|
2018-09-17 20:09:09 +00:00
|
|
|
import { hex, printFlags, invertMap } from "./util";
|
2018-08-17 19:13:58 +00:00
|
|
|
import { CodeAnalyzer } from "./analysis";
|
2018-08-27 21:31:49 +00:00
|
|
|
import { disassemble6502 } from "./cpu/disasm6502";
|
|
|
|
import { disassembleZ80 } from "./cpu/disasmz80";
|
2019-08-25 20:20:12 +00:00
|
|
|
import { Z80 } from "./cpu/ZilogZ80";
|
2018-08-16 23:19:20 +00:00
|
|
|
|
2019-08-25 20:20:12 +00:00
|
|
|
declare var jt, CPU6809;
|
2018-08-16 23:19:20 +00:00
|
|
|
|
2018-07-08 04:58:11 +00:00
|
|
|
export interface OpcodeMetadata {
|
|
|
|
minCycles: number;
|
2018-07-11 15:17:37 +00:00
|
|
|
maxCycles: number;
|
2018-08-16 23:19:20 +00:00
|
|
|
insnlength: number;
|
|
|
|
opcode: number;
|
2018-07-08 04:58:11 +00:00
|
|
|
}
|
|
|
|
|
2018-08-22 03:39:34 +00:00
|
|
|
export interface CpuState {
|
2018-09-14 13:10:41 +00:00
|
|
|
PC:number;
|
|
|
|
EPC?:number; // effective PC (for bankswitching)
|
|
|
|
o?:number;/*opcode*/
|
2018-08-16 23:19:20 +00:00
|
|
|
SP?:number
|
2018-07-11 15:17:37 +00:00
|
|
|
/*
|
2018-11-22 12:39:06 +00:00
|
|
|
A:number, X:number, Y:number, SP:number, R:boolean,
|
2018-07-11 15:17:37 +00:00
|
|
|
N,V,D,Z,C:boolean*/
|
|
|
|
};
|
2018-08-22 18:50:10 +00:00
|
|
|
export interface EmuState {
|
2018-08-29 12:24:13 +00:00
|
|
|
c?:CpuState, // CPU state
|
2018-09-18 18:09:51 +00:00
|
|
|
b?:Uint8Array|number[], // RAM (TODO: not for vcs, support Uint8Array)
|
2019-08-23 14:37:30 +00:00
|
|
|
ram?:Uint8Array,
|
2018-08-29 12:24:13 +00:00
|
|
|
o?:{}, // verilog
|
2018-08-22 18:50:10 +00:00
|
|
|
};
|
|
|
|
export interface EmuControlsState {
|
|
|
|
}
|
|
|
|
export type DisasmLine = {
|
|
|
|
line:string,
|
2018-08-28 12:28:53 +00:00
|
|
|
nbytes:number,
|
|
|
|
isaddr:boolean
|
2018-08-22 18:50:10 +00:00
|
|
|
};
|
2018-07-11 15:17:37 +00:00
|
|
|
|
2018-09-17 20:09:09 +00:00
|
|
|
export type SymbolMap = {[ident:string]:number};
|
|
|
|
export type AddrSymbolMap = {[address:number]:string};
|
|
|
|
|
|
|
|
export class DebugSymbols {
|
2019-03-08 01:00:12 +00:00
|
|
|
symbolmap : SymbolMap; // symbol -> address
|
2018-09-17 20:09:09 +00:00
|
|
|
addr2symbol : AddrSymbolMap; // address -> symbol
|
2018-11-22 12:39:06 +00:00
|
|
|
|
2018-09-17 20:09:09 +00:00
|
|
|
constructor(symbolmap : SymbolMap) {
|
|
|
|
this.symbolmap = symbolmap;
|
|
|
|
this.addr2symbol = invertMap(symbolmap);
|
2019-08-13 19:43:41 +00:00
|
|
|
//// TODO: shouldn't be necc.
|
2018-09-17 20:09:09 +00:00
|
|
|
if (!this.addr2symbol[0x0]) this.addr2symbol[0x0] = '__START__'; // needed for ...
|
|
|
|
this.addr2symbol[0x10000] = '__END__'; // ... dump memory to work
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-20 15:42:39 +00:00
|
|
|
export interface PlatformMetadata {
|
|
|
|
name : string; // TODO
|
|
|
|
}
|
|
|
|
|
2019-08-23 19:05:12 +00:00
|
|
|
export function isDebuggable(arg:any): arg is Debuggable {
|
|
|
|
return typeof arg.getDebugCategories === 'function';
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface Debuggable {
|
|
|
|
getDebugCategories?() : string[];
|
|
|
|
getDebugInfo?(category:string, state:EmuState) : string;
|
|
|
|
}
|
|
|
|
|
2018-07-08 04:58:11 +00:00
|
|
|
export interface Platform {
|
|
|
|
start() : void;
|
|
|
|
reset() : void;
|
|
|
|
isRunning() : boolean;
|
|
|
|
getToolForFilename(s:string) : string;
|
|
|
|
getDefaultExtension() : string;
|
|
|
|
getPresets() : Preset[];
|
|
|
|
pause() : void;
|
|
|
|
resume() : void;
|
2018-07-10 01:46:45 +00:00
|
|
|
loadROM(title:string, rom:any); // TODO: Uint8Array
|
2019-05-08 02:36:06 +00:00
|
|
|
loadBIOS?(title:string, rom:Uint8Array);
|
2019-08-20 15:42:39 +00:00
|
|
|
getMetadata?() : PlatformMetadata;
|
2018-08-26 02:00:45 +00:00
|
|
|
|
|
|
|
loadState?(state : EmuState) : void;
|
|
|
|
saveState?() : EmuState;
|
|
|
|
loadControlsState?(state : EmuControlsState) : void;
|
|
|
|
saveControlsState?() : EmuControlsState;
|
2018-11-22 12:39:06 +00:00
|
|
|
|
2018-08-27 13:28:31 +00:00
|
|
|
inspect?(ident:string) : string;
|
2018-07-11 15:17:37 +00:00
|
|
|
disassemble?(addr:number, readfn:(addr:number)=>number) : DisasmLine;
|
2018-07-08 04:58:11 +00:00
|
|
|
readAddress?(addr:number) : number;
|
2019-03-15 01:55:16 +00:00
|
|
|
readVRAMAddress?(addr:number) : number;
|
2018-07-08 04:58:11 +00:00
|
|
|
setFrameRate?(fps:number) : void;
|
|
|
|
getFrameRate?() : number;
|
2018-08-26 02:00:45 +00:00
|
|
|
|
2019-03-08 01:00:12 +00:00
|
|
|
setupDebug?(callback : BreakpointCallback) : void;
|
2018-07-08 04:58:11 +00:00
|
|
|
clearDebug?() : void;
|
|
|
|
step?() : void;
|
|
|
|
runToVsync?() : void;
|
|
|
|
runToPC?(pc:number) : void;
|
|
|
|
runUntilReturn?() : void;
|
|
|
|
stepBack?() : void;
|
2018-12-01 14:48:30 +00:00
|
|
|
runEval?(evalfunc : DebugEvalCondition) : void;
|
2019-04-07 01:47:42 +00:00
|
|
|
runToFrameClock?(clock : number) : void;
|
2018-11-22 12:39:06 +00:00
|
|
|
|
2018-07-08 04:58:11 +00:00
|
|
|
getOpcodeMetadata?(opcode:number, offset:number) : OpcodeMetadata; //TODO
|
|
|
|
getSP?() : number;
|
2019-08-23 14:37:30 +00:00
|
|
|
getPC?() : number;
|
2018-08-16 23:19:20 +00:00
|
|
|
getOriginPC?() : number;
|
2018-08-26 02:00:45 +00:00
|
|
|
newCodeAnalyzer?() : CodeAnalyzer;
|
2018-07-29 20:26:05 +00:00
|
|
|
|
2018-11-22 12:39:06 +00:00
|
|
|
|
2018-08-22 03:39:34 +00:00
|
|
|
setRecorder?(recorder : EmuRecorder) : void;
|
|
|
|
advance?(novideo? : boolean) : void;
|
2018-11-22 16:22:54 +00:00
|
|
|
showHelp?(tool:string, ident?:string) : void;
|
2018-11-26 11:12:45 +00:00
|
|
|
resize?() : void;
|
2018-11-22 12:39:06 +00:00
|
|
|
|
2019-03-03 16:32:25 +00:00
|
|
|
getRasterScanline?() : number;
|
2019-06-09 15:13:25 +00:00
|
|
|
setBreakpoint?(id : string, cond : DebugCondition);
|
|
|
|
clearBreakpoint?(id : string);
|
|
|
|
getCPUState?() : CpuState;
|
2019-03-03 16:32:25 +00:00
|
|
|
|
2018-09-17 20:09:09 +00:00
|
|
|
debugSymbols? : DebugSymbols;
|
2019-08-24 15:28:04 +00:00
|
|
|
|
|
|
|
startProbing?() : ProbeRecorder;
|
|
|
|
stopProbing?() : void;
|
2018-07-08 04:58:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface Preset {
|
|
|
|
id : string;
|
|
|
|
name : string;
|
|
|
|
chapter? : number;
|
|
|
|
title? : string;
|
|
|
|
}
|
|
|
|
|
2018-07-11 15:17:37 +00:00
|
|
|
export interface MemoryBus {
|
|
|
|
read : (address:number) => number;
|
|
|
|
write : (address:number, value:number) => void;
|
2019-05-27 01:54:37 +00:00
|
|
|
contend?: (address:number, cycles:number) => number;
|
|
|
|
isContended?: (address:number) => boolean;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
|
2018-12-01 14:48:30 +00:00
|
|
|
export type DebugCondition = () => boolean;
|
|
|
|
export type DebugEvalCondition = (c:CpuState) => boolean;
|
|
|
|
export type BreakpointCallback = (s:EmuState) => void;
|
2019-03-08 01:00:12 +00:00
|
|
|
// for composite breakpoints w/ single debug function
|
|
|
|
export class BreakpointList {
|
|
|
|
id2bp : {[id:string] : Breakpoint} = {};
|
|
|
|
getDebugCondition() : DebugCondition {
|
|
|
|
if (Object.keys(this.id2bp).length == 0) {
|
|
|
|
return null; // no breakpoints
|
|
|
|
} else {
|
|
|
|
// evaluate all breakpoints
|
|
|
|
return () => {
|
|
|
|
var result = false;
|
|
|
|
for (var id in this.id2bp)
|
|
|
|
if (this.id2bp[id].cond())
|
|
|
|
result = true;
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
export type Breakpoint = {cond:DebugCondition};
|
2018-07-11 15:17:37 +00:00
|
|
|
|
2018-08-22 03:39:34 +00:00
|
|
|
export interface EmuRecorder {
|
|
|
|
frameRequested() : boolean;
|
2018-08-22 18:50:10 +00:00
|
|
|
recordFrame(state : EmuState);
|
2018-08-22 03:39:34 +00:00
|
|
|
}
|
|
|
|
|
2019-03-03 16:32:25 +00:00
|
|
|
export interface ProfilerScanline {
|
|
|
|
start,end : number; // start/end frameindex
|
|
|
|
}
|
|
|
|
export interface ProfilerFrame {
|
|
|
|
iptab : Uint32Array; // array of IPs
|
|
|
|
lines : ProfilerScanline[];
|
|
|
|
}
|
|
|
|
export interface ProfilerOutput {
|
|
|
|
frame : ProfilerFrame;
|
|
|
|
}
|
2019-06-09 15:13:25 +00:00
|
|
|
export interface EmuProfiler {
|
|
|
|
start() : ProfilerOutput;
|
|
|
|
stop();
|
2019-08-13 19:43:41 +00:00
|
|
|
// TODO?
|
|
|
|
logRead(a : number);
|
|
|
|
logWrite(a : number);
|
|
|
|
logInterrupt(a : number);
|
2019-06-09 15:13:25 +00:00
|
|
|
}
|
2019-03-03 16:32:25 +00:00
|
|
|
|
2018-07-11 15:17:37 +00:00
|
|
|
/////
|
|
|
|
|
2018-08-27 13:28:31 +00:00
|
|
|
export abstract class BasePlatform {
|
|
|
|
recorder : EmuRecorder = null;
|
2019-08-13 19:43:41 +00:00
|
|
|
profiler : EmuProfiler = null;
|
2018-09-17 20:09:09 +00:00
|
|
|
debugSymbols : DebugSymbols;
|
2018-08-27 13:28:31 +00:00
|
|
|
|
|
|
|
abstract loadState(state : EmuState) : void;
|
|
|
|
abstract saveState() : EmuState;
|
|
|
|
abstract pause() : void;
|
|
|
|
abstract resume() : void;
|
2018-08-29 12:24:13 +00:00
|
|
|
abstract advance(novideo? : boolean) : void;
|
2018-08-27 13:28:31 +00:00
|
|
|
|
|
|
|
setRecorder(recorder : EmuRecorder) : void {
|
|
|
|
this.recorder = recorder;
|
|
|
|
}
|
|
|
|
updateRecorder() {
|
|
|
|
// are we recording and do we need to save a frame?
|
|
|
|
if (this.recorder && (<Platform><any>this).isRunning() && this.recorder.frameRequested()) {
|
|
|
|
this.recorder.recordFrame(this.saveState());
|
|
|
|
}
|
|
|
|
}
|
2019-03-08 01:00:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export abstract class BaseDebugPlatform extends BasePlatform {
|
|
|
|
onBreakpointHit : BreakpointCallback;
|
|
|
|
debugCallback : DebugCondition;
|
|
|
|
debugSavedState : EmuState = null;
|
|
|
|
debugBreakState : EmuState = null;
|
|
|
|
debugTargetClock : number = 0;
|
|
|
|
debugClock : number = 0;
|
|
|
|
breakpoints : BreakpointList = new BreakpointList();
|
|
|
|
|
|
|
|
abstract getCPUState() : CpuState;
|
|
|
|
abstract readAddress(addr:number) : number;
|
|
|
|
|
|
|
|
setBreakpoint(id : string, cond : DebugCondition) {
|
|
|
|
if (cond) {
|
|
|
|
this.breakpoints.id2bp[id] = {cond:cond};
|
|
|
|
this.restartDebugging();
|
|
|
|
} else {
|
|
|
|
this.clearBreakpoint(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
clearBreakpoint(id : string) {
|
|
|
|
delete this.breakpoints.id2bp[id];
|
|
|
|
}
|
|
|
|
getDebugCallback() : DebugCondition {
|
|
|
|
return this.breakpoints.getDebugCondition();
|
2018-11-22 12:39:06 +00:00
|
|
|
}
|
2019-03-08 01:00:12 +00:00
|
|
|
setupDebug(callback : BreakpointCallback) : void {
|
2018-07-11 15:17:37 +00:00
|
|
|
this.onBreakpointHit = callback;
|
|
|
|
}
|
|
|
|
clearDebug() {
|
|
|
|
this.debugSavedState = null;
|
|
|
|
this.debugBreakState = null;
|
2018-08-17 02:45:59 +00:00
|
|
|
this.debugTargetClock = -1;
|
2018-07-11 15:17:37 +00:00
|
|
|
this.debugClock = 0;
|
|
|
|
this.onBreakpointHit = null;
|
2019-03-08 01:00:12 +00:00
|
|
|
this.clearBreakpoint('debug');
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
setDebugCondition(debugCond : DebugCondition) {
|
2019-03-08 01:00:12 +00:00
|
|
|
this.setBreakpoint('debug', debugCond);
|
|
|
|
}
|
|
|
|
restartDebugging() {
|
2018-07-11 15:17:37 +00:00
|
|
|
if (this.debugSavedState) {
|
|
|
|
this.loadState(this.debugSavedState);
|
|
|
|
} else {
|
|
|
|
this.debugSavedState = this.saveState();
|
|
|
|
}
|
|
|
|
this.debugClock = 0;
|
2019-03-08 01:00:12 +00:00
|
|
|
this.debugCallback = this.getDebugCallback();
|
2018-07-11 15:17:37 +00:00
|
|
|
this.debugBreakState = null;
|
|
|
|
this.resume();
|
|
|
|
}
|
2018-08-23 20:02:13 +00:00
|
|
|
preFrame() {
|
|
|
|
}
|
|
|
|
postFrame() {
|
2019-08-26 22:14:50 +00:00
|
|
|
if (this.debugCallback) {
|
|
|
|
if (this.debugBreakState) {
|
|
|
|
// reload debug state at end of frame after breakpoint
|
|
|
|
this.loadState(this.debugBreakState);
|
|
|
|
} else {
|
|
|
|
// save state every frame and rewind debug clocks
|
|
|
|
this.debugSavedState = this.saveState();
|
|
|
|
this.debugTargetClock -= this.debugClock;
|
|
|
|
this.debugClock = 0;
|
|
|
|
}
|
|
|
|
}
|
2018-08-23 20:02:13 +00:00
|
|
|
}
|
2019-06-07 17:03:30 +00:00
|
|
|
pollControls() {
|
|
|
|
}
|
2018-08-23 20:02:13 +00:00
|
|
|
nextFrame(novideo : boolean) {
|
2019-06-07 17:03:30 +00:00
|
|
|
this.pollControls();
|
|
|
|
this.updateRecorder();
|
2018-08-23 20:02:13 +00:00
|
|
|
this.preFrame();
|
|
|
|
this.advance(novideo);
|
|
|
|
this.postFrame();
|
|
|
|
}
|
2019-08-26 22:14:50 +00:00
|
|
|
// default debugging
|
|
|
|
abstract getSP() : number;
|
|
|
|
abstract getPC() : number;
|
|
|
|
abstract isStable() : boolean;
|
2018-07-11 15:17:37 +00:00
|
|
|
|
2018-07-27 17:39:09 +00:00
|
|
|
evalDebugCondition() {
|
2019-03-08 01:00:12 +00:00
|
|
|
if (this.debugCallback && !this.debugBreakState) {
|
|
|
|
this.debugCallback();
|
2018-07-27 17:39:09 +00:00
|
|
|
}
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2019-08-26 22:14:50 +00:00
|
|
|
wasBreakpointHit() : boolean {
|
|
|
|
return this.debugBreakState != null;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
breakpointHit(targetClock : number) {
|
2019-08-26 22:14:50 +00:00
|
|
|
console.log(this.debugTargetClock, targetClock, this.debugClock, this.isStable());
|
2018-07-11 15:17:37 +00:00
|
|
|
this.debugTargetClock = targetClock;
|
|
|
|
this.debugBreakState = this.saveState();
|
|
|
|
console.log("Breakpoint at clk", this.debugClock, "PC", this.debugBreakState.c.PC.toString(16));
|
|
|
|
this.pause();
|
|
|
|
if (this.onBreakpointHit) {
|
|
|
|
this.onBreakpointHit(this.debugBreakState);
|
|
|
|
}
|
|
|
|
}
|
2018-07-27 17:39:09 +00:00
|
|
|
runEval(evalfunc : DebugEvalCondition) {
|
|
|
|
this.setDebugCondition( () => {
|
2019-08-26 22:14:50 +00:00
|
|
|
if (++this.debugClock >= this.debugTargetClock && this.isStable()) {
|
2018-07-27 17:39:09 +00:00
|
|
|
var cpuState = this.getCPUState();
|
|
|
|
if (evalfunc(cpuState)) {
|
2019-08-26 22:14:50 +00:00
|
|
|
this.breakpointHit(this.debugClock);
|
2018-07-27 17:39:09 +00:00
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2019-08-26 22:14:50 +00:00
|
|
|
runUntilReturn() {
|
|
|
|
var SP0 = this.getSP();
|
|
|
|
this.runEval( (c:CpuState) : boolean => {
|
|
|
|
return c.SP > SP0;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
runToFrameClock(clock : number) : void {
|
2019-04-07 01:47:42 +00:00
|
|
|
this.restartDebugging();
|
|
|
|
this.debugTargetClock = clock;
|
2019-08-26 22:14:50 +00:00
|
|
|
this.runEval(() : boolean => { return true; });
|
2019-04-07 01:47:42 +00:00
|
|
|
}
|
2018-07-11 15:17:37 +00:00
|
|
|
step() {
|
2019-08-26 22:14:50 +00:00
|
|
|
this.runToFrameClock(this.debugClock+1);
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
stepBack() {
|
|
|
|
var prevState;
|
|
|
|
var prevClock;
|
2019-08-26 22:14:50 +00:00
|
|
|
var clock0 = this.debugTargetClock;
|
|
|
|
this.restartDebugging();
|
|
|
|
this.debugTargetClock = clock0 - 25; // TODO: depends on CPU
|
|
|
|
this.runEval( (c:CpuState) : boolean => {
|
|
|
|
if (this.debugClock < clock0) {
|
|
|
|
prevState = this.saveState();
|
|
|
|
prevClock = this.debugClock;
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
if (prevState) {
|
|
|
|
this.loadState(prevState);
|
|
|
|
this.debugClock = prevClock;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2019-08-26 22:14:50 +00:00
|
|
|
return true;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2019-08-26 22:14:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
////// 6502
|
|
|
|
|
|
|
|
export function getToolForFilename_6502(fn:string) : string {
|
|
|
|
if (fn.endsWith(".pla")) return "plasm";
|
|
|
|
if (fn.endsWith(".c")) return "cc65";
|
|
|
|
if (fn.endsWith(".h")) return "cc65";
|
|
|
|
if (fn.endsWith(".s")) return "ca65";
|
|
|
|
if (fn.endsWith(".ca65")) return "ca65";
|
|
|
|
if (fn.endsWith(".dasm")) return "dasm";
|
|
|
|
if (fn.endsWith(".acme")) return "acme";
|
|
|
|
return "dasm"; // .a
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: can merge w/ Z80?
|
|
|
|
export abstract class Base6502Platform extends BaseDebugPlatform {
|
|
|
|
|
|
|
|
// some platforms store their PC one byte before or after the first opcode
|
|
|
|
// so we correct when saving and loading from state
|
|
|
|
debugPCDelta = -1;
|
|
|
|
fixPC(c) { c.PC = (c.PC + this.debugPCDelta) & 0xffff; return c; }
|
|
|
|
unfixPC(c) { c.PC = (c.PC - this.debugPCDelta) & 0xffff; return c;}
|
|
|
|
getSP() { return this.getCPUState().SP };
|
|
|
|
getPC() { return this.getCPUState().PC };
|
|
|
|
isStable() { return !this.getCPUState()['T']; }
|
2018-07-27 17:39:09 +00:00
|
|
|
|
|
|
|
newCPU(membus : MemoryBus) {
|
|
|
|
var cpu = new jt.M6502();
|
|
|
|
cpu.connectBus(membus);
|
|
|
|
return cpu;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2018-07-27 17:39:09 +00:00
|
|
|
|
|
|
|
getOpcodeMetadata(opcode, offset) {
|
2018-08-02 15:00:47 +00:00
|
|
|
return getOpcodeMetadata_6502(opcode, offset);
|
2018-07-27 17:39:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getOriginPC() : number {
|
|
|
|
return (this.readAddress(0xfffc) | (this.readAddress(0xfffd) << 8)) & 0xffff;
|
|
|
|
}
|
|
|
|
|
2018-07-11 15:17:37 +00:00
|
|
|
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
|
|
|
return disassemble6502(pc, read(pc), read(pc+1), read(pc+2));
|
|
|
|
}
|
|
|
|
getToolForFilename = getToolForFilename_6502;
|
|
|
|
getDefaultExtension() { return ".a"; };
|
2018-11-22 12:39:06 +00:00
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
getDebugCategories() {
|
|
|
|
return ['CPU','ZPRAM','Stack'];
|
|
|
|
}
|
|
|
|
getDebugInfo(category:string, state:EmuState) : string {
|
|
|
|
switch (category) {
|
|
|
|
case 'CPU': return cpuStateToLongString_6502(state.c);
|
2019-08-23 14:37:30 +00:00
|
|
|
case 'ZPRAM': return dumpRAM(state.b||state.ram, 0x0, 0x100);
|
|
|
|
case 'Stack': return dumpStackToString(<Platform><any>this, state.b||state.ram, 0x100, 0x1ff, 0x100+state.c.SP, 0x20);
|
2018-08-16 23:19:20 +00:00
|
|
|
}
|
2018-07-29 20:26:05 +00:00
|
|
|
}
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
export function cpuStateToLongString_6502(c) : string {
|
2018-07-11 15:17:37 +00:00
|
|
|
function decodeFlags(c) {
|
|
|
|
var s = "";
|
|
|
|
s += c.N ? " N" : " -";
|
|
|
|
s += c.V ? " V" : " -";
|
|
|
|
s += c.D ? " D" : " -";
|
|
|
|
s += c.Z ? " Z" : " -";
|
|
|
|
s += c.C ? " C" : " -";
|
2018-07-30 15:51:57 +00:00
|
|
|
s += c.I ? " I" : " -";
|
2018-07-11 15:17:37 +00:00
|
|
|
return s;
|
|
|
|
}
|
|
|
|
return "PC " + hex(c.PC,4) + " " + decodeFlags(c) + "\n"
|
|
|
|
+ " A " + hex(c.A) + " " + (c.R ? "" : "BUSY") + "\n"
|
|
|
|
+ " X " + hex(c.X) + "\n"
|
|
|
|
+ " Y " + hex(c.Y) + " " + "SP " + hex(c.SP) + "\n";
|
|
|
|
}
|
|
|
|
|
2018-08-02 17:08:37 +00:00
|
|
|
var OPMETA_6502 = {
|
|
|
|
cycletime: [
|
|
|
|
7, 6, 0, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6, 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 0, 7, 4, 4, 7, 7, 6, 6, 0, 8, 3, 3, 5, 5, 4, 2, 2, 2, 4, 4, 6, 6, 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 0, 7, 4, 4, 7, 7, 6, 6, 0, 8, 3, 3, 5, 5, 3, 2, 2, 2, 3, 4, 6, 6, 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 0, 7, 4, 4, 7, 7, 6, 6, 0, 8, 3, 3, 5, 5, 4, 2, 2, 2, 5, 4, 6, 6, 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 0, 7, 4, 4, 7, 7, 0, 6, 0, 6, 3, 3, 3, 3, 2, 0, 2, 0, 4, 4, 4, 4, 2, 6, 0, 0, 4, 4, 4, 4, 2, 5, 2, 0, 0, 5, 0, 0, 2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 0, 4, 4, 4, 4, 2, 5, 0, 5, 4, 4, 4, 4, 2, 4, 2, 0, 4, 4, 4, 4, 2, 6, 0, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 3, 6, 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 0, 7, 4, 4, 7, 7, 2, 6, 0, 8, 3, 3, 5, 5, 2, 2, 2, 0, 4, 4, 6, 6, 2, 5, 0, 8, 4, 4, 6, 6, 2, 4, 0, 7, 4, 4, 7, 7
|
|
|
|
],
|
|
|
|
extracycles: [
|
|
|
|
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1
|
|
|
|
],
|
|
|
|
insnlengths: [
|
|
|
|
1, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 0, 3, 3, 3, 3, 3, 3, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 0, 3, 3, 3, 3, 3, 1, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 0, 3, 3, 3, 3, 3, 1, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 0, 3, 3, 3, 3, 3, 0, 2, 0, 2, 2, 2, 2, 2, 1, 0, 1, 0, 3, 3, 3, 3, 2, 2, 0, 0, 2, 2, 2, 3, 1, 3, 1, 0, 0, 3, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 0, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 1, 0, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 2, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 0, 3, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 2, 1, 0, 3, 3, 3, 3, 2, 2, 0, 2, 2, 2, 2, 2, 1, 3, 0, 3, 3, 3, 3, 3
|
|
|
|
],
|
|
|
|
validinsns: [
|
|
|
|
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 0, 3, 3, 0, 2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, 3, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, 1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, 1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, 0, 2, 0, 0, 2, 2, 2, 0, 1, 0, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 0, 3, 0, 0, 2, 2, 2, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0, 2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, 2, 2, 0, 0, 0, 2, 2, 0, 1, 3, 0, 0, 0, 3, 3, 0
|
|
|
|
],
|
|
|
|
}
|
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
export function getOpcodeMetadata_6502(opcode, address) {
|
2018-08-02 15:00:47 +00:00
|
|
|
// TODO: more intelligent maximum cycles
|
2018-08-18 00:46:55 +00:00
|
|
|
// TODO: must always be new object, b/c we might modify it
|
2018-08-02 15:00:47 +00:00
|
|
|
return {
|
|
|
|
opcode:opcode,
|
2018-08-02 17:08:37 +00:00
|
|
|
minCycles:OPMETA_6502.cycletime[opcode],
|
|
|
|
maxCycles:OPMETA_6502.cycletime[opcode] + OPMETA_6502.extracycles[opcode],
|
|
|
|
insnlength:OPMETA_6502.insnlengths[opcode]
|
2018-08-02 15:00:47 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-07-11 15:17:37 +00:00
|
|
|
////// Z80
|
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
export function cpuStateToLongString_Z80(c) {
|
2018-07-11 15:17:37 +00:00
|
|
|
function decodeFlags(flags) {
|
2018-08-29 17:43:46 +00:00
|
|
|
return printFlags(flags, ["S","Z",,"H",,"V","N","C"], true);
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2019-08-25 19:09:21 +00:00
|
|
|
return "PC " + hex(c.PC,4) + " " + decodeFlags(c.AF) + " " + (c.iff1?"I":"-") + (c.iff2?"I":"-") + "\n"
|
2018-07-11 15:17:37 +00:00
|
|
|
+ "SP " + hex(c.SP,4) + " IR " + hex(c.IR,4) + "\n"
|
|
|
|
+ "IX " + hex(c.IX,4) + " IY " + hex(c.IY,4) + "\n"
|
|
|
|
+ "AF " + hex(c.AF,4) + " BC " + hex(c.BC,4) + "\n"
|
|
|
|
+ "DE " + hex(c.DE,4) + " HL " + hex(c.HL,4) + "\n"
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
2018-07-12 04:59:35 +00:00
|
|
|
export abstract class BaseZ80Platform extends BaseDebugPlatform {
|
2018-07-11 15:17:37 +00:00
|
|
|
|
|
|
|
_cpu;
|
2019-08-25 20:20:12 +00:00
|
|
|
waitCycles : number = 0;
|
2018-07-11 15:17:37 +00:00
|
|
|
|
2019-08-25 20:20:12 +00:00
|
|
|
newCPU(membus : MemoryBus, iobus : MemoryBus) {
|
|
|
|
this._cpu = new Z80();
|
|
|
|
this._cpu.connectMemoryBus(membus);
|
|
|
|
this._cpu.connectIOBus(iobus);
|
|
|
|
return this._cpu;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getPC() { return this._cpu.getPC(); }
|
|
|
|
getSP() { return this._cpu.getSP(); }
|
2019-08-26 22:14:50 +00:00
|
|
|
isStable() { return true; }
|
2018-07-11 15:17:37 +00:00
|
|
|
|
|
|
|
// TODO: refactor other parts into here
|
2018-12-02 15:13:59 +00:00
|
|
|
runCPU(cpu, cycles:number) {
|
2018-07-11 15:17:37 +00:00
|
|
|
this._cpu = cpu; // TODO?
|
2019-08-25 20:20:12 +00:00
|
|
|
this.waitCycles = 0; // TODO: needs to spill over betwenn calls
|
2018-07-11 15:17:37 +00:00
|
|
|
if (this.wasBreakpointHit())
|
|
|
|
return 0;
|
|
|
|
var debugCond = this.getDebugCallback();
|
2019-08-25 20:20:12 +00:00
|
|
|
var n = 0;
|
|
|
|
this.waitCycles += cycles;
|
|
|
|
while (this.waitCycles > 0) {
|
|
|
|
if (debugCond && debugCond()) {
|
|
|
|
debugCond = null;
|
|
|
|
break;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2019-08-25 20:20:12 +00:00
|
|
|
var cyc = cpu.advanceInsn();
|
|
|
|
n += cyc;
|
|
|
|
this.waitCycles -= cyc;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2019-08-25 20:20:12 +00:00
|
|
|
return n;
|
2018-09-14 13:10:41 +00:00
|
|
|
}
|
2019-08-26 22:14:50 +00:00
|
|
|
|
2018-07-11 15:17:37 +00:00
|
|
|
getToolForFilename = getToolForFilename_z80;
|
|
|
|
getDefaultExtension() { return ".c"; };
|
2019-06-03 14:08:29 +00:00
|
|
|
// TODO: Z80 opcode metadata
|
2018-07-11 15:17:37 +00:00
|
|
|
//this.getOpcodeMetadata = function() { }
|
2018-07-29 20:26:05 +00:00
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
getDebugCategories() {
|
2018-08-25 02:55:16 +00:00
|
|
|
return ['CPU','Stack'];
|
2018-08-16 23:19:20 +00:00
|
|
|
}
|
|
|
|
getDebugInfo(category:string, state:EmuState) : string {
|
|
|
|
switch (category) {
|
|
|
|
case 'CPU': return cpuStateToLongString_Z80(state.c);
|
2018-08-25 02:55:16 +00:00
|
|
|
case 'Stack': {
|
2018-09-17 20:09:09 +00:00
|
|
|
var sp = (state.c.SP-1) & 0xffff;
|
2018-09-05 02:28:12 +00:00
|
|
|
var start = sp & 0xff00;
|
2018-08-25 02:55:16 +00:00
|
|
|
var end = start + 0xff;
|
|
|
|
if (sp == 0) sp = 0x10000;
|
2018-09-17 20:09:09 +00:00
|
|
|
console.log(sp,start,end);
|
2018-08-25 02:55:16 +00:00
|
|
|
return dumpStackToString(<Platform><any>this, [], start, end, sp, 0xcd);
|
|
|
|
}
|
2018-08-16 23:19:20 +00:00
|
|
|
}
|
2018-07-29 20:26:05 +00:00
|
|
|
}
|
2018-08-27 21:31:49 +00:00
|
|
|
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
|
|
|
return disassembleZ80(pc, read(pc), read(pc+1), read(pc+2), read(pc+3));
|
|
|
|
}
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
|
2019-03-21 01:38:53 +00:00
|
|
|
export function getToolForFilename_z80(fn) : string {
|
2018-07-11 15:17:37 +00:00
|
|
|
if (fn.endsWith(".c")) return "sdcc";
|
2018-11-24 21:08:33 +00:00
|
|
|
if (fn.endsWith(".h")) return "sdcc";
|
2018-07-11 15:17:37 +00:00
|
|
|
if (fn.endsWith(".s")) return "sdasz80";
|
|
|
|
if (fn.endsWith(".ns")) return "naken";
|
|
|
|
if (fn.endsWith(".scc")) return "sccz80";
|
2018-08-28 17:59:12 +00:00
|
|
|
if (fn.endsWith(".z")) return "zmac";
|
2018-08-28 14:10:59 +00:00
|
|
|
return "zmac";
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
|
2018-08-16 23:19:20 +00:00
|
|
|
////// 6809
|
|
|
|
|
|
|
|
export function cpuStateToLongString_6809(c) {
|
|
|
|
function decodeFlags(flags) {
|
2018-08-29 17:43:46 +00:00
|
|
|
return printFlags(flags, ["E","F","H","I", "N","Z","V","C"], true);
|
2018-08-16 23:19:20 +00:00
|
|
|
}
|
|
|
|
return "PC " + hex(c.PC,4) + " " + decodeFlags(c.CC) + "\n"
|
|
|
|
+ "SP " + hex(c.SP,4) + "\n"
|
|
|
|
+ " A " + hex(c.A,2) + "\n"
|
|
|
|
+ " B " + hex(c.B,2) + "\n"
|
|
|
|
+ " X " + hex(c.X,4) + "\n"
|
|
|
|
+ " Y " + hex(c.Y,4) + "\n"
|
|
|
|
+ " U " + hex(c.U,4) + "\n"
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
export abstract class Base6809Platform extends BaseZ80Platform {
|
|
|
|
|
|
|
|
newCPU(membus : MemoryBus) {
|
|
|
|
var cpu = new CPU6809();
|
|
|
|
cpu.init(membus.write, membus.read, 0);
|
|
|
|
return cpu;
|
|
|
|
}
|
|
|
|
|
2019-08-11 14:23:56 +00:00
|
|
|
getPC() { return this._cpu.PC; }
|
|
|
|
getSP() { return this._cpu.SP; }
|
|
|
|
|
2019-04-07 01:47:42 +00:00
|
|
|
cpuStateToLongString(c:CpuState) {
|
2018-08-16 23:19:20 +00:00
|
|
|
return cpuStateToLongString_6809(c);
|
|
|
|
}
|
|
|
|
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
|
|
|
// TODO: don't create new CPU
|
|
|
|
return new CPU6809().disasm(read(pc), read(pc+1), read(pc+2), read(pc+3), read(pc+4), pc);
|
|
|
|
}
|
2019-03-21 01:38:53 +00:00
|
|
|
getDefaultExtension() : string { return ".asm"; };
|
2018-08-16 23:19:20 +00:00
|
|
|
//this.getOpcodeMetadata = function() { }
|
2019-03-21 01:38:53 +00:00
|
|
|
getToolForFilename = () => { return "xasm6809"; }
|
|
|
|
getDebugCategories() {
|
|
|
|
return ['CPU','Stack'];
|
|
|
|
}
|
|
|
|
getDebugInfo(category:string, state:EmuState) : string {
|
|
|
|
switch (category) {
|
|
|
|
case 'CPU': return cpuStateToLongString_6809(state.c);
|
|
|
|
default: return super.getDebugInfo(category, state);
|
|
|
|
}
|
|
|
|
}
|
2018-08-16 23:19:20 +00:00
|
|
|
}
|
|
|
|
|
2018-07-11 15:17:37 +00:00
|
|
|
/// MAME SUPPORT
|
|
|
|
|
|
|
|
declare var FS, ENV, Module; // mame emscripten
|
|
|
|
|
2018-08-23 12:49:14 +00:00
|
|
|
export abstract class BaseMAMEPlatform {
|
|
|
|
|
2019-04-03 21:00:05 +00:00
|
|
|
loaded : boolean = false;
|
|
|
|
preinitted : boolean = false;
|
|
|
|
started : boolean = false;
|
|
|
|
romfn : string;
|
|
|
|
romdata : Uint8Array;
|
2018-08-23 12:49:14 +00:00
|
|
|
video;
|
|
|
|
running = false;
|
|
|
|
console_vars : {[varname:string]:string[]} = {};
|
2019-04-03 21:00:05 +00:00
|
|
|
console_varname : string;
|
|
|
|
initluavars : boolean = false;
|
|
|
|
luadebugscript : string;
|
2018-08-23 12:49:14 +00:00
|
|
|
js_lua_string;
|
|
|
|
onBreakpointHit;
|
2019-04-03 21:00:05 +00:00
|
|
|
mainElement : HTMLElement;
|
2018-11-22 12:39:06 +00:00
|
|
|
|
2018-08-23 12:49:14 +00:00
|
|
|
constructor(mainElement) {
|
|
|
|
this.mainElement = mainElement;
|
|
|
|
}
|
2018-07-11 15:17:37 +00:00
|
|
|
|
2018-08-23 12:49:14 +00:00
|
|
|
luareset() {
|
|
|
|
this.console_vars = {};
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// http://docs.mamedev.org/techspecs/luaengine.html
|
2018-08-23 12:49:14 +00:00
|
|
|
luacall(s) {
|
|
|
|
this.console_varname = null;
|
2018-07-11 15:17:37 +00:00
|
|
|
//Module.ccall('_Z13js_lua_stringPKc', 'void', ['string'], [s+""]);
|
2018-08-23 12:49:14 +00:00
|
|
|
if (!this.js_lua_string) this.js_lua_string = Module.cwrap('_Z13js_lua_stringPKc', 'void', ['string']);
|
|
|
|
this.js_lua_string(s || "");
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
|
2018-08-23 12:49:14 +00:00
|
|
|
pause() {
|
|
|
|
if (this.loaded && this.running) {
|
2018-07-11 15:17:37 +00:00
|
|
|
this.luacall('emu.pause()');
|
2018-08-23 12:49:14 +00:00
|
|
|
this.running = false;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-23 12:49:14 +00:00
|
|
|
resume() {
|
|
|
|
if (this.loaded && !this.running) { // TODO
|
2018-07-11 15:17:37 +00:00
|
|
|
this.luacall('emu.unpause()');
|
2018-08-23 12:49:14 +00:00
|
|
|
this.running = true;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-23 12:49:14 +00:00
|
|
|
reset() {
|
2018-09-21 12:39:15 +00:00
|
|
|
if (this.loaded) {
|
|
|
|
this.luacall('manager:machine():soft_reset()');
|
|
|
|
this.running = true;
|
|
|
|
this.initluavars = false;
|
|
|
|
}
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
|
2018-08-23 12:49:14 +00:00
|
|
|
isRunning() {
|
|
|
|
return this.running;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
|
2018-08-23 12:49:14 +00:00
|
|
|
bufferConsoleOutput(s) {
|
2019-08-16 09:53:49 +00:00
|
|
|
if (typeof s !== 'string') return;
|
2018-07-11 15:17:37 +00:00
|
|
|
if (s.startsWith(">>>")) {
|
2018-08-23 12:49:14 +00:00
|
|
|
this.console_varname = s.length > 3 ? s.slice(3) : null;
|
|
|
|
if (this.console_varname) this.console_vars[this.console_varname] = [];
|
|
|
|
} else if (this.console_varname) {
|
|
|
|
this.console_vars[this.console_varname].push(s);
|
|
|
|
if (this.console_varname == 'debug_stopped') {
|
|
|
|
var debugSaveState = this.preserveState();
|
|
|
|
this.pause();
|
|
|
|
if (this.onBreakpointHit) {
|
|
|
|
this.onBreakpointHit(debugSaveState);
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
console.log(s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-23 12:49:14 +00:00
|
|
|
startModule(mainElement, opts) {
|
2019-04-03 21:00:05 +00:00
|
|
|
this.started = true;
|
2018-08-23 12:49:14 +00:00
|
|
|
var romfn = this.romfn = this.romfn || opts.romfn;
|
|
|
|
var romdata = this.romdata = this.romdata || opts.romdata || new RAM(opts.romsize).mem;
|
2018-07-11 15:17:37 +00:00
|
|
|
// create canvas
|
2018-08-23 12:49:14 +00:00
|
|
|
var video = this.video = new RasterVideo(this.mainElement, opts.width, opts.height);
|
2018-07-11 15:17:37 +00:00
|
|
|
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('-cart', romfn);
|
|
|
|
window['JSMESS'] = {};
|
|
|
|
window['Module'] = {
|
|
|
|
arguments: modargs,
|
|
|
|
screenIsReadOnly: true,
|
2018-08-23 12:49:14 +00:00
|
|
|
print: this.bufferConsoleOutput,
|
2018-07-11 15:17:37 +00:00
|
|
|
canvas:video.canvas,
|
|
|
|
doNotCaptureKeyboard:true,
|
|
|
|
keyboardListeningElement:video.canvas,
|
2018-08-23 12:49:14 +00:00
|
|
|
preInit: () => {
|
2018-07-11 15:17:37 +00:00
|
|
|
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');
|
2019-04-03 21:00:05 +00:00
|
|
|
if (romfn) {
|
2018-07-11 15:17:37 +00:00
|
|
|
FS.writeFile(romfn, romdata, {encoding:'binary'});
|
2019-04-03 21:00:05 +00:00
|
|
|
}
|
2018-07-11 15:17:37 +00:00
|
|
|
//FS.writeFile('/debug.ini', 'debugger none\n', {encoding:'utf8'});
|
|
|
|
if (opts.preInit) {
|
|
|
|
opts.preInit(self);
|
|
|
|
}
|
2018-08-23 12:49:14 +00:00
|
|
|
this.preinitted = true;
|
2018-07-11 15:17:37 +00:00
|
|
|
},
|
|
|
|
preRun: [
|
2018-08-23 12:49:14 +00:00
|
|
|
() => {
|
|
|
|
$(video.canvas).click((e) =>{
|
2018-07-11 15:17:37 +00:00
|
|
|
video.canvas.focus();
|
|
|
|
});
|
2018-08-23 12:49:14 +00:00
|
|
|
this.loaded = true;
|
2018-07-11 15:17:37 +00:00
|
|
|
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) {
|
2018-08-23 12:49:14 +00:00
|
|
|
fetch_cfg = $.get('mame/cfg/' + opts.cfgfile, (data) => {
|
2018-07-11 15:17:37 +00:00
|
|
|
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";
|
2018-08-23 12:49:14 +00:00
|
|
|
oReq1.onload = (oEvent) => {
|
2018-07-11 15:17:37 +00:00
|
|
|
opts.biosdata = new Uint8Array(oReq1.response);
|
|
|
|
console.log("loaded " + opts.biosfile); // + " (" + oEvent.total + " bytes)");
|
|
|
|
fetch_bios.resolve();
|
|
|
|
};
|
|
|
|
oReq1.send();
|
|
|
|
} else {
|
|
|
|
fetch_bios.resolve();
|
|
|
|
}
|
|
|
|
// load debugger Lua script
|
2018-08-23 12:49:14 +00:00
|
|
|
fetch_lua = $.get('mame/debugger.lua', (data) => {
|
|
|
|
this.luadebugscript = data;
|
2018-07-11 15:17:37 +00:00
|
|
|
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";
|
2018-08-23 12:49:14 +00:00
|
|
|
oReq2.onload = (oEvent) => {
|
2018-07-11 15:17:37 +00:00
|
|
|
console.log("loaded WASM file");
|
|
|
|
window['Module'].wasmBinary = new Uint8Array(oReq2.response);
|
|
|
|
fetch_wasm.resolve();
|
|
|
|
};
|
|
|
|
oReq2.send();
|
|
|
|
}
|
|
|
|
// start loading script
|
2018-08-23 12:49:14 +00:00
|
|
|
$.when(fetch_lua, fetch_cfg, fetch_bios, fetch_wasm).done( () => {
|
2018-07-11 15:17:37 +00:00
|
|
|
var script = document.createElement('script');
|
|
|
|
script.src = 'mame/' + opts.jsfile;
|
|
|
|
document.getElementsByTagName('head')[0].appendChild(script);
|
|
|
|
console.log("created script element");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-08-23 12:49:14 +00:00
|
|
|
loadROMFile(data) {
|
|
|
|
this.romdata = data;
|
|
|
|
if (this.preinitted && this.romfn) {
|
|
|
|
FS.writeFile(this.romfn, data, {encoding:'binary'});
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-23 12:49:14 +00:00
|
|
|
loadRegion(region, data) {
|
2019-04-03 21:00:05 +00:00
|
|
|
if (this.loaded && data.length > 0) {
|
2018-08-23 12:49:14 +00:00
|
|
|
//this.luacall('cart=manager:machine().images["cart"]\nprint(cart:filename())\ncart:load("' + romfn + '")\n');
|
2018-07-11 15:17:37 +00:00
|
|
|
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?
|
|
|
|
}
|
2018-08-23 12:49:14 +00:00
|
|
|
this.luacall(s);
|
|
|
|
this.reset();
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-23 12:49:14 +00:00
|
|
|
preserveState() {
|
2018-07-11 15:17:37 +00:00
|
|
|
var state = {c:{}};
|
2018-08-23 12:49:14 +00:00
|
|
|
for (var k in this.console_vars) {
|
2018-07-11 15:17:37 +00:00
|
|
|
if (k.startsWith("cpu_")) {
|
2018-08-23 12:49:14 +00:00
|
|
|
var v = parseInt(this.console_vars[k][0]);
|
2018-07-11 15:17:37 +00:00
|
|
|
state.c[k.slice(4)] = v;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// TODO: memory?
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
2018-08-23 12:49:14 +00:00
|
|
|
initlua() {
|
|
|
|
if (!this.initluavars) {
|
|
|
|
this.luacall(this.luadebugscript);
|
|
|
|
this.luacall('mamedbg.init()')
|
|
|
|
this.initluavars = true;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-22 17:23:04 +00:00
|
|
|
// DEBUGGING SUPPORT
|
|
|
|
/*
|
|
|
|
saveState() {
|
|
|
|
this.luareset();
|
|
|
|
this.luacall('mamedbg.printstate()');
|
|
|
|
return this.preserveState();
|
|
|
|
}
|
2018-08-23 12:49:14 +00:00
|
|
|
readAddress(a) {
|
|
|
|
this.initlua();
|
|
|
|
this.luacall('print(">>>v"); print(mem:read_u8(' + a + '))');
|
|
|
|
return parseInt(this.console_vars.v[0]);
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2018-08-23 12:49:14 +00:00
|
|
|
clearDebug() {
|
|
|
|
this.onBreakpointHit = null;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2018-08-23 12:49:14 +00:00
|
|
|
getDebugCallback() {
|
|
|
|
return this.onBreakpointHit;// TODO?
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2018-08-23 12:49:14 +00:00
|
|
|
setupDebug(callback) {
|
2018-09-21 12:39:15 +00:00
|
|
|
if (this.loaded) { // TODO?
|
|
|
|
this.initlua();
|
|
|
|
this.luareset();
|
|
|
|
}
|
2018-08-23 12:49:14 +00:00
|
|
|
this.onBreakpointHit = callback;
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2018-08-23 12:49:14 +00:00
|
|
|
runToPC(pc) {
|
|
|
|
this.luacall('mamedbg.runTo(' + pc + ')');
|
|
|
|
this.resume();
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2018-08-23 12:49:14 +00:00
|
|
|
runToVsync() {
|
|
|
|
this.luacall('mamedbg.runToVsync()');
|
|
|
|
this.resume();
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2018-08-23 12:49:14 +00:00
|
|
|
runUntilReturn() {
|
|
|
|
this.luacall('mamedbg.runUntilReturn()');
|
|
|
|
this.resume();
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2018-08-23 12:49:14 +00:00
|
|
|
step() {
|
|
|
|
this.luacall('mamedbg.step()');
|
|
|
|
this.resume();
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
|
|
|
// TODO: other than z80
|
2018-08-23 12:49:14 +00:00
|
|
|
cpuStateToLongString(c) {
|
2018-07-11 15:17:37 +00:00
|
|
|
if (c.HL)
|
|
|
|
return cpuStateToLongString_Z80(c);
|
|
|
|
else
|
|
|
|
return null; // TODO
|
|
|
|
}
|
2019-08-22 17:23:04 +00:00
|
|
|
*/
|
2018-07-11 15:17:37 +00:00
|
|
|
}
|
2018-08-16 23:19:20 +00:00
|
|
|
|
2018-09-18 18:09:51 +00:00
|
|
|
export function dumpStackToString(platform:Platform, mem:Uint8Array|number[], start:number, end:number, sp:number, jsrop:number) : string {
|
2018-08-25 02:55:16 +00:00
|
|
|
var s = "";
|
|
|
|
var nraw = 0;
|
|
|
|
//s = dumpRAM(mem.slice(start,start+end+1), start, end-start+1);
|
|
|
|
function read(addr) {
|
|
|
|
if (addr < mem.length) return mem[addr];
|
|
|
|
else return platform.readAddress(addr);
|
|
|
|
}
|
|
|
|
while (sp < end) {
|
|
|
|
sp++;
|
|
|
|
// see if there's a JSR on the stack here
|
|
|
|
// TODO: make work with roms and memory maps
|
|
|
|
var addr = read(sp) + read(sp+1)*256;
|
2019-05-04 23:17:15 +00:00
|
|
|
var jsrofs = jsrop==0x20 ? -2 : -3; // 6502 vs Z80
|
2018-08-25 02:55:16 +00:00
|
|
|
var opcode = read(addr + jsrofs); // might be out of bounds
|
|
|
|
if (opcode == jsrop) { // JSR
|
|
|
|
s += "\n$" + hex(sp) + ": ";
|
2019-03-03 16:32:25 +00:00
|
|
|
s += hex(addr,4) + " " + lookupSymbol(platform, addr, true);
|
2018-08-25 02:55:16 +00:00
|
|
|
sp++;
|
|
|
|
nraw = 0;
|
|
|
|
} else {
|
|
|
|
if (nraw == 0)
|
|
|
|
s += "\n$" + hex(sp) + ": ";
|
2018-09-05 02:28:12 +00:00
|
|
|
s += hex(read(sp)) + " ";
|
2018-08-25 02:55:16 +00:00
|
|
|
if (++nraw == 8) nraw = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return s+"\n";
|
|
|
|
}
|
2018-11-30 01:36:08 +00:00
|
|
|
|
2019-08-20 12:28:59 +00:00
|
|
|
// TODO: slow, funky, uses global
|
2019-03-03 16:32:25 +00:00
|
|
|
export function lookupSymbol(platform:Platform, addr:number, extra:boolean) {
|
2018-11-30 01:36:08 +00:00
|
|
|
var start = addr;
|
|
|
|
var addr2symbol = platform.debugSymbols && platform.debugSymbols.addr2symbol;
|
|
|
|
while (addr2symbol && addr >= 0) {
|
|
|
|
var sym = addr2symbol[addr];
|
2019-03-03 16:32:25 +00:00
|
|
|
if (sym) { // return first symbol we find
|
|
|
|
var sym = addr2symbol[addr];
|
2019-08-20 12:28:59 +00:00
|
|
|
return extra ? (sym + " + $" + hex(start-addr)) : sym;
|
2018-11-30 01:36:08 +00:00
|
|
|
}
|
2019-08-24 15:28:04 +00:00
|
|
|
if (!extra) break;
|
2018-11-30 01:36:08 +00:00
|
|
|
addr--;
|
|
|
|
}
|
2019-03-03 16:32:25 +00:00
|
|
|
return "";
|
2018-11-30 01:36:08 +00:00
|
|
|
}
|
|
|
|
|
2019-08-25 17:01:07 +00:00
|
|
|
/// new Machine platform adapters
|
2019-08-23 14:37:30 +00:00
|
|
|
|
2019-08-23 22:52:59 +00:00
|
|
|
import { Bus, Resettable, FrameBased, VideoSource, SampledAudioSource, AcceptsROM, AcceptsKeyInput, SavesState, SavesInputState, HasCPU } from "./devices";
|
2019-08-26 16:58:42 +00:00
|
|
|
import { Probeable, RasterFrameBased, AcceptsPaddleInput } from "./devices";
|
2019-08-23 14:37:30 +00:00
|
|
|
import { SampledAudio } from "./audio";
|
2019-08-24 15:28:04 +00:00
|
|
|
import { ProbeRecorder } from "./recorder";
|
2019-08-23 14:37:30 +00:00
|
|
|
|
2019-08-23 22:52:59 +00:00
|
|
|
interface Machine extends Bus, Resettable, FrameBased, AcceptsROM, HasCPU, SavesState<EmuState>, SavesInputState<any> {
|
2019-08-23 14:37:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function hasVideo(arg:any): arg is VideoSource {
|
|
|
|
return typeof arg.connectVideo === 'function';
|
|
|
|
}
|
|
|
|
function hasAudio(arg:any): arg is SampledAudioSource {
|
|
|
|
return typeof arg.connectAudio === 'function';
|
|
|
|
}
|
2019-08-23 22:52:59 +00:00
|
|
|
function hasKeyInput(arg:any): arg is AcceptsKeyInput {
|
|
|
|
return typeof arg.setKeyInput === 'function';
|
|
|
|
}
|
2019-08-26 16:58:42 +00:00
|
|
|
function hasPaddleInput(arg:any): arg is AcceptsPaddleInput {
|
|
|
|
return typeof arg.setPaddleInput === 'function';
|
|
|
|
}
|
2019-08-23 22:52:59 +00:00
|
|
|
function isRaster(arg:any): arg is RasterFrameBased {
|
|
|
|
return typeof arg.getRasterY === 'function';
|
2019-08-23 14:37:30 +00:00
|
|
|
}
|
2019-08-24 15:28:04 +00:00
|
|
|
function hasProbe(arg:any): arg is Probeable {
|
|
|
|
return typeof arg.connectProbe == 'function';
|
|
|
|
}
|
2019-08-23 14:37:30 +00:00
|
|
|
|
|
|
|
export abstract class BaseMachinePlatform<T extends Machine> extends BaseDebugPlatform implements Platform {
|
|
|
|
machine : T;
|
|
|
|
mainElement : HTMLElement;
|
|
|
|
timer : AnimationTimer;
|
|
|
|
video : RasterVideo;
|
|
|
|
audio : SampledAudio;
|
2019-08-23 22:34:40 +00:00
|
|
|
poller : ControllerPoller;
|
2019-08-24 15:28:04 +00:00
|
|
|
probeRecorder : ProbeRecorder;
|
|
|
|
startProbing;
|
|
|
|
stopProbing;
|
2019-08-23 14:37:30 +00:00
|
|
|
|
|
|
|
abstract newMachine() : T;
|
|
|
|
abstract getToolForFilename(s:string) : string;
|
|
|
|
abstract getDefaultExtension() : string;
|
|
|
|
abstract getPresets() : Preset[];
|
|
|
|
|
|
|
|
constructor(mainElement : HTMLElement) {
|
|
|
|
super();
|
|
|
|
this.mainElement = mainElement;
|
|
|
|
this.machine = this.newMachine();
|
|
|
|
}
|
|
|
|
|
|
|
|
reset() { this.machine.reset(); }
|
|
|
|
loadState(s) { this.machine.loadState(s); }
|
|
|
|
saveState() { return this.machine.saveState(); }
|
|
|
|
getSP() { return this.machine.cpu.getSP(); }
|
|
|
|
getPC() { return this.machine.cpu.getPC(); }
|
2019-08-26 22:14:50 +00:00
|
|
|
isStable() { return this.machine.cpu.isStable(); }
|
2019-08-23 14:37:30 +00:00
|
|
|
getCPUState() { return this.machine.cpu.saveState(); }
|
2019-08-23 22:52:59 +00:00
|
|
|
loadControlsState(s) { this.machine.loadControlsState(s); }
|
|
|
|
saveControlsState() { return this.machine.saveControlsState(); }
|
2019-08-23 14:37:30 +00:00
|
|
|
|
|
|
|
start() {
|
2019-08-24 15:28:04 +00:00
|
|
|
const m = this.machine;
|
2019-08-23 14:37:30 +00:00
|
|
|
this.timer = new AnimationTimer(60, this.nextFrame.bind(this));
|
|
|
|
if (hasVideo(m)) {
|
|
|
|
var vp = m.getVideoParams();
|
2019-08-24 02:32:10 +00:00
|
|
|
this.video = new RasterVideo(this.mainElement, vp.width, vp.height, {overscan:!!vp.overscan,rotate:vp.rotate|0});
|
2019-08-23 14:37:30 +00:00
|
|
|
this.video.create();
|
|
|
|
m.connectVideo(this.video.getFrameData());
|
|
|
|
}
|
|
|
|
if (hasAudio(m)) {
|
|
|
|
var ap = m.getAudioParams();
|
|
|
|
this.audio = new SampledAudio(ap.sampleRate);
|
|
|
|
this.audio.start();
|
|
|
|
m.connectAudio(this.audio);
|
|
|
|
}
|
2019-08-23 22:52:59 +00:00
|
|
|
if (hasKeyInput(m)) {
|
|
|
|
this.video.setKeyboardEvents(m.setKeyInput.bind(m));
|
|
|
|
this.poller = new ControllerPoller(m.setKeyInput.bind(m));
|
2019-08-23 14:37:30 +00:00
|
|
|
}
|
2019-08-26 16:58:42 +00:00
|
|
|
if (hasPaddleInput(m)) {
|
|
|
|
this.video.setupMouseEvents();
|
|
|
|
}
|
2019-08-24 15:28:04 +00:00
|
|
|
if (hasProbe(m)) {
|
|
|
|
this.probeRecorder = new ProbeRecorder(m);
|
|
|
|
this.startProbing = () => {
|
|
|
|
m.connectProbe(this.probeRecorder);
|
|
|
|
return this.probeRecorder;
|
|
|
|
};
|
|
|
|
this.stopProbing = () => {
|
|
|
|
m.connectProbe(null);
|
|
|
|
};
|
|
|
|
}
|
2019-08-23 14:37:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
loadROM(title, data) {
|
|
|
|
this.machine.loadROM(data);
|
|
|
|
this.reset();
|
|
|
|
}
|
|
|
|
|
2019-08-26 16:58:42 +00:00
|
|
|
pollControls() {
|
|
|
|
this.poller && this.poller.poll();
|
|
|
|
if (hasPaddleInput(this.machine)) {
|
|
|
|
this.machine.setPaddleInput(0, this.video.paddle_x);
|
|
|
|
this.machine.setPaddleInput(1, this.video.paddle_y);
|
|
|
|
}
|
|
|
|
}
|
2019-08-23 22:34:40 +00:00
|
|
|
|
2019-08-23 14:37:30 +00:00
|
|
|
advance(novideo:boolean) {
|
|
|
|
this.machine.advanceFrame(999999, this.getDebugCallback());
|
|
|
|
if (!novideo) this.video.updateFrame();
|
|
|
|
}
|
|
|
|
|
|
|
|
isRunning() {
|
|
|
|
return this.timer.isRunning();
|
|
|
|
}
|
|
|
|
|
|
|
|
resume() {
|
|
|
|
this.timer.start();
|
|
|
|
this.audio && this.audio.start();
|
|
|
|
}
|
|
|
|
|
|
|
|
pause() {
|
|
|
|
this.timer.stop();
|
|
|
|
this.audio && this.audio.stop();
|
|
|
|
}
|
|
|
|
|
2019-08-25 19:09:21 +00:00
|
|
|
// TODO: reset target clock counter
|
2019-08-23 22:52:59 +00:00
|
|
|
getRasterScanline() {
|
|
|
|
return isRaster(this.machine) && this.machine.getRasterY();
|
|
|
|
}
|
2019-08-23 14:37:30 +00:00
|
|
|
}
|
|
|
|
|
2019-08-23 22:52:59 +00:00
|
|
|
// TODO: move debug info into CPU?
|
|
|
|
|
2019-08-23 14:37:30 +00:00
|
|
|
export abstract class Base6502MachinePlatform<T extends Machine> extends BaseMachinePlatform<T> {
|
|
|
|
|
|
|
|
getOpcodeMetadata = getOpcodeMetadata_6502;
|
|
|
|
getToolForFilename = getToolForFilename_6502;
|
|
|
|
|
|
|
|
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
|
|
|
return disassemble6502(pc, read(pc), read(pc+1), read(pc+2));
|
|
|
|
}
|
|
|
|
getDebugCategories() {
|
2019-08-23 19:05:12 +00:00
|
|
|
if (isDebuggable(this.machine))
|
|
|
|
return this.machine.getDebugCategories();
|
|
|
|
else
|
|
|
|
return ['CPU','ZPRAM','Stack'];
|
2019-08-23 14:37:30 +00:00
|
|
|
}
|
|
|
|
getDebugInfo(category:string, state:EmuState) : string {
|
|
|
|
switch (category) {
|
|
|
|
case 'CPU': return cpuStateToLongString_6502(state.c);
|
|
|
|
case 'ZPRAM': return dumpRAM(state.b||state.ram, 0x0, 0x100);
|
|
|
|
case 'Stack': return dumpStackToString(<Platform><any>this, state.b||state.ram, 0x100, 0x1ff, 0x100+state.c.SP, 0x20);
|
2019-08-23 19:05:12 +00:00
|
|
|
default: return isDebuggable(this.machine) && this.machine.getDebugInfo(category, state);
|
2019-08-23 14:37:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2019-08-23 22:52:59 +00:00
|
|
|
|
|
|
|
export abstract class BaseZ80MachinePlatform<T extends Machine> extends BaseMachinePlatform<T> {
|
|
|
|
|
|
|
|
//getOpcodeMetadata = getOpcodeMetadata_z80;
|
|
|
|
getToolForFilename = getToolForFilename_z80;
|
|
|
|
|
|
|
|
getDebugCategories() {
|
2019-08-25 17:01:07 +00:00
|
|
|
if (isDebuggable(this.machine))
|
|
|
|
return this.machine.getDebugCategories();
|
|
|
|
else
|
|
|
|
return ['CPU','Stack'];
|
2019-08-23 22:52:59 +00:00
|
|
|
}
|
|
|
|
getDebugInfo(category:string, state:EmuState) : string {
|
|
|
|
switch (category) {
|
|
|
|
case 'CPU': return cpuStateToLongString_Z80(state.c);
|
|
|
|
case 'Stack': {
|
|
|
|
var sp = (state.c.SP-1) & 0xffff;
|
|
|
|
var start = sp & 0xff00;
|
|
|
|
var end = start + 0xff;
|
|
|
|
if (sp == 0) sp = 0x10000;
|
|
|
|
console.log(sp,start,end);
|
|
|
|
return dumpStackToString(<Platform><any>this, [], start, end, sp, 0xcd);
|
|
|
|
}
|
2019-08-25 19:09:21 +00:00
|
|
|
default: return isDebuggable(this.machine) && this.machine.getDebugInfo(category, state);
|
2019-08-23 22:52:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
disassemble(pc:number, read:(addr:number)=>number) : DisasmLine {
|
|
|
|
return disassembleZ80(pc, read(pc), read(pc+1), read(pc+2), read(pc+3));
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|