1
0
mirror of https://github.com/sehugg/8bitworkshop.git synced 2024-06-25 15:29:34 +00:00

made some platforms classy

This commit is contained in:
Steven Hugg 2018-08-23 08:49:14 -04:00
parent 86823c2c21
commit 0dd741f446
13 changed files with 513 additions and 417 deletions

View File

@ -55,6 +55,7 @@ TODO:
- state buffer/replay - state buffer/replay
- intro/help text for each platform - intro/help text for each platform
- make sure controls work with replay feature (we'll have to save control state every frame) - make sure controls work with replay feature (we'll have to save control state every frame)
- vscode/atom extension?
FYI: Image links for the books on http://8bitworkshop.com/ are broken FYI: Image links for the books on http://8bitworkshop.com/ are broken
On the website the additional grey spacing next to the program line numbers is not dynamically resized when the web browser window size is changed. Intentional? On the website the additional grey spacing next to the program line numbers is not dynamically resized when the web browser window size is changed. Intentional?

View File

@ -569,71 +569,75 @@ export abstract class Base6809Platform extends BaseZ80Platform {
declare var FS, ENV, Module; // mame emscripten declare var FS, ENV, Module; // mame emscripten
// TODO: make class // TODO: make class
export function BaseMAMEPlatform() { export abstract class BaseMAMEPlatform {
var self = this; loaded = false;
preinitted = false;
romfn;
romdata;
video;
preload_files;
running = false;
console_vars : {[varname:string]:string[]} = {};
console_varname;
initluavars = false;
luadebugscript;
js_lua_string;
onBreakpointHit;
mainElement;
var loaded = false; constructor(mainElement) {
var preinitted = false; this.mainElement = mainElement;
var romfn; }
var romdata;
var video;
var preload_files;
var running = false;
var console_vars : {[varname:string]:string[]} = {};
var console_varname;
var initluavars = false;
var luadebugscript;
var js_lua_string;
this.luareset = function() { luareset() {
console_vars = {}; this.console_vars = {};
} }
// http://docs.mamedev.org/techspecs/luaengine.html // http://docs.mamedev.org/techspecs/luaengine.html
this.luacall = function(s) { luacall(s) {
console_varname = null; this.console_varname = null;
//Module.ccall('_Z13js_lua_stringPKc', 'void', ['string'], [s+""]); //Module.ccall('_Z13js_lua_stringPKc', 'void', ['string'], [s+""]);
if (!js_lua_string) js_lua_string = Module.cwrap('_Z13js_lua_stringPKc', 'void', ['string']); if (!this.js_lua_string) this.js_lua_string = Module.cwrap('_Z13js_lua_stringPKc', 'void', ['string']);
js_lua_string(s || ""); this.js_lua_string(s || "");
} }
this.pause = function() { pause() {
if (loaded && running) { if (this.loaded && this.running) {
this.luacall('emu.pause()'); this.luacall('emu.pause()');
running = false; this.running = false;
} }
} }
this.resume = function() { resume() {
if (loaded && !running) { // TODO if (this.loaded && !this.running) { // TODO
this.luacall('emu.unpause()'); this.luacall('emu.unpause()');
running = true; this.running = true;
} }
} }
this.reset = function() { reset() {
this.luacall('manager:machine():soft_reset()'); this.luacall('manager:machine():soft_reset()');
running = true; this.running = true;
initluavars = false; this.initluavars = false;
} }
this.isRunning = function() { isRunning() {
return running; return this.running;
} }
function bufferConsoleOutput(s) { bufferConsoleOutput(s) {
if (!s) return; if (!s) return;
if (s.startsWith(">>>")) { if (s.startsWith(">>>")) {
console_varname = s.length > 3 ? s.slice(3) : null; this.console_varname = s.length > 3 ? s.slice(3) : null;
if (console_varname) console_vars[console_varname] = []; if (this.console_varname) this.console_vars[this.console_varname] = [];
} else if (console_varname) { } else if (this.console_varname) {
console_vars[console_varname].push(s); this.console_vars[this.console_varname].push(s);
if (console_varname == 'debug_stopped') { if (this.console_varname == 'debug_stopped') {
var debugSaveState = self.preserveState(); var debugSaveState = this.preserveState();
self.pause(); this.pause();
if (onBreakpointHit) { if (this.onBreakpointHit) {
onBreakpointHit(debugSaveState); this.onBreakpointHit(debugSaveState);
} }
} }
} else { } else {
@ -641,12 +645,11 @@ export function BaseMAMEPlatform() {
} }
} }
this.startModule = function(mainElement, opts) { startModule(mainElement, opts) {
romfn = opts.romfn; var romfn = this.romfn = this.romfn || opts.romfn;
if (opts.romdata) romdata = opts.romdata; var romdata = this.romdata = this.romdata || opts.romdata || new RAM(opts.romsize).mem;
if (!romdata) romdata = new RAM(opts.romsize).mem;
// create canvas // create canvas
video = new RasterVideo(mainElement, opts.width, opts.height); var video = this.video = new RasterVideo(this.mainElement, opts.width, opts.height);
video.create(); video.create();
$(video.canvas).attr('id','canvas'); $(video.canvas).attr('id','canvas');
// load asm.js module // load asm.js module
@ -662,11 +665,11 @@ export function BaseMAMEPlatform() {
window['Module'] = { window['Module'] = {
arguments: modargs, arguments: modargs,
screenIsReadOnly: true, screenIsReadOnly: true,
print: bufferConsoleOutput, print: this.bufferConsoleOutput,
canvas:video.canvas, canvas:video.canvas,
doNotCaptureKeyboard:true, doNotCaptureKeyboard:true,
keyboardListeningElement:video.canvas, keyboardListeningElement:video.canvas,
preInit: function () { preInit: () => {
console.log("loading FS"); console.log("loading FS");
ENV.SDL_EMSCRIPTEN_KEYBOARD_ELEMENT = 'canvas'; ENV.SDL_EMSCRIPTEN_KEYBOARD_ELEMENT = 'canvas';
if (opts.cfgfile) { if (opts.cfgfile) {
@ -685,14 +688,14 @@ export function BaseMAMEPlatform() {
if (opts.preInit) { if (opts.preInit) {
opts.preInit(self); opts.preInit(self);
} }
preinitted = true; this.preinitted = true;
}, },
preRun: [ preRun: [
function() { () => {
$(video.canvas).click(function(e) { $(video.canvas).click((e) =>{
video.canvas.focus(); video.canvas.focus();
}); });
loaded = true; this.loaded = true;
console.log("about to run..."); console.log("about to run...");
} }
] ]
@ -704,7 +707,7 @@ export function BaseMAMEPlatform() {
var fetch_wasm = $.Deferred(); var fetch_wasm = $.Deferred();
// fetch config file // fetch config file
if (opts.cfgfile) { if (opts.cfgfile) {
fetch_cfg = $.get('mame/cfg/' + opts.cfgfile, function(data) { fetch_cfg = $.get('mame/cfg/' + opts.cfgfile, (data) => {
opts.cfgdata = data; opts.cfgdata = data;
console.log("loaded " + opts.cfgfile); console.log("loaded " + opts.cfgfile);
}, 'text'); }, 'text');
@ -714,7 +717,7 @@ export function BaseMAMEPlatform() {
var oReq1 = new XMLHttpRequest(); var oReq1 = new XMLHttpRequest();
oReq1.open("GET", 'mame/roms/' + opts.biosfile, true); oReq1.open("GET", 'mame/roms/' + opts.biosfile, true);
oReq1.responseType = "arraybuffer"; oReq1.responseType = "arraybuffer";
oReq1.onload = function(oEvent) { oReq1.onload = (oEvent) => {
opts.biosdata = new Uint8Array(oReq1.response); opts.biosdata = new Uint8Array(oReq1.response);
console.log("loaded " + opts.biosfile); // + " (" + oEvent.total + " bytes)"); console.log("loaded " + opts.biosfile); // + " (" + oEvent.total + " bytes)");
fetch_bios.resolve(); fetch_bios.resolve();
@ -724,8 +727,8 @@ export function BaseMAMEPlatform() {
fetch_bios.resolve(); fetch_bios.resolve();
} }
// load debugger Lua script // load debugger Lua script
fetch_lua = $.get('mame/debugger.lua', function(data) { fetch_lua = $.get('mame/debugger.lua', (data) => {
luadebugscript = data; this.luadebugscript = data;
console.log("loaded debugger.lua"); console.log("loaded debugger.lua");
}, 'text'); }, 'text');
// load WASM // load WASM
@ -733,7 +736,7 @@ export function BaseMAMEPlatform() {
var oReq2 = new XMLHttpRequest(); var oReq2 = new XMLHttpRequest();
oReq2.open("GET", 'mame/' + opts.jsfile.replace('.js','.wasm'), true); oReq2.open("GET", 'mame/' + opts.jsfile.replace('.js','.wasm'), true);
oReq2.responseType = "arraybuffer"; oReq2.responseType = "arraybuffer";
oReq2.onload = function(oEvent) { oReq2.onload = (oEvent) => {
console.log("loaded WASM file"); console.log("loaded WASM file");
window['Module'].wasmBinary = new Uint8Array(oReq2.response); window['Module'].wasmBinary = new Uint8Array(oReq2.response);
fetch_wasm.resolve(); fetch_wasm.resolve();
@ -741,7 +744,7 @@ export function BaseMAMEPlatform() {
oReq2.send(); oReq2.send();
} }
// start loading script // start loading script
$.when(fetch_lua, fetch_cfg, fetch_bios, fetch_wasm).done(function() { $.when(fetch_lua, fetch_cfg, fetch_bios, fetch_wasm).done( () => {
var script = document.createElement('script'); var script = document.createElement('script');
script.src = 'mame/' + opts.jsfile; script.src = 'mame/' + opts.jsfile;
document.getElementsByTagName('head')[0].appendChild(script); document.getElementsByTagName('head')[0].appendChild(script);
@ -749,32 +752,32 @@ export function BaseMAMEPlatform() {
}); });
} }
this.loadROMFile = function(data) { loadROMFile(data) {
romdata = data; this.romdata = data;
if (preinitted && romfn) { if (this.preinitted && this.romfn) {
FS.writeFile(romfn, data, {encoding:'binary'}); FS.writeFile(this.romfn, data, {encoding:'binary'});
} }
} }
this.loadRegion = function(region, data) { loadRegion(region, data) {
if (loaded) { if (this.loaded) {
//self.luacall('cart=manager:machine().images["cart"]\nprint(cart:filename())\ncart:load("' + romfn + '")\n'); //this.luacall('cart=manager:machine().images["cart"]\nprint(cart:filename())\ncart:load("' + romfn + '")\n');
var s = 'rgn = manager:machine():memory().regions["' + region + '"]\n'; var s = 'rgn = manager:machine():memory().regions["' + region + '"]\n';
//s += 'print(rgn.size)\n'; //s += 'print(rgn.size)\n';
for (var i=0; i<data.length; i+=4) { 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); 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? s += 'rgn:write_u32(' + i + ',' + v + ')\n'; // TODO: endian?
} }
self.luacall(s); this.luacall(s);
self.reset(); this.reset();
} }
} }
this.preserveState = function() { preserveState() {
var state = {c:{}}; var state = {c:{}};
for (var k in console_vars) { for (var k in this.console_vars) {
if (k.startsWith("cpu_")) { if (k.startsWith("cpu_")) {
var v = parseInt(console_vars[k][0]); var v = parseInt(this.console_vars[k][0]);
state.c[k.slice(4)] = v; state.c[k.slice(4)] = v;
} }
} }
@ -782,59 +785,57 @@ export function BaseMAMEPlatform() {
return state; return state;
} }
this.saveState = function() { saveState() {
this.luareset(); this.luareset();
this.luacall('mamedbg.printstate()'); this.luacall('mamedbg.printstate()');
return self.preserveState(); return this.preserveState();
} }
this.initlua = function() { initlua() {
if (!initluavars) { if (!this.initluavars) {
self.luacall(luadebugscript); this.luacall(this.luadebugscript);
self.luacall('mamedbg.init()') this.luacall('mamedbg.init()')
initluavars = true; this.initluavars = true;
} }
} }
this.readAddress = function(a) { readAddress(a) {
self.initlua(); this.initlua();
self.luacall('print(">>>v"); print(mem:read_u8(' + a + '))'); this.luacall('print(">>>v"); print(mem:read_u8(' + a + '))');
return parseInt(console_vars.v[0]); return parseInt(this.console_vars.v[0]);
} }
// DEBUGGING SUPPORT // DEBUGGING SUPPORT
var onBreakpointHit; clearDebug() {
this.onBreakpointHit = null;
this.clearDebug = function() {
onBreakpointHit = null;
} }
this.getDebugCallback = function() { getDebugCallback() {
return onBreakpointHit;// TODO? return this.onBreakpointHit;// TODO?
} }
this.setupDebug = function(callback) { setupDebug(callback) {
self.initlua(); this.initlua();
self.luareset(); this.luareset();
onBreakpointHit = callback; this.onBreakpointHit = callback;
} }
this.runToPC = function(pc) { runToPC(pc) {
self.luacall('mamedbg.runTo(' + pc + ')'); this.luacall('mamedbg.runTo(' + pc + ')');
self.resume(); this.resume();
} }
this.runToVsync = function() { runToVsync() {
self.luacall('mamedbg.runToVsync()'); this.luacall('mamedbg.runToVsync()');
self.resume(); this.resume();
} }
this.runUntilReturn = function() { runUntilReturn() {
self.luacall('mamedbg.runUntilReturn()'); this.luacall('mamedbg.runUntilReturn()');
self.resume(); this.resume();
} }
this.step = function() { step() {
self.luacall('mamedbg.step()'); this.luacall('mamedbg.step()');
self.resume(); this.resume();
} }
// TODO: other than z80 // TODO: other than z80
this.cpuStateToLongString = function(c) { cpuStateToLongString(c) {
if (c.HL) if (c.HL)
return cpuStateToLongString_Z80(c); return cpuStateToLongString_Z80(c);
else else

View File

@ -7,7 +7,7 @@ import { SampleAudio } from "../audio";
declare var jt; // 6502 declare var jt; // 6502
var APPLE2_PRESETS = [ const APPLE2_PRESETS = [
{id:'sieve.c', name:'Sieve'}, {id:'sieve.c', name:'Sieve'},
{id:'mandel.c', name:'Mandelbrot'}, {id:'mandel.c', name:'Mandelbrot'},
{id:'tgidemo.c', name:'TGI Graphics Demo'}, {id:'tgidemo.c', name:'TGI Graphics Demo'},
@ -19,15 +19,12 @@ var APPLE2_PRESETS = [
// {id:'tb_6502.s', name:'Tom Bombem (assembler game)'}, // {id:'tb_6502.s', name:'Tom Bombem (assembler game)'},
]; ];
var GR_TXMODE = 1; const GR_TXMODE = 1;
var GR_MIXMODE = 2; const GR_MIXMODE = 2;
var GR_PAGE1 = 4; const GR_PAGE1 = 4;
var GR_HIRES = 8; const GR_HIRES = 8;
var Apple2Platform = function(mainElement) {
var self = this;
this.__proto__ = new (Base6502Platform as any)();
const _Apple2Platform = function(mainElement) {
var cpuFrequency = 1023000; var cpuFrequency = 1023000;
var cpuCyclesPerLine = 65; var cpuCyclesPerLine = 65;
var cpu, ram, bus; var cpu, ram, bus;
@ -50,11 +47,12 @@ var Apple2Platform = function(mainElement) {
var bank2rdoffset=0, bank2wroffset=0; var bank2rdoffset=0, bank2wroffset=0;
var grparams; var grparams;
this.getPresets = function() { class Apple2Platform extends Base6502Platform {
getPresets() {
return APPLE2_PRESETS; return APPLE2_PRESETS;
} }
start() {
this.start = function() {
cpu = new jt.M6502(); cpu = new jt.M6502();
ram = new RAM(0x13000); // 64K + 16K LC RAM - 4K hardware ram = new RAM(0x13000); // 64K + 16K LC RAM - 4K hardware
// ROM // ROM
@ -183,7 +181,7 @@ var Apple2Platform = function(mainElement) {
timer = new AnimationTimer(60, this.advance.bind(this)); timer = new AnimationTimer(60, this.advance.bind(this));
} }
this.advance = function(novideo : boolean) { advance(novideo : boolean) {
// 262.5 scanlines per frame // 262.5 scanlines per frame
var clock = 0; var clock = 0;
var debugCond = this.getDebugCallback(); var debugCond = this.getDebugCallback();
@ -214,6 +212,70 @@ var Apple2Platform = function(mainElement) {
this.restartDebugState(); // reset debug start state this.restartDebugState(); // reset debug start state
} }
loadROM(title, data) {
pgmbin = data;
this.reset();
}
isRunning() {
return timer.isRunning();
}
pause() {
timer.stop();
audio.stop();
}
resume() {
timer.start();
audio.start();
}
reset() {
cpu.reset();
// execute until $c600 boot
for (var i=0; i<2000000; i++) {
cpu.clockPulse();
if (this.getCPUState().PC == 0xc602) {
cpu.clockPulse();
cpu.clockPulse();
break;
}
}
}
readAddress(addr) {
return bus.read(addr);
}
loadState(state) {
cpu.loadState(state.c);
ram.mem.set(state.b);
kbdlatch = state.kbd;
grswitch = state.gr;
auxRAMselected = state.lc.s;
auxRAMbank = state.lc.b;
writeinhibit = state.lc.w;
setupLanguageCardConstants();
}
saveState() {
return {
c:cpu.saveState(),
b:ram.mem.slice(0),
kbd:kbdlatch,
gr:grswitch,
lc:{s:auxRAMselected,b:auxRAMbank,w:writeinhibit},
};
}
loadControlsState(state) {
kbdlatch = state.kbd;
}
saveControlsState() {
return {
kbd:kbdlatch,
};
}
getCPUState() {
return cpu.saveState();
}
}
function doLanguageCardIO(address, value) function doLanguageCardIO(address, value)
{ {
switch (address & 0x0f) { switch (address & 0x0f) {
@ -284,68 +346,7 @@ var Apple2Platform = function(mainElement) {
bank2wroffset = 0x3000; // map 0xd000-0xdfff -> 0x10000-0x10fff bank2wroffset = 0x3000; // map 0xd000-0xdfff -> 0x10000-0x10fff
} }
this.loadROM = function(title, data) { return new Apple2Platform(); // return inner class from constructor
pgmbin = data;
this.reset();
}
this.isRunning = function() {
return timer.isRunning();
}
this.pause = function() {
timer.stop();
audio.stop();
}
this.resume = function() {
timer.start();
audio.start();
}
this.reset = function() {
cpu.reset();
// execute until $c600 boot
for (var i=0; i<2000000; i++) {
cpu.clockPulse();
if (this.getCPUState().PC == 0xc602) {
cpu.clockPulse();
cpu.clockPulse();
break;
}
}
}
this.readAddress = function(addr) {
return bus.read(addr);
}
this.loadState = function(state) {
cpu.loadState(state.c);
ram.mem.set(state.b);
kbdlatch = state.kbd;
grswitch = state.gr;
auxRAMselected = state.lc.s;
auxRAMbank = state.lc.b;
writeinhibit = state.lc.w;
setupLanguageCardConstants();
}
this.saveState = function() {
return {
c:cpu.saveState(),
b:ram.mem.slice(0),
kbd:kbdlatch,
gr:grswitch,
lc:{s:auxRAMselected,b:auxRAMbank,w:writeinhibit},
};
}
this.loadControlsState = function(state) {
kbdlatch = state.kbd;
}
this.saveControlsState = function() {
return {
kbd:kbdlatch,
};
}
this.getCPUState = function() {
return cpu.saveState();
}
}; };
var Apple2Display = function(pixels, apple) { var Apple2Display = function(pixels, apple) {
@ -1015,12 +1016,10 @@ var APPLEIIGO_LZG = [
/// MAME support /// MAME support
var Apple2MAMEPlatform = function(mainElement) { class Apple2MAMEPlatform extends BaseMAMEPlatform {
var self = this;
this.__proto__ = new BaseMAMEPlatform();
this.start = function() { start () {
self.startModule(mainElement, { this.startModule(this.mainElement, {
jsfile:'mameapple2e.js', jsfile:'mameapple2e.js',
biosfile:['apple2e.zip'], biosfile:['apple2e.zip'],
//cfgfile:'nes.cfg', //cfgfile:'nes.cfg',
@ -1035,16 +1034,16 @@ var Apple2MAMEPlatform = function(mainElement) {
}); });
} }
this.getOpcodeMetadata = getOpcodeMetadata_6502; getOpcodeMetadata = getOpcodeMetadata_6502;
this.getDefaultExtension = function() { return ".c"; }; getDefaultExtension () { return ".c"; };
this.getPresets = function() { return APPLE2_PRESETS; } getPresets () { return APPLE2_PRESETS; }
this.loadROM = function(title, data) { loadROM (title, data) {
this.loadROMFile(data); this.loadROMFile(data);
// TODO // TODO
} }
} }
PLATFORMS['apple2'] = Apple2Platform; PLATFORMS['apple2'] = _Apple2Platform;
PLATFORMS['apple2-e'] = Apple2MAMEPlatform; PLATFORMS['apple2-e'] = Apple2MAMEPlatform;

View File

@ -12,7 +12,7 @@ var Atari8_PRESETS = [
var Atari8MAMEPlatform = function(mainElement) { var Atari8MAMEPlatform = function(mainElement) {
var self = this; var self = this;
this.__proto__ = new BaseMAMEPlatform(); this.__proto__ = new (BaseMAMEPlatform as any)();
this.loadROM = function(title, data) { this.loadROM = function(title, data) {
this.loadROMFile(data); this.loadROMFile(data);
@ -47,7 +47,7 @@ var Atari800Platform = function(mainElement) {
var Atari5200Platform = function(mainElement) { var Atari5200Platform = function(mainElement) {
var self = this; var self = this;
this.__proto__ = new Atari8MAMEPlatform(mainElement); this.__proto__ = new (Atari8MAMEPlatform as any)(mainElement);
this.start = function() { this.start = function() {
self.startModule(mainElement, { self.startModule(mainElement, {

View File

@ -33,13 +33,10 @@ var ColecoVision_PRESETS = [
/// MAME support /// MAME support
var ColecoVisionMAMEPlatform = function(mainElement) { class ColecoVisionMAMEPlatform extends BaseMAMEPlatform {
var self = this;
this.__proto__ = new BaseMAMEPlatform();
// start() {
this.start = function() { this.startModule(this.mainElement, {
self.startModule(mainElement, {
jsfile:'mamecoleco.js', jsfile:'mamecoleco.js',
cfgfile:'coleco.cfg', cfgfile:'coleco.cfg',
biosfile:'coleco/313 10031-4005 73108a.u2', biosfile:'coleco/313 10031-4005 73108a.u2',
@ -53,15 +50,15 @@ var ColecoVisionMAMEPlatform = function(mainElement) {
}); });
} }
this.loadROM = function(title, data) { loadROM(title, data) {
this.loadROMFile(data); this.loadROMFile(data);
this.loadRegion(":coleco_cart:rom", data); this.loadRegion(":coleco_cart:rom", data);
} }
this.getPresets = function() { return ColecoVision_PRESETS; } getPresets() { return ColecoVision_PRESETS; }
this.getToolForFilename = getToolForFilename_z80; getToolForFilename = getToolForFilename_z80;
this.getDefaultExtension = function() { return ".c"; }; getDefaultExtension() { return ".c"; };
} }
/// ///

View File

@ -5,12 +5,12 @@ import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap,
import { hex } from "../util"; import { hex } from "../util";
import { MasterAudio, AY38910_Audio } from "../audio"; import { MasterAudio, AY38910_Audio } from "../audio";
var GALAXIAN_PRESETS = [ const GALAXIAN_PRESETS = [
{id:'gfxtest.c', name:'Graphics Test'}, {id:'gfxtest.c', name:'Graphics Test'},
{id:'shoot2.c', name:'Solarian Game'}, {id:'shoot2.c', name:'Solarian Game'},
]; ];
var GALAXIAN_KEYCODE_MAP = makeKeycodeMap([ const GALAXIAN_KEYCODE_MAP = makeKeycodeMap([
[Keys.VK_SPACE, 0, 0x10], // P1 [Keys.VK_SPACE, 0, 0x10], // P1
[Keys.VK_LEFT, 0, 0x4], [Keys.VK_LEFT, 0, 0x4],
[Keys.VK_RIGHT, 0, 0x8], [Keys.VK_RIGHT, 0, 0x8],
@ -22,7 +22,7 @@ var GALAXIAN_KEYCODE_MAP = makeKeycodeMap([
[Keys.VK_2, 1, 0x2], [Keys.VK_2, 1, 0x2],
]); ]);
var SCRAMBLE_KEYCODE_MAP = makeKeycodeMap([ const SCRAMBLE_KEYCODE_MAP = makeKeycodeMap([
[Keys.VK_UP, 0, -0x1], // P1 [Keys.VK_UP, 0, -0x1], // P1
[Keys.VK_SHIFT, 0, -0x2], // fire [Keys.VK_SHIFT, 0, -0x2], // fire
[Keys.VK_7, 0, -0x4], // credit [Keys.VK_7, 0, -0x4], // credit
@ -38,10 +38,7 @@ var SCRAMBLE_KEYCODE_MAP = makeKeycodeMap([
]); ]);
var GalaxianPlatform = function(mainElement, options) { const _GalaxianPlatform = function(mainElement, options) {
var self = this;
this.__proto__ = new (BaseZ80Platform as any)();
options = options || {}; options = options || {};
var romSize = options.romSize || 0x4000; var romSize = options.romSize || 0x4000;
var gfxBase = options.gfxBase || 0x2800; var gfxBase = options.gfxBase || 0x2800;
@ -169,10 +166,6 @@ var GalaxianPlatform = function(mainElement, options) {
} }
} }
this.getPresets = function() {
return GALAXIAN_PRESETS;
}
var m_protection_state = 0; var m_protection_state = 0;
var m_protection_result = 0; var m_protection_result = 0;
function scramble_protection_w(addr,data) { function scramble_protection_w(addr,data) {
@ -201,7 +194,19 @@ var GalaxianPlatform = function(mainElement, options) {
return (bit << 5) | ((bit^1) << 7); return (bit << 5) | ((bit^1) << 7);
} }
this.start = function() { const bitcolors = [
0x000021, 0x000047, 0x000097, // red
0x002100, 0x004700, 0x009700, // green
0x510000, 0xae0000 // blue
];
class GalaxianPlatform extends BaseZ80Platform {
getPresets() {
return GALAXIAN_PRESETS;
}
start() {
ram = new RAM(0x800); ram = new RAM(0x800);
vram = new RAM(0x400); vram = new RAM(0x400);
oram = new RAM(0x100); oram = new RAM(0x100);
@ -271,9 +276,6 @@ var GalaxianPlatform = function(mainElement, options) {
isContended: function() { return false; }, isContended: function() { return false; },
}; };
} }
this.readAddress = function(a) {
return (a == 0x7000 || a == 0x7800) ? null : membus.read(a); // ignore watchdog
};
audio = new MasterAudio(); audio = new MasterAudio();
psg1 = new AY38910_Audio(audio); psg1 = new AY38910_Audio(audio);
psg2 = new AY38910_Audio(audio); psg2 = new AY38910_Audio(audio);
@ -288,7 +290,7 @@ var GalaxianPlatform = function(mainElement, options) {
if (addr & 0x8) { psg2.setData(val); }; if (addr & 0x8) { psg2.setData(val); };
} }
}; };
cpu = self.newCPU(membus, iobus); cpu = this.newCPU(membus, iobus);
video = new RasterVideo(mainElement,264,264,{rotate:90}); video = new RasterVideo(mainElement,264,264,{rotate:90});
video.create(); video.create();
var idata = video.getFrameData(); var idata = video.getFrameData();
@ -297,7 +299,11 @@ var GalaxianPlatform = function(mainElement, options) {
timer = new AnimationTimer(60, this.advance.bind(this)); timer = new AnimationTimer(60, this.advance.bind(this));
} }
this.advance = function(novideo : boolean) { readAddress(a) {
return (a == 0x7000 || a == 0x7800) ? null : membus.read(a); // ignore watchdog
}
advance(novideo : boolean) {
var debugCond = this.getDebugCallback(); var debugCond = this.getDebugCallback();
var targetTstates = cpu.getTstates(); var targetTstates = cpu.getTstates();
for (var sl=0; sl<scanlinesPerFrame; sl++) { for (var sl=0; sl<scanlinesPerFrame; sl++) {
@ -326,13 +332,7 @@ var GalaxianPlatform = function(mainElement, options) {
this.restartDebugState(); // TODO: after interrupt? this.restartDebugState(); // TODO: after interrupt?
} }
var bitcolors = [ loadROM(title, data) {
0x000021, 0x000047, 0x000097, // red
0x002100, 0x004700, 0x009700, // green
0x510000, 0xae0000 // blue
];
this.loadROM = function(title, data) {
rom = padBytes(data, romSize); rom = padBytes(data, romSize);
palette = new Uint32Array(new ArrayBuffer(32*4)); palette = new Uint32Array(new ArrayBuffer(32*4));
@ -343,10 +343,10 @@ var GalaxianPlatform = function(mainElement, options) {
if (((1<<j) & b)) if (((1<<j) & b))
palette[i] += bitcolors[j]; palette[i] += bitcolors[j];
} }
self.reset(); this.reset();
} }
this.loadState = function(state) { loadState(state) {
cpu.loadState(state.c); cpu.loadState(state.c);
ram.mem.set(state.b); ram.mem.set(state.b);
vram.mem.set(state.bv); vram.mem.set(state.bv);
@ -359,9 +359,9 @@ var GalaxianPlatform = function(mainElement, options) {
inputs[1] = state.in1; inputs[1] = state.in1;
inputs[2] = state.in2; inputs[2] = state.in2;
} }
this.saveState = function() { saveState() {
return { return {
c:self.getCPUState(), c:this.getCPUState(),
b:ram.mem.slice(0), b:ram.mem.slice(0),
bv:vram.mem.slice(0), bv:vram.mem.slice(0),
bo:oram.mem.slice(0), bo:oram.mem.slice(0),
@ -374,31 +374,46 @@ var GalaxianPlatform = function(mainElement, options) {
in2:inputs[2], in2:inputs[2],
}; };
} }
this.getCPUState = function() { loadControlsState(state) {
inputs[0] = state.in0;
inputs[1] = state.in1;
inputs[2] = state.in2;
}
saveControlsState() {
return {
in0:inputs[0],
in1:inputs[1],
in2:inputs[2],
};
}
getCPUState() {
return cpu.saveState(); return cpu.saveState();
} }
this.isRunning = function() { isRunning() {
return timer && timer.isRunning(); return timer && timer.isRunning();
} }
this.pause = function() { pause() {
timer.stop(); timer.stop();
audio.stop(); audio.stop();
} }
this.resume = function() { resume() {
timer.start(); timer.start();
audio.start(); audio.start();
} }
this.reset = function() { reset() {
cpu.reset(); cpu.reset();
//audio.reset(); //audio.reset();
if (!this.getDebugCallback()) cpu.setTstates(0); // TODO? if (!this.getDebugCallback()) cpu.setTstates(0); // TODO?
watchdog_counter = INITIAL_WATCHDOG; watchdog_counter = INITIAL_WATCHDOG;
} }
}
return new GalaxianPlatform();
} }
var GalaxianScramblePlatform = function(mainElement) { const _GalaxianScramblePlatform = function(mainElement) {
this.__proto__ = new GalaxianPlatform(mainElement, { return _GalaxianPlatform(mainElement, {
romSize: 0x5020, romSize: 0x5020,
gfxBase: 0x4000, gfxBase: 0x4000,
palBase: 0x5000, palBase: 0x5000,
@ -409,5 +424,5 @@ var GalaxianScramblePlatform = function(mainElement) {
}); });
} }
PLATFORMS['galaxian'] = GalaxianPlatform; PLATFORMS['galaxian'] = _GalaxianPlatform;
PLATFORMS['galaxian-scramble'] = GalaxianScramblePlatform; PLATFORMS['galaxian-scramble'] = _GalaxianScramblePlatform;

View File

@ -6,15 +6,13 @@ import { hex } from "../util";
// http://www.computerarcheology.com/Arcade/ // http://www.computerarcheology.com/Arcade/
var MW8080BW_PRESETS = [ const MW8080BW_PRESETS = [
{id:'gfxtest.c', name:'Graphics Test'}, {id:'gfxtest.c', name:'Graphics Test'},
{id:'shifter.c', name:'Sprite w/ Bit Shifter'}, {id:'shifter.c', name:'Sprite w/ Bit Shifter'},
{id:'game2.c', name:'Cosmic Impalas'}, {id:'game2.c', name:'Cosmic Impalas'},
]; ];
var Midway8080BWPlatform = function(mainElement) { const _Midway8080BWPlatform = function(mainElement) {
var self = this;
this.__proto__ = new (BaseZ80Platform as any)();
var cpu, ram, membus, iobus, rom; var cpu, ram, membus, iobus, rom;
var probe; var probe;
@ -23,13 +21,13 @@ var Midway8080BWPlatform = function(mainElement) {
var bitshift_offset = 0; var bitshift_offset = 0;
var bitshift_register = 0; var bitshift_register = 0;
var watchdog_counter; var watchdog_counter;
var cpuFrequency = 1996800; const cpuFrequency = 1996800;
var cpuCyclesPerLine = cpuFrequency/(60*224); // TODO const cpuCyclesPerLine = cpuFrequency/(60*224); // TODO
var INITIAL_WATCHDOG = 256; const INITIAL_WATCHDOG = 256;
var PIXEL_ON = 0xffeeeeee; const PIXEL_ON = 0xffeeeeee;
var PIXEL_OFF = 0xff000000; const PIXEL_OFF = 0xff000000;
var SPACEINV_KEYCODE_MAP = makeKeycodeMap([ const SPACEINV_KEYCODE_MAP = makeKeycodeMap([
[Keys.VK_SPACE, 1, 0x10], // P1 [Keys.VK_SPACE, 1, 0x10], // P1
[Keys.VK_LEFT, 1, 0x20], [Keys.VK_LEFT, 1, 0x20],
[Keys.VK_RIGHT, 1, 0x40], [Keys.VK_RIGHT, 1, 0x40],
@ -41,11 +39,13 @@ var Midway8080BWPlatform = function(mainElement) {
[Keys.VK_2, 1, 0x2], [Keys.VK_2, 1, 0x2],
]); ]);
this.getPresets = function() { class Midway8080BWPlatform extends BaseZ80Platform {
getPresets() {
return MW8080BW_PRESETS; return MW8080BW_PRESETS;
} }
this.start = function() { start = function() {
ram = new RAM(0x2000); ram = new RAM(0x2000);
//displayPCs = new Uint16Array(new ArrayBuffer(0x2000*2)); //displayPCs = new Uint16Array(new ArrayBuffer(0x2000*2));
membus = { membus = {
@ -65,7 +65,6 @@ var Midway8080BWPlatform = function(mainElement) {
]), ]),
isContended: function() { return false; }, isContended: function() { return false; },
}; };
this.readAddress = membus.read;
iobus = { iobus = {
read: function(addr) { read: function(addr) {
addr &= 0x3; addr &= 0x3;
@ -116,7 +115,11 @@ var Midway8080BWPlatform = function(mainElement) {
timer = new AnimationTimer(60, this.advance.bind(this)); timer = new AnimationTimer(60, this.advance.bind(this));
} }
this.advance = function(novideo : boolean) { readAddress(addr) {
return membus.read(addr);
}
advance(novideo : boolean) {
var debugCond = this.getDebugCallback(); var debugCond = this.getDebugCallback();
var targetTstates = cpu.getTstates(); var targetTstates = cpu.getTstates();
for (var sl=0; sl<224; sl++) { for (var sl=0; sl<224; sl++) {
@ -149,12 +152,12 @@ var Midway8080BWPlatform = function(mainElement) {
this.restartDebugState(); this.restartDebugState();
} }
this.loadROM = function(title, data) { loadROM(title, data) {
rom = padBytes(data, 0x2000); rom = padBytes(data, 0x2000);
self.reset(); this.reset();
} }
this.loadState = function(state) { loadState(state) {
cpu.loadState(state.c); cpu.loadState(state.c);
ram.mem.set(state.b); ram.mem.set(state.b);
bitshift_register = state.bsr; bitshift_register = state.bsr;
@ -164,9 +167,9 @@ var Midway8080BWPlatform = function(mainElement) {
inputs[1] = state.in1; inputs[1] = state.in1;
inputs[2] = state.in2; inputs[2] = state.in2;
} }
this.saveState = function() { saveState() {
return { return {
c:self.getCPUState(), c:this.getCPUState(),
b:ram.mem.slice(0), b:ram.mem.slice(0),
bsr:bitshift_register, bsr:bitshift_register,
bso:bitshift_offset, bso:bitshift_offset,
@ -176,24 +179,38 @@ var Midway8080BWPlatform = function(mainElement) {
in2:inputs[2], in2:inputs[2],
}; };
} }
this.getCPUState = function() { loadControlsState(state) {
inputs[0] = state.in0;
inputs[1] = state.in1;
inputs[2] = state.in2;
}
saveControlsState() {
return {
in0:inputs[0],
in1:inputs[1],
in2:inputs[2],
};
}
getCPUState() {
return cpu.saveState(); return cpu.saveState();
} }
this.isRunning = function() { isRunning() {
return timer && timer.isRunning(); return timer && timer.isRunning();
} }
this.pause = function() { pause() {
timer.stop(); timer.stop();
} }
this.resume = function() { resume() {
timer.start(); timer.start();
} }
this.reset = function() { reset() {
cpu.reset(); cpu.reset();
cpu.setTstates(0); cpu.setTstates(0);
watchdog_counter = INITIAL_WATCHDOG; watchdog_counter = INITIAL_WATCHDOG;
} }
}
return new Midway8080BWPlatform();
} }
PLATFORMS['mw8080bw'] = Midway8080BWPlatform; PLATFORMS['mw8080bw'] = _Midway8080BWPlatform;

View File

@ -8,7 +8,7 @@ import { SampleAudio } from "../audio";
declare var jsnes : any; declare var jsnes : any;
var JSNES_PRESETS = [ const JSNES_PRESETS = [
{id:'ex0.asm', name:'Initialization (ASM)'}, {id:'ex0.asm', name:'Initialization (ASM)'},
{id:'ex1.asm', name:'Scrolling Demo (ASM)'}, {id:'ex1.asm', name:'Scrolling Demo (ASM)'},
{id:'ex2.asm', name:'Sprite Demo (ASM)'}, {id:'ex2.asm', name:'Sprite Demo (ASM)'},
@ -25,7 +25,7 @@ var JSNES_PRESETS = [
{id:'musicdemo.asm', name:'Famitone Demo (ASM)'}, {id:'musicdemo.asm', name:'Famitone Demo (ASM)'},
]; ];
var NES_NESLIB_PRESETS = [ const NES_NESLIB_PRESETS = [
{id:'neslib1.c', name:'Text'}, {id:'neslib1.c', name:'Text'},
{id:'neslib2.c', name:'Sprites'}, {id:'neslib2.c', name:'Sprites'},
{id:'neslib3.c', name:'Cursor'}, {id:'neslib3.c', name:'Cursor'},
@ -33,7 +33,7 @@ var NES_NESLIB_PRESETS = [
{id:'chase/game.c', name:'Chase (example game)'}, {id:'chase/game.c', name:'Chase (example game)'},
]; ];
var NES_CONIO_PRESETS = [ const NES_CONIO_PRESETS = [
{id:'ex0.asm', name:'ASM: Initialization'}, {id:'ex0.asm', name:'ASM: Initialization'},
{id:'ex1.asm', name:'ASM: Scrolling Demo'}, {id:'ex1.asm', name:'ASM: Scrolling Demo'},
{id:'hello.c', name:'C: Hello PPU'}, {id:'hello.c', name:'C: Hello PPU'},
@ -43,7 +43,7 @@ var NES_CONIO_PRESETS = [
/// JSNES /// JSNES
var JSNES_KEYCODE_MAP = makeKeycodeMap([ const JSNES_KEYCODE_MAP = makeKeycodeMap([
[Keys.VK_Z, 0, 0], [Keys.VK_Z, 0, 0],
[Keys.VK_X, 0, 1], [Keys.VK_X, 0, 1],
[Keys.VK_2, 0, 2], [Keys.VK_2, 0, 2],
@ -62,21 +62,22 @@ var JSNES_KEYCODE_MAP = makeKeycodeMap([
[Keys.VK_D, 1, 7], [Keys.VK_D, 1, 7],
]); ]);
var JSNESPlatform = function(mainElement) { const _JSNESPlatform = function(mainElement) {
var self = this;
this.__proto__ = new (Base6502Platform as any)();
this.debugPCDelta = 1;
var nes; var nes;
var rom; var rom;
var video, audio, timer; var video, audio, timer;
var audioFrequency = 44030; //44100 const audioFrequency = 44030; //44100
var frameindex = 0; var frameindex = 0;
var nsamples = 0; var nsamples = 0;
this.getPresets = function() { return JSNES_PRESETS; } class JSNESPlatform extends Base6502Platform {
debugPCDelta = 1;
this.start = function() { getPresets() { return JSNES_PRESETS; }
start() {
var self = this;
video = new RasterVideo(mainElement,256,224); video = new RasterVideo(mainElement,256,224);
audio = new SampleAudio(audioFrequency); audio = new SampleAudio(audioFrequency);
video.create(); video.create();
@ -128,52 +129,51 @@ var JSNESPlatform = function(mainElement) {
}); });
} }
this.advance = function(novideo : boolean) { advance(novideo : boolean) {
nes.frame(); nes.frame();
} }
this.loadROM = function(title, data) { loadROM(title, data) {
var romstr = String.fromCharCode.apply(null, data); var romstr = String.fromCharCode.apply(null, data);
nes.loadROM(romstr); nes.loadROM(romstr);
frameindex = 0; frameindex = 0;
} }
this.newCodeAnalyzer = function() { newCodeAnalyzer() {
return new CodeAnalyzer_nes(this); return new CodeAnalyzer_nes(this);
} }
this.getOriginPC = function() { // TODO: is actually NMI getOriginPC() { // TODO: is actually NMI
return (this.readAddress(0xfffa) | (this.readAddress(0xfffb) << 8)) & 0xffff; return (this.readAddress(0xfffa) | (this.readAddress(0xfffb) << 8)) & 0xffff;
} }
this.getOpcodeMetadata = getOpcodeMetadata_6502; getDefaultExtension() { return ".c"; };
this.getDefaultExtension = function() { return ".c"; };
this.reset = function() { reset() {
//nes.cpu.reset(); // doesn't work right, crashes //nes.cpu.reset(); // doesn't work right, crashes
nes.cpu.requestIrq(nes.cpu.IRQ_RESET); nes.cpu.requestIrq(nes.cpu.IRQ_RESET);
} }
this.isRunning = function() { isRunning() {
return timer.isRunning(); return timer.isRunning();
} }
this.pause = function() { pause() {
timer.stop(); timer.stop();
audio.stop(); audio.stop();
} }
this.resume = function() { resume() {
timer.start(); timer.start();
audio.start(); audio.start();
} }
this.runToVsync = function() { runToVsync() {
var frame0 = frameindex; var frame0 = frameindex;
this.runEval(function(c) { return frameindex>frame0; }); this.runEval(function(c) { return frameindex>frame0; });
} }
this.getCPUState = function() { getCPUState() {
var c = nes.cpu.toJSON(); var c = nes.cpu.toJSON();
this.copy6502REGvars(c); this.copy6502REGvars(c);
return c; return c;
} }
// TODO don't need to save ROM? // TODO don't need to save ROM?
this.saveState = function() { saveState() {
//var s = $.extend(true, {}, nes); //var s = $.extend(true, {}, nes);
var s = nes.toJSON(); var s = nes.toJSON();
s.c = s.cpu; s.c = s.cpu;
@ -181,9 +181,10 @@ var JSNESPlatform = function(mainElement) {
s.b = s.cpu.mem = s.cpu.mem.slice(0); s.b = s.cpu.mem = s.cpu.mem.slice(0);
s.ppu.vramMem = s.ppu.vramMem.slice(0); s.ppu.vramMem = s.ppu.vramMem.slice(0);
s.ppu.spriteMem = s.ppu.spriteMem.slice(0); s.ppu.spriteMem = s.ppu.spriteMem.slice(0);
s.ctrl = this.saveControlsState();
return s; return s;
} }
this.loadState = function(state) { loadState(state) {
nes.fromJSON(state); nes.fromJSON(state);
//nes.cpu.fromJSON(state.cpu); //nes.cpu.fromJSON(state.cpu);
//nes.mmap.fromJSON(state.mmap); //nes.mmap.fromJSON(state.mmap);
@ -191,12 +192,23 @@ var JSNESPlatform = function(mainElement) {
nes.cpu.mem = state.cpu.mem.slice(0); nes.cpu.mem = state.cpu.mem.slice(0);
nes.ppu.vramMem = state.ppu.vramMem.slice(0); nes.ppu.vramMem = state.ppu.vramMem.slice(0);
nes.ppu.spriteMem = state.ppu.spriteMem.slice(0); nes.ppu.spriteMem = state.ppu.spriteMem.slice(0);
this.loadControlsState(state.ctrl);
//$.extend(nes, state); //$.extend(nes, state);
} }
this.readAddress = function(addr) { saveControlsState() {
return {
c1: nes.controllers[1].state.slice(0),
c2: nes.controllers[2].state.slice(0),
};
}
loadControlsState(state) {
nes.controllers[1].state = state.c1;
nes.controllers[2].state = state.c2;
}
readAddress(addr) {
return nes.cpu.mem[addr] & 0xff; return nes.cpu.mem[addr] & 0xff;
} }
this.copy6502REGvars = function(c) { copy6502REGvars(c) {
c.T = 0; c.T = 0;
c.PC = c.REG_PC; c.PC = c.REG_PC;
c.A = c.REG_ACC; c.A = c.REG_ACC;
@ -214,10 +226,10 @@ var JSNESPlatform = function(mainElement) {
return c; return c;
} }
this.getDebugCategories = function() { getDebugCategories() {
return ['CPU','ZPRAM','Stack','PPU']; return ['CPU','ZPRAM','Stack','PPU'];
} }
this.getDebugInfo = function(category, state) { getDebugInfo(category, state) {
switch (category) { switch (category) {
case 'CPU': return cpuStateToLongString_6502(state.c); case 'CPU': return cpuStateToLongString_6502(state.c);
case 'ZPRAM': return dumpRAM(state.b, 0x0, 0x100); case 'ZPRAM': return dumpRAM(state.b, 0x0, 0x100);
@ -225,7 +237,7 @@ var JSNESPlatform = function(mainElement) {
case 'PPU': return this.ppuStateToLongString(state.ppu, state.b); case 'PPU': return this.ppuStateToLongString(state.ppu, state.b);
} }
} }
this.ppuStateToLongString = function(ppu, mem) { ppuStateToLongString(ppu, mem) {
var s = ''; var s = '';
var PPUFLAGS = [ var PPUFLAGS = [
["f_nmiOnVblank","NMI_ON_VBLANK"], ["f_nmiOnVblank","NMI_ON_VBLANK"],
@ -283,54 +295,57 @@ var JSNESPlatform = function(mainElement) {
*/ */
return s; return s;
} }
}
return new JSNESPlatform();
} }
/// MAME support /// MAME support
var NESMAMEPlatform = function(mainElement, lzgRom, romSize) { class NESMAMEPlatform extends BaseMAMEPlatform {
var self = this; // = function(mainElement, lzgRom, romSize) {
this.__proto__ = new BaseMAMEPlatform(); lzgRom;
romSize;
// start() {
this.start = function() { this.startModule(this.mainElement, {
self.startModule(mainElement, {
jsfile:'mamenes.js', jsfile:'mamenes.js',
//cfgfile:'nes.cfg', //cfgfile:'nes.cfg',
driver:'nes', driver:'nes',
width:256*2, width:256*2,
height:240*2, height:240*2,
romfn:'/emulator/cart.nes', romfn:'/emulator/cart.nes',
romsize:romSize, romsize:this.romSize,
romdata:new Uint8Array(new lzgmini().decode(lzgRom).slice(0, romSize)), romdata:new Uint8Array(new lzgmini().decode(this.lzgRom).slice(0, this.romSize)),
preInit:function(_self) { preInit:function(_self) {
}, },
}); });
} }
this.getOpcodeMetadata = getOpcodeMetadata_6502; getOpcodeMetadata = getOpcodeMetadata_6502;
this.getToolForFilename = getToolForFilename_6502; getToolForFilename = getToolForFilename_6502;
this.getDefaultExtension = function() { return ".c"; }; getDefaultExtension() { return ".c"; };
} }
var NESConIOPlatform = function(mainElement) { class NESConIOPlatform extends NESMAMEPlatform {
var self = this; lzgRom = NES_CONIO_ROM_LZG;
this.__proto__ = new NESMAMEPlatform(mainElement, NES_CONIO_ROM_LZG, 0xa010); romSize = 0xa010;
this.getPresets = function() { return NES_CONIO_PRESETS; } getPresets() { return NES_CONIO_PRESETS; }
this.loadROM = function(title, data) { loadROM(title, data) {
this.loadROMFile(data); this.loadROMFile(data);
this.loadRegion(":nes_slot:cart:prg_rom", data.slice(0x10, 0x8010)); this.loadRegion(":nes_slot:cart:prg_rom", data.slice(0x10, 0x8010));
this.loadRegion(":nes_slot:cart:chr_rom", data.slice(0x8010, 0xa010)); this.loadRegion(":nes_slot:cart:chr_rom", data.slice(0x8010, 0xa010));
} }
} }
var NESLibPlatform = function(mainElement) {
var self = this;
this.__proto__ = new NESMAMEPlatform(mainElement, NES_NESLIB_ROM_LZG, 0x8010);
this.getPresets = function() { return NES_NESLIB_PRESETS; } class NESLibPlatform extends NESMAMEPlatform {
lzgRom = NES_NESLIB_ROM_LZG;
romSize = 0x8010;
this.loadROM = function(title, data) { getPresets() { return NES_NESLIB_PRESETS; }
loadROM(title, data) {
this.loadROMFile(data); this.loadROMFile(data);
this.loadRegion(":nes_slot:cart:prg_rom", data.slice(0x10, 0x8010)); this.loadRegion(":nes_slot:cart:prg_rom", data.slice(0x10, 0x8010));
} }
@ -338,7 +353,7 @@ var NESLibPlatform = function(mainElement) {
/// ///
PLATFORMS['nes'] = JSNESPlatform; PLATFORMS['nes'] = _JSNESPlatform;
PLATFORMS['nes-lib'] = NESLibPlatform; PLATFORMS['nes-lib'] = NESLibPlatform;
PLATFORMS['nes-conio'] = NESConIOPlatform; PLATFORMS['nes-conio'] = NESConIOPlatform;

View File

@ -9,7 +9,7 @@ declare var platform : Platform; // global platform object
declare var Javatari : any; declare var Javatari : any;
declare var jt : any; // 6502 declare var jt : any; // 6502
var VCS_PRESETS = [ const VCS_PRESETS = [
{id:'examples/hello', chapter:4, name:'Hello 6502 and TIA'}, {id:'examples/hello', chapter:4, name:'Hello 6502 and TIA'},
{id:'examples/vsync', chapter:5, name:'Painting on the CRT', title:'Color Bars'}, {id:'examples/vsync', chapter:5, name:'Painting on the CRT', title:'Color Bars'},
{id:'examples/playfield', chapter:6, name:'Playfield Graphics'}, {id:'examples/playfield', chapter:6, name:'Playfield Graphics'},
@ -47,13 +47,16 @@ Javatari.CARTRIDGE_CHANGE_DISABLED = true;
Javatari.DEBUG_SCANLINE_OVERFLOW = false; // TODO: make a switch Javatari.DEBUG_SCANLINE_OVERFLOW = false; // TODO: make a switch
Javatari.AUDIO_BUFFER_SIZE = 256; Javatari.AUDIO_BUFFER_SIZE = 256;
var VCSPlatform = function() { class VCSPlatform {
var self = this;
this.paused = true;
this.getPresets = function() { return VCS_PRESETS; } current_output;
recorder : EmuRecorder;
paused : boolean = true;
this.start = function() { getPresets() { return VCS_PRESETS; }
start() {
var self = this;
$("#javatari-div").show(); $("#javatari-div").show();
Javatari.start(); Javatari.start();
// intercept clockPulse function // intercept clockPulse function
@ -65,16 +68,16 @@ var VCSPlatform = function() {
this.paused = false; this.paused = false;
} }
this.loadROM = function(title, data) { loadROM(title, data) {
Javatari.loadROM(title, data); Javatari.loadROM(title, data);
this.current_output = data; // TODO: use bus this.current_output = data; // TODO: use bus
} }
this.getOpcodeMetadata = function(opcode, offset) { getOpcodeMetadata(opcode, offset) {
return Javatari.getOpcodeMetadata(opcode, offset); return Javatari.getOpcodeMetadata(opcode, offset);
} }
this.getRasterPosition = function() { getRasterPosition() {
var clkfs = Javatari.room.console.getClocksFromFrameStart() - 1; var clkfs = Javatari.room.console.getClocksFromFrameStart() - 1;
var row = Math.floor(clkfs/76); var row = Math.floor(clkfs/76);
var col = clkfs - row*76; var col = clkfs - row*76;
@ -84,10 +87,10 @@ var VCSPlatform = function() {
} }
// TODO: Clock changes this on event, so it may not be current // TODO: Clock changes this on event, so it may not be current
this.isRunning = function() { isRunning() {
return Javatari.room && Javatari.room.console.isRunning(); return Javatari.room && Javatari.room.console.isRunning();
} }
this.pause = function() { pause() {
//console.log('pause', this.paused, this.isRunning()); //console.log('pause', this.paused, this.isRunning());
if (!this.paused) { if (!this.paused) {
this.paused = true; this.paused = true;
@ -95,7 +98,7 @@ var VCSPlatform = function() {
Javatari.room.speaker.mute(); Javatari.room.speaker.mute();
} }
} }
this.resume = function() { resume() {
//console.log('resume', this.paused, this.isRunning()); //console.log('resume', this.paused, this.isRunning());
if (this.paused) { if (this.paused) {
this.paused = false; this.paused = false;
@ -103,51 +106,52 @@ var VCSPlatform = function() {
Javatari.room.speaker.play(); Javatari.room.speaker.play();
} }
} }
this.advance = function() { advance() {
Javatari.room.console.clockPulse(); Javatari.room.console.clockPulse();
} }
this.step = function() { Javatari.room.console.debugSingleStepCPUClock(); } step() { Javatari.room.console.debugSingleStepCPUClock(); }
this.stepBack = function() { Javatari.room.console.debugStepBackInstruction(); } stepBack() { Javatari.room.console.debugStepBackInstruction(); }
this.runEval = function(evalfunc) { Javatari.room.console.debugEval(evalfunc); } runEval(evalfunc) { Javatari.room.console.debugEval(evalfunc); }
this.setupDebug = function(callback) { setupDebug(callback) {
Javatari.room.console.onBreakpointHit = function(state) { Javatari.room.console.onBreakpointHit = (state) => {
self.paused = true; this.paused = true;
callback(state); callback(state);
} }
Javatari.room.speaker.mute(); Javatari.room.speaker.mute();
} }
this.clearDebug = function() { clearDebug() {
Javatari.room.console.disableDebug(); Javatari.room.console.disableDebug();
Javatari.room.console.onBreakpointHit = null; Javatari.room.console.onBreakpointHit = null;
if (this.isRunning()) Javatari.room.speaker.play(); if (this.isRunning()) Javatari.room.speaker.play();
} }
this.reset = function() { reset() {
Javatari.room.console.powerOff(); Javatari.room.console.powerOff();
Javatari.room.console.resetDebug(); Javatari.room.console.resetDebug();
Javatari.room.console.powerOn(); Javatari.room.console.powerOn();
Javatari.room.speaker.play(); Javatari.room.speaker.play();
} }
this.getOriginPC = function() { getOriginPC() {
return (this.readAddress(0xfffc) | (this.readAddress(0xfffd) << 8)) & 0xffff; return (this.readAddress(0xfffc) | (this.readAddress(0xfffd) << 8)) & 0xffff;
} }
this.newCodeAnalyzer = function() { newCodeAnalyzer() {
return new CodeAnalyzer_vcs(this); return new CodeAnalyzer_vcs(this);
} }
this.saveState = function() { saveState() {
return Javatari.room.console.saveState(); return Javatari.room.console.saveState();
} }
this.loadState = function(state) { loadState(state) {
return Javatari.room.console.loadState(state); return Javatari.room.console.loadState(state);
} }
this.readAddress = function(addr) { // TODO: load/save controls state
readAddress(addr) {
return this.current_output[addr & 0xfff]; // TODO: use bus to read return this.current_output[addr & 0xfff]; // TODO: use bus to read
} }
this.runUntilReturn = function() { runUntilReturn() {
var depth = 1; var depth = 1;
self.runEval(function(c) { this.runEval(function(c) {
if (depth <= 0 && c.T == 0) if (depth <= 0 && c.T == 0)
return true; return true;
if (c.o == 0x20) if (c.o == 0x20)
@ -157,38 +161,38 @@ var VCSPlatform = function() {
return false; return false;
}); });
} }
this.cpuStateToLongString = function(c) { cpuStateToLongString(c) {
return cpuStateToLongString_6502(c); return cpuStateToLongString_6502(c);
} }
this.getRAMForState = function(state) { getRAMForState(state) {
return jt.Util.byteStringToUInt8Array(atob(state.r.b)); return jt.Util.byteStringToUInt8Array(atob(state.r.b));
} }
this.ramStateToLongString = function(state) { ramStateToLongString(state) {
var ram = self.getRAMForState(state); var ram = this.getRAMForState(state);
return "\n" + dumpRAM(ram, 0x80, 0x80); return "\n" + dumpRAM(ram, 0x80, 0x80);
} }
this.getToolForFilename = function(fn) { getToolForFilename(fn) {
return "dasm"; return "dasm";
} }
this.getDefaultExtension = function() { return ".a"; }; getDefaultExtension() { return ".a"; };
this.getDebugCategories = function() { getDebugCategories() {
return ['CPU','PIA','TIA']; return ['CPU','PIA','TIA'];
} }
this.getDebugInfo = function(category, state) { getDebugInfo(category, state) {
switch (category) { switch (category) {
case 'CPU': return this.cpuStateToLongString(state.c); case 'CPU': return this.cpuStateToLongString(state.c);
case 'PIA': return this.ramStateToLongString(state) + "\n" + this.piaStateToLongString(state.p); case 'PIA': return this.ramStateToLongString(state) + "\n" + this.piaStateToLongString(state.p);
case 'TIA': return this.tiaStateToLongString(state.t); case 'TIA': return this.tiaStateToLongString(state.t);
} }
} }
this.piaStateToLongString = function(p) { piaStateToLongString(p) {
return "Timer " + p.t + "/" + p.c + "\n"; return "Timer " + p.t + "/" + p.c + "\n";
} }
this.tiaStateToLongString = function(t) { tiaStateToLongString(t) {
var pos = this.getRasterPosition(); var pos = this.getRasterPosition();
var s = ''; var s = '';
s += "H" + lpad(pos.x,5) + " V" + lpad(pos.y,5) + " "; s += "H" + lpad(pos.x.toString(),5) + " V" + lpad(pos.y.toString(),5) + " ";
s += (t.vs?"VSYNC ":"- ") + (t.vb?"VBLANK ":"- ") + "\n"; s += (t.vs?"VSYNC ":"- ") + (t.vb?"VBLANK ":"- ") + "\n";
s += "\n"; s += "\n";
s += "Playfield " + t.f + "\n"; s += "Playfield " + t.f + "\n";
@ -214,13 +218,13 @@ var VCSPlatform = function() {
return s; return s;
} }
this.setRecorder = function(recorder : EmuRecorder) : void { setRecorder(recorder : EmuRecorder) : void {
this.recorder = recorder; this.recorder = recorder;
} }
this.updateRecorder = function() { updateRecorder() {
// are we recording and do we need to save a frame? // are we recording and do we need to save a frame?
if (this.recorder && !this.paused && this.isRunning() && this.recorder.frameRequested()) { if (this.recorder && !this.paused && this.isRunning() && this.recorder.frameRequested()) {
this.recorder.recordFrame(this, this.saveState()); this.recorder.recordFrame(this.saveState());
} }
} }
}; };
@ -231,13 +235,12 @@ function nonegstr(n) {
/////////////// ///////////////
var VCSMAMEPlatform = function(mainElement) { class VCSMAMEPlatform extends BaseMAMEPlatform {
var self = this;
this.__proto__ = new BaseMAMEPlatform();
// MCFG_SCREEN_RAW_PARAMS( MASTER_CLOCK_NTSC, 228, 26, 26 + 160 + 16, 262, 24 , 24 + 192 + 31 ) // MCFG_SCREEN_RAW_PARAMS( MASTER_CLOCK_NTSC, 228, 26, 26 + 160 + 16, 262, 24 , 24 + 192 + 31 )
this.start = function() {
self.startModule(mainElement, { start = function() {
this.startModule(this.mainElement, {
jsfile:'mamea2600.js', jsfile:'mamea2600.js',
driver:'a2600', driver:'a2600',
width:176*2, width:176*2,
@ -247,27 +250,21 @@ var VCSMAMEPlatform = function(mainElement) {
}); });
} }
this.loadROM = function(title, data) { loadROM = function(title, data) {
this.loadROMFile(data); this.loadROMFile(data);
this.loadRegion(":cartslot:cart:rom", data); this.loadRegion(":cartslot:cart:rom", data);
} }
this.getPresets = function() { return VCS_PRESETS; } getPresets = function() { return VCS_PRESETS; }
this.getToolForFilename = function(fn) { getToolForFilename = function(fn) {
return "dasm"; return "dasm";
} }
this.getDefaultExtension = function() { return ".a"; }; getDefaultExtension = function() { return ".a"; };
this.getOriginPC = function() { getOriginPC = function() {
return (this.readAddress(0xfffc) | (this.readAddress(0xfffd) << 8)) & 0xffff; return (this.readAddress(0xfffc) | (this.readAddress(0xfffd) << 8)) & 0xffff;
} }
/*
this.getOpcodeMetadata = function(opcode, offset) {
return Javatari.getOpcodeMetadata(opcode, offset);
}
*/
} }
//////////////// ////////////////

View File

@ -169,6 +169,14 @@ var AtariVectorPlatform = function(mainElement) {
nmic:nmicount nmic:nmicount
} }
} }
this.loadControlsState = function(state) {
switches.set(state.sw);
}
this.saveControlsState = function() {
return {
sw:switches.slice(0),
}
}
this.getCPUState = function() { this.getCPUState = function() {
return cpu.saveState(); return cpu.saveState();
} }
@ -308,6 +316,14 @@ var AtariColorVectorPlatform = function(mainElement) {
nmic:nmicount nmic:nmicount
} }
} }
this.loadControlsState = function(state) {
switches.set(state.sw);
}
this.saveControlsState = function() {
return {
sw:switches.slice(0),
}
}
this.getCPUState = function() { this.getCPUState = function() {
return cpu.saveState(); return cpu.saveState();
} }
@ -431,6 +447,14 @@ var Z80ColorVectorPlatform = function(mainElement, proto) {
mr:mathram.slice(0), mr:mathram.slice(0),
} }
} }
this.loadControlsState = function(state) {
switches.set(state.sw);
}
this.saveControlsState = function() {
return {
sw:switches.slice(0),
}
}
this.getCPUState = function() { this.getCPUState = function() {
return cpu.saveState(); return cpu.saveState();
} }

View File

@ -5,7 +5,7 @@ import { PLATFORMS, RAM, newAddressDecoder, padBytes, noise, setKeyboardFromMap,
import { hex } from "../util"; import { hex } from "../util";
import { MasterAudio, AY38910_Audio } from "../audio"; import { MasterAudio, AY38910_Audio } from "../audio";
var VICDUAL_PRESETS = [ const VICDUAL_PRESETS = [
{id:'minimal.c', name:'Minimal Example'}, {id:'minimal.c', name:'Minimal Example'},
{id:'hello.c', name:'Hello World'}, {id:'hello.c', name:'Hello World'},
{id:'gfxtest.c', name:'Graphics Test'}, {id:'gfxtest.c', name:'Graphics Test'},
@ -15,31 +15,30 @@ var VICDUAL_PRESETS = [
{id:'music.c', name:'Music Player'}, {id:'music.c', name:'Music Player'},
]; ];
var VicDualPlatform = function(mainElement) { const _VicDualPlatform = function(mainElement) {
var self = this;
this.__proto__ = new (BaseZ80Platform as any)();
var cpu, ram, membus, iobus, rom; var cpu, ram, membus, iobus, rom;
var video, audio, psg, timer, pixels; var video, audio, psg, timer, pixels;
var inputs = [0xff, 0xff, 0xff, 0xff^0x8]; // most things active low var inputs = [0xff, 0xff, 0xff, 0xff^0x8]; // most things active low
var palbank = 0; var palbank = 0;
var XTAL = 15468000.0; const XTAL = 15468000.0;
var scanlinesPerFrame = 0x106; const scanlinesPerFrame = 0x106;
var vblankStart = 0xe0; const vblankStart = 0xe0;
var vsyncStart = 0xec; const vsyncStart = 0xec;
var vsyncEnd = 0xf0; const vsyncEnd = 0xf0;
var cpuFrequency = XTAL/8; const cpuFrequency = XTAL/8;
var hsyncFrequency = XTAL/3/scanlinesPerFrame; const hsyncFrequency = XTAL/3/scanlinesPerFrame;
var vsyncFrequency = hsyncFrequency/0x148; const vsyncFrequency = hsyncFrequency/0x148;
var cpuCyclesPerLine = cpuFrequency/hsyncFrequency; const cpuCyclesPerLine = cpuFrequency/hsyncFrequency;
var timerFrequency = 500; // input 2 bit 0x8 const timerFrequency = 500; // input 2 bit 0x8
var cyclesPerTimerTick = cpuFrequency / (2 * timerFrequency); const cyclesPerTimerTick = cpuFrequency / (2 * timerFrequency);
var reset_disable = false; var reset_disable = false;
var reset_disable_timer; var reset_disable_timer;
var framestats; var framestats;
var palette = [ const palette = [
0xff000000, // black 0xff000000, // black
0xff0000ff, // red 0xff0000ff, // red
0xff00ff00, // green 0xff00ff00, // green
@ -50,6 +49,7 @@ var VicDualPlatform = function(mainElement) {
0xffffffff // white 0xffffffff // white
]; ];
// default PROM
var colorprom = [ var colorprom = [
0xe0,0x60,0x20,0x60, 0xc0,0x60,0x40,0xc0, 0xe0,0x60,0x20,0x60, 0xc0,0x60,0x40,0xc0,
0x20,0x40,0x60,0x80, 0xa0,0xc0,0xe0,0x0e, 0x20,0x40,0x60,0x80, 0xa0,0xc0,0xe0,0x0e,
@ -85,7 +85,7 @@ var VicDualPlatform = function(mainElement) {
} }
} }
var CARNIVAL_KEYCODE_MAP = makeKeycodeMap([ const CARNIVAL_KEYCODE_MAP = makeKeycodeMap([
[Keys.VK_SPACE, 2, -0x20], [Keys.VK_SPACE, 2, -0x20],
[Keys.VK_SHIFT, 2, -0x40], [Keys.VK_SHIFT, 2, -0x40],
[Keys.VK_LEFT, 1, -0x10], [Keys.VK_LEFT, 1, -0x10],
@ -97,11 +97,13 @@ var VicDualPlatform = function(mainElement) {
[Keys.VK_5, 3, 0x8], [Keys.VK_5, 3, 0x8],
]); ]);
this.getPresets = function() { class VicDualPlatform extends BaseZ80Platform {
getPresets() {
return VICDUAL_PRESETS; return VICDUAL_PRESETS;
} }
this.start = function() { start() {
ram = new RAM(0x1000); ram = new RAM(0x1000);
membus = { membus = {
read: newAddressDecoder([ read: newAddressDecoder([
@ -113,7 +115,6 @@ var VicDualPlatform = function(mainElement) {
]), ]),
isContended: function() { return false; }, isContended: function() { return false; },
}; };
this.readAddress = membus.read;
iobus = { iobus = {
read: function(addr) { read: function(addr) {
return inputs[addr&3]; return inputs[addr&3];
@ -145,7 +146,11 @@ var VicDualPlatform = function(mainElement) {
timer = new AnimationTimer(60, this.advance.bind(this)); timer = new AnimationTimer(60, this.advance.bind(this));
} }
this.advance = function(novideo : boolean) { readAddress(addr) {
return membus.read(addr);
}
advance(novideo : boolean) {
var targetTstates = cpu.getTstates(); var targetTstates = cpu.getTstates();
for (var sl=0; sl<scanlinesPerFrame; sl++) { for (var sl=0; sl<scanlinesPerFrame; sl++) {
inputs[2] &= ~0x8; inputs[2] &= ~0x8;
@ -162,26 +167,23 @@ var VicDualPlatform = function(mainElement) {
this.restartDebugState(); this.restartDebugState();
} }
this.loadROM = function(title, data) { loadROM(title, data) {
if (data.length >= 0x4020 && (data[0x4000] || data[0x401f])) { if (data.length >= 0x4020 && (data[0x4000] || data[0x401f])) {
colorprom = data.slice(0x4000,0x4020); colorprom = data.slice(0x4000,0x4020);
} }
rom = padBytes(data, 0x4040); rom = padBytes(data, 0x4040);
self.reset(); this.reset();
} }
this.loadState = function(state) { loadState(state) {
cpu.loadState(state.c); cpu.loadState(state.c);
ram.mem.set(state.b); ram.mem.set(state.b);
inputs[0] = state.in0; this.loadControlsState(state);
inputs[1] = state.in1;
inputs[2] = state.in2;
inputs[3] = state.in3;
palbank = state.pb; palbank = state.pb;
} }
this.saveState = function() { saveState() {
return { return {
c:self.getCPUState(), c:this.getCPUState(),
b:ram.mem.slice(0), b:ram.mem.slice(0),
in0:inputs[0], in0:inputs[0],
in1:inputs[1], in1:inputs[1],
@ -190,32 +192,48 @@ var VicDualPlatform = function(mainElement) {
pb:palbank, pb:palbank,
}; };
} }
this.getCPUState = function() { loadControlsState(state) {
inputs[0] = state.in0;
inputs[1] = state.in1;
inputs[2] = state.in2;
inputs[3] = state.in3;
}
saveControlsState() {
return {
in0:inputs[0],
in1:inputs[1],
in2:inputs[2],
in3:inputs[3]
};
}
getCPUState() {
return cpu.saveState(); return cpu.saveState();
} }
this.isRunning = function() { isRunning() {
return timer && timer.isRunning(); return timer && timer.isRunning();
} }
this.pause = function() { pause() {
timer.stop(); timer.stop();
audio.stop(); audio.stop();
} }
this.resume = function() { resume() {
timer.start(); timer.start();
audio.start(); audio.start();
} }
this.reset = function() { reset() {
cpu.reset(); cpu.reset();
psg.reset(); psg.reset();
if (!this.getDebugCallback()) cpu.setTstates(0); // TODO? if (!this.getDebugCallback()) cpu.setTstates(0); // TODO?
} }
this.setFrameStats = function(on) { setFrameStats(on) {
framestats = on ? { framestats = on ? {
palette: palette, palette: palette,
layers: {width:256, height:224, tiles:[]} layers: {width:256, height:224, tiles:[]}
} : null; } : null;
} }
}
return new VicDualPlatform();
} }
PLATFORMS['vicdual'] = VicDualPlatform; PLATFORMS['vicdual'] = _VicDualPlatform;

View File

@ -364,7 +364,7 @@ var WilliamsPlatform = function(mainElement, proto) {
cpu.loadState(state.c); cpu.loadState(state.c);
ram.mem.set(state.b); ram.mem.set(state.b);
nvram.mem.set(state.nvram); nvram.mem.set(state.nvram);
//pia6821.set(state.pia); pia6821.set(state.pia);
blitregs.set(state.blt); blitregs.set(state.blt);
watchdog_counter = state.wdc; watchdog_counter = state.wdc;
banksel = state.bs; banksel = state.bs;
@ -382,6 +382,14 @@ var WilliamsPlatform = function(mainElement, proto) {
ps:portsel, ps:portsel,
}; };
} }
this.loadControlsState = function(state) {
pia6821.set(state.pia);
}
this.saveControlsState = function() {
return {
pia:pia6821.slice(0),
};
}
this.getCPUState = function() { this.getCPUState = function() {
return cpu.saveState(); return cpu.saveState();
} }

View File

@ -951,6 +951,13 @@ function loadSharedFile(sharekey : string) {
return true; return true;
} }
function loadScript(scriptfn, onload) {
var script = document.createElement('script');
script.onload = onload;
script.src = scriptfn;
document.getElementsByTagName('head')[0].appendChild(script);
}
// start // start
function startUI(loadplatform : boolean) { function startUI(loadplatform : boolean) {
installErrorHandler(); installErrorHandler();
@ -975,14 +982,11 @@ function startUI(loadplatform : boolean) {
// load and start platform object // load and start platform object
if (loadplatform) { if (loadplatform) {
var scriptfn = 'gen/platform/' + platform_id.split(/[.-]/)[0] + '.js'; var scriptfn = 'gen/platform/' + platform_id.split(/[.-]/)[0] + '.js';
var script = document.createElement('script'); loadScript(scriptfn, () => {
script.onload = function() {
console.log("loaded platform", platform_id); console.log("loaded platform", platform_id);
startPlatform(); startPlatform();
showWelcomeMessage(); showWelcomeMessage();
}; });
script.src = scriptfn;
document.getElementsByTagName('head')[0].appendChild(script);
} else { } else {
startPlatform(); startPlatform();
showWelcomeMessage(); showWelcomeMessage();