started on MAME platform support; pause on page lose focus

This commit is contained in:
Steven Hugg 2017-05-01 11:30:47 -04:00
parent 992bf927e0
commit 4974e395e5
7 changed files with 474 additions and 12 deletions

View File

@ -193,6 +193,8 @@ a.dropdown-toggle {
margin-bottom: 20px;
background: #000;
outline-color: #666;
width: 100%;
height: 100%;
}
canvas.pixelated {
image-rendering: optimizeSpeed; /* Older versions of FF */

View File

@ -29,3 +29,9 @@ debugjs/%.js: js/%.bc
-s FORCE_FILESYSTEM=1 \
$< -o $@ $(ARGS_$*) \
js/fssdcc.js:
ln -s ./sdcc/device/include include
ln -s ./sdcc/device/lib/build lib
python $(EMSCRIPTEN)/tools/file_packager.py js/fssdcc.data \
--preload include lib/z80 \
--separate-metadata --js-output=js/fssdcc.js

68
mame/cfg/coleco.cfg Normal file
View File

@ -0,0 +1,68 @@
<?xml version="1.0"?>
<!-- This file is autogenerated; comments and unknown tags will be stripped -->
<mameconfig version="10">
<system name="coleco">
<input>
<port tag=":STD_KEYPAD1" type="KEYPAD" mask="1" defvalue="1">
<newseq type="standard">
KEYCODE_0
</newseq>
</port>
<port tag=":STD_KEYPAD1" type="KEYPAD" mask="2" defvalue="2">
<newseq type="standard">
KEYCODE_1
</newseq>
</port>
<port tag=":STD_KEYPAD1" type="KEYPAD" mask="4" defvalue="4">
<newseq type="standard">
KEYCODE_2
</newseq>
</port>
<port tag=":STD_KEYPAD1" type="KEYPAD" mask="8" defvalue="8">
<newseq type="standard">
KEYCODE_3
</newseq>
</port>
<port tag=":STD_KEYPAD1" type="KEYPAD" mask="16" defvalue="16">
<newseq type="standard">
KEYCODE_4
</newseq>
</port>
<port tag=":STD_KEYPAD1" type="KEYPAD" mask="32" defvalue="32">
<newseq type="standard">
KEYCODE_5
</newseq>
</port>
<port tag=":STD_KEYPAD1" type="KEYPAD" mask="64" defvalue="64">
<newseq type="standard">
KEYCODE_6
</newseq>
</port>
<port tag=":STD_KEYPAD1" type="KEYPAD" mask="128" defvalue="128">
<newseq type="standard">
KEYCODE_7
</newseq>
</port>
<port tag=":STD_KEYPAD1" type="KEYPAD" mask="256" defvalue="256">
<newseq type="standard">
KEYCODE_8
</newseq>
</port>
<port tag=":STD_KEYPAD1" type="KEYPAD" mask="512" defvalue="512">
<newseq type="standard">
KEYCODE_9
</newseq>
</port>
<port tag=":STD_KEYPAD1" type="KEYPAD" mask="1024" defvalue="1024">
<newseq type="standard">
KEYCODE_MINUS
</newseq>
</port>
<port tag=":STD_KEYPAD1" type="KEYPAD" mask="2048" defvalue="2048">
<newseq type="standard">
KEYCODE_EQUALS
</newseq>
</port>
</input>
</system>
</mameconfig>

View File

@ -19,8 +19,6 @@ function __createCanvas(mainElement, width, height) {
canvas.width = width;
canvas.height = height;
canvas.classList.add("emuvideo");
canvas.style.width = "100%";
canvas.style.height = "100%";
canvas.tabIndex = "-1"; // Make it focusable
fsElement.appendChild(canvas);
@ -594,17 +592,19 @@ var BaseZ80Platform = function() {
this.cpuStateToLongString = function(c) {
return cpuStateToLongString_Z80(c);
}
this.getToolForFilename = function(fn) {
if (fn.endsWith(".c")) return "sdcc";
if (fn.endsWith(".s")) return "sdasz80";
if (fn.endsWith(".ns")) return "naken";
return "z80asm";
}
this.getToolForFilename = getToolForFilename_z80;
this.getDefaultExtension = function() { return ".c"; };
// TODO
//this.getOpcodeMetadata = function() { }
}
function getToolForFilename_z80(fn) {
if (fn.endsWith(".c")) return "sdcc";
if (fn.endsWith(".s")) return "sdasz80";
if (fn.endsWith(".ns")) return "naken";
return "z80asm";
}
////// 6809
function cpuStateToLongString_6809(c) {
@ -862,3 +862,120 @@ var BusProbe = function(bus) {
bus.write(a,v);
}
}
/// MAME SUPPORT
var BaseMAMEPlatform = function() {
var self = this;
var loaded = false;
var romfn;
var romdata;
var video;
var preload_files;
this.luacall = function(s) {
//console.log(s);
Module.ccall('_Z13js_lua_stringPKc', 'void', ['string'], [s+""]);
}
this.clearDebug = function() {
//TODO
}
this.pause = function() {
if (loaded) this.luacall('emu.pause()');
}
this.resume = function() {
if (loaded) this.luacall('emu.unpause()');
}
this.reset = function() {
this.luacall('manager:machine():soft_reset()');
}
this.isRunning = function() {
// TODO
}
this.startModule = function(mainElement, opts) {
romfn = opts.romfn;
if (!romdata) romdata = new RAM(opts.romsize).mem;
// create canvas
video = new RasterVideo(mainElement, opts.width, opts.height);
video.create();
$(video.canvas).attr('id','canvas');
// load asm.js module
console.log("loading", opts.jsfile);
var script = document.createElement('script');
window.JSMESS = {};
window.Module = {
arguments: [opts.driver, '-verbose', '-window', '-nokeepaspect', '-resolution', canvas.width+'x'+canvas.height, '-cart', romfn],
screenIsReadOnly: true,
print: function (text) { console.log(text); },
canvas:video.canvas,
doNotCaptureKeyboard:true,
keyboardListeningElement:video.canvas,
preInit: function () {
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');
FS.writeFile(romfn, romdata, {encoding:'binary'});
if (opts.preInit) {
opts.preInit(self);
}
$(video.canvas).click(function(e) {
video.canvas.focus();
});
loaded = true;
}
};
// preload files
// TODO: ensure loaded
if (opts.cfgfile) {
$.get('mame/cfg/' + opts.cfgfile, function(data) {
opts.cfgdata = data;
console.log("loaded " + opts.cfgfile);
}, 'text');
}
if (opts.biosfile) {
var oReq = new XMLHttpRequest();
oReq.open("GET", 'mame/roms/' + opts.biosfile, true);
oReq.responseType = "arraybuffer";
oReq.onload = function(oEvent) {
console.log("loaded " + opts.biosfile);
opts.biosdata = new Uint8Array(oReq.response);
};
oReq.send();
}
// start loading script
script.src = 'mame/' + opts.jsfile;
document.getElementsByTagName('head')[0].appendChild(script);
}
this.loadRegion = function(region, data) {
romdata = data;
if (loaded) {
FS.writeFile(romfn, data, {encoding:'binary'});
//self.luacall('cart=manager:machine().images["cart"]\nprint(cart:filename())\ncart:load("' + romfn + '")\n');
var s = 'mem = manager:machine():memory().regions["' + region + '"]\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 += 'mem:write_u32(' + i + ',' + v + ')\n'; // TODO: endian?
}
self.luacall(s);
self.reset();
}
}
}

217
src/platform/coleco.js Normal file
View File

@ -0,0 +1,217 @@
"use strict";
// http://www.colecovision.eu/ColecoVision/development/tutorial1.shtml
// http://www.colecovision.eu/ColecoVision/development/libcv.shtml
// http://www.kernelcrash.com/blog/recreating-the-colecovision/2016/01/27/
// http://www.atarihq.com/danb/files/CV-Tech.txt
// http://www.colecoboxart.com/faq/FAQ05.htm
var ColecoVision_PRESETS = [
{id:'minimal.c', name:'Minimal Example'},
];
var ColecoVisionPlatform = function(mainElement) {
var self = this;
this.__proto__ = new BaseZ80Platform();
var cpu, ram, membus, iobus, rom, bios;
var video, audio, psg, timer, pixels;
var inputs = [0xff, 0xff, 0xff, 0xff^0x8]; // most things active low
var palbank = 0;
var XTAL = 3579545*2;
var totalScanlinesPerFrame = 262.5;
var visibleScanlinesPerFrame = 192;
var visiblePixelsPerScanline = 256;
var cpuFrequency = XTAL/2;
var hsyncFrequency = XTAL*3/(2*322);
var vsyncFrequency = hsyncFrequency/totalScanlinesPerFrame;
var cpuCyclesPerLine = cpuFrequency/hsyncFrequency;
var framestats;
function RGB(r,g,b) {
return (r << 0) + (g << 8) + (b << 16) | 0xff000000;
}
var palette = [
RGB(0x00,0x00,0x00),RGB(0x00,0x00,0x00),RGB(0x47,0xB7,0x3B),RGB(0x7C,0xCF,0x6F),
RGB(0x5D,0x4E,0xFF),RGB(0x80,0x72,0xFF),RGB(0xB6,0x62,0x47),RGB(0x5D,0xC8,0xED),
RGB(0xD7,0x6B,0x48),RGB(0xFB,0x8F,0x6C),RGB(0xC3,0xCD,0x41),RGB(0xD3,0xDA,0x76),
RGB(0x3E,0x9F,0x2F),RGB(0xB6,0x64,0xC7),RGB(0xCC,0xCC,0xCC),RGB(0xFF,0xFF,0xFF)
];
// videoram 0xc000-0xc3ff
// RAM 0xc400-0xc7ff
// charram 0xc800-0xcfff
function drawScanline(pixels, sl) {
if (sl >= visibleScanlinesPerFrame) return;
var pixofs = sl * 256;
var outi = pixofs; // starting output pixel in frame buffer
var vramofs = (sl>>3)<<5; // offset in VRAM
var yy = sl & 7; // y offset within tile
for (var xx=0; xx<32; xx++) {
var code = ram.mem[vramofs+xx];
var data = ram.mem[0x800 + (code<<3) + yy];
var col = (code>>5) + (palbank<<3);
var color1 = palette[col&15];
var color2 = 0;
for (var i=0; i<8; i++) {
var bm = 128>>i;
pixels[outi] = (data&bm) ? color2 : color1;
outi++;
}
}
}
var CARNIVAL_KEYCODE_MAP = makeKeycodeMap([
[Keys.VK_SPACE, 2, -0x20],
[Keys.VK_SHIFT, 2, -0x40],
[Keys.VK_LEFT, 1, -0x10],
[Keys.VK_RIGHT, 1, -0x20],
[Keys.VK_UP, 1, -0x40],
[Keys.VK_DOWN, 1, -0x80],
[Keys.VK_1, 2, -0x10],
[Keys.VK_2, 3, -0x20],
[Keys.VK_5, 3, 0x8],
]);
this.getPresets = function() {
return ColecoVision_PRESETS;
}
this.start = function() {
ram = new RAM(0x400);
//bios = COLECO_BIOS;
membus = {
read: new AddressDecoder([
[0x0000, 0x1fff, 0x1fff, function(a) { return bios ? bios[a] : null; }],
[0x6000, 0x7fff, 0x3ff, function(a) { return ram.mem[a]; }],
]),
write: new AddressDecoder([
[0x6000, 0x7fff, 0x3ff, function(a,v) { ram.mem[a] = v; }],
]),
isContended: function() { return false; },
};
this.readAddress = membus.read;
iobus = {
read: function(addr) {
return inputs[addr&3];
},
write: function(addr, val) {
console.log(addr,val);
}
};
cpu = this.newCPU(membus, iobus);
video = new RasterVideo(mainElement,visiblePixelsPerScanline,visibleScanlinesPerFrame);
audio = new MasterAudio();
psg = new AY38910_Audio(audio);
//var speech = new VotraxSpeech();
//audio.master.addChannel(speech);
video.create();
var idata = video.getFrameData();
setKeyboardFromMap(video, inputs, CARNIVAL_KEYCODE_MAP);
pixels = video.getFrameData();
timer = new AnimationTimer(60, function() {
if (!self.isRunning())
return;
var debugCond = self.getDebugCallback();
var targetTstates = cpu.getTstates();
for (var sl=0; sl<visibleScanlinesPerFrame; sl++) {
drawScanline(pixels, sl);
targetTstates += cpuCyclesPerLine;
self.runCPU(cpu, targetTstates - cpu.getTstates());
}
video.updateFrame();
self.restartDebugState();
});
}
this.loadROM = function(title, data) {
rom = padBytes(data, 0x8000);
self.reset();
}
this.loadState = function(state) {
cpu.loadState(state.c);
ram.mem.set(state.b);
inputs[0] = state.in0;
inputs[1] = state.in1;
inputs[2] = state.in2;
inputs[3] = state.in3;
palbank = state.pb;
}
this.saveState = function() {
return {
c:self.getCPUState(),
b:ram.mem.slice(0),
in0:inputs[0],
in1:inputs[1],
in2:inputs[2],
in3:inputs[3],
pb:palbank,
};
}
this.getCPUState = function() {
return cpu.saveState();
}
this.isRunning = function() {
return timer && timer.isRunning();
}
this.pause = function() {
timer.stop();
audio.stop();
}
this.resume = function() {
timer.start();
audio.start();
}
this.reset = function() {
cpu.reset();
psg.reset();
if (!this.getDebugCallback()) cpu.setTstates(0); // TODO?
}
}
/// MAME support
var ColecoVisionMAMEPlatform = function(mainElement) {
var self = this;
this.__proto__ = new BaseMAMEPlatform();
//
this.start = function() {
self.startModule(mainElement, {
jsfile:'mamecoleco.js',
cfgfile:'coleco.cfg',
biosfile:'coleco/313 10031-4005 73108a.u2',
driver:'coleco',
width:280*2,
height:216*2,
romfn:'/emulator/cart.rom',
romsize:0x8000,
preInit:function(_self) {
/*
console.log("Writing BIOS");
var dir = '/roms/coleco';
FS.mkdir('/roms');
FS.mkdir(dir);
FS.writeFile(dir + "/313 10031-4005 73108a.u2", COLECO_BIOS, {encoding:'binary'});
*/
},
});
}
this.loadROM = function(title, data) {
this.loadRegion(":coleco_cart:rom", data);
}
this.getPresets = function() { return ColecoVision_PRESETS; }
this.getToolForFilename = getToolForFilename_z80;
this.getDefaultExtension = function() { return ".c"; };
}
///
PLATFORMS['coleco'] = ColecoVisionMAMEPlatform;

View File

@ -125,8 +125,6 @@ var VCSPlatform = function() {
this.getDefaultExtension = function() { return ".a"; };
};
PLATFORMS['vcs'] = VCSPlatform;
/// VCS TIMING ANALYSIS
var pc2minclocks = {};
@ -268,3 +266,38 @@ function traceTiming() {
trace_pending_at_pc = platform.getOriginPC();
setCode(editor.getValue());
}
///////////////
var VCSMAMEPlatform = function(mainElement) {
var self = this;
this.__proto__ = new BaseMAMEPlatform();
// MCFG_SCREEN_RAW_PARAMS( MASTER_CLOCK_NTSC, 228, 26, 26 + 160 + 16, 262, 24 , 24 + 192 + 31 )
this.start = function() {
self.startModule(mainElement, {
jsfile:'mamea2600.js',
driver:'a2600',
width:176*2,
height:223,
romfn:'/emulator/cart.rom',
romsize:0x1000,
});
}
this.loadROM = function(title, data) {
this.loadRegion(":cartslot:cart:rom", data);
}
this.getPresets = function() { return VCS_PRESETS; }
this.getToolForFilename = function(fn) {
return "dasm";
}
this.getDefaultExtension = function() { return ".a"; };
}
////////////////
PLATFORMS['vcs'] = VCSPlatform;
PLATFORMS['vcs-mame'] = VCSMAMEPlatform;

View File

@ -1111,6 +1111,21 @@ function showBookLink() {
$("#booklink_arcade").show();
}
function addPageFocusHandlers() {
document.addEventListener("visibilitychange", function() {
if (document.visibilityState == 'hidden')
platform.pause();
else if (document.visibilityState == 'visible')
platform.resume();
});
window.onfocus = function() {
platform.resume();
};
window.onblur = function() {
platform.pause();
};
}
function startPlatform() {
initPlatform();
if (!PLATFORMS[platform_id]) throw Error("Invalid platform '" + platform_id + "'.");
@ -1124,6 +1139,7 @@ function startPlatform() {
loadPreset(qs['file']);
updateSelector();
showBookLink();
addPageFocusHandlers();
return true;
} else {
// try to load last file (redirect)
@ -1180,11 +1196,14 @@ function startUI(loadplatform) {
// load and start platform object
if (loadplatform) {
var scriptfn = 'src/platform/' + platform_id.split('-')[0] + '.js';
$.getScript(scriptfn, function() {
var script = document.createElement('script');
script.onload = function() {
console.log("loaded platform", platform_id);
startPlatform();
showWelcomeMessage();
});
};
script.src = scriptfn;
document.getElementsByTagName('head')[0].appendChild(script);
} else {
startPlatform();
showWelcomeMessage();