system-7/index_files/loader.js

1755 lines
75 KiB
JavaScript
Raw Normal View History

2018-09-01 05:07:00 +00:00
/** The Emularity; easily embed emulators
* Copyright © 2014-2016 Daniel Brooks <db48x@db48x.net>, Jason
* Scott <jscott@archive.org>, Grant Galitz <grantgalitz@gmail.com>,
* John Vilk <jvilk@cs.umass.edu>, and Tracey Jaquith <tracey@archive.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
var Module = null;
(function (Promise) {
/**
* IALoader
*/
function IALoader(canvas, game, callbacks, scale) {
// IA actually gives us an object here, and we really ought to be
// looking things up from it instead.
if (typeof game !== 'string') {
game = game.toString();
}
if (!callbacks || typeof callbacks !== 'object') {
callbacks = { before_emulator: updateLogo,
before_run: callbacks };
} else {
if (typeof callbacks.before_emulator === 'function') {
var func = callbacks.before_emulator;
callbacks.before_emulator = function () {
updateLogo();
func();
};
} else {
callbacks.before_emulator = updateLogo;
}
}
function img(src) {
var img = new Image();
img.src = src;
return img;
}
// yea, this is a hack
var images;
if (/archive\.org$/.test(document.location.hostname)) {
images = { ia: img("/images/ialogo.png"),
mame: img("/images/mame.png"),
mess: img("/images/mame.png"),
dosbox: img("/images/dosbox.png"),
sae: img("/images/sae.png"),
pce: img("/images/pce.png")
};
} else {
images = { ia: img("other_logos/ia-logo-150x150.png"),
mame: img("other_logos/mame.png"),
mess: img("other_logos/mame.png"),
dosbox: img("other_logos/dosbox.png"),
sae: img("other_logos/sae.png"),
pce: img("other_logos/pce.png")
};
}
function updateLogo() {
/*
if (emulator_logo) {
emulator.setSplashImage(emulator_logo);
}
*/
}
var SAMPLE_RATE = (function () {
var audio_ctx = window.AudioContext || window.webkitAudioContext || false;
if (!audio_ctx) {
return false;
}
var sample = new audio_ctx;
return sample.sampleRate.toString();
}());
var metadata, filelist, module, modulecfg, config_args, emulator_logo,
emulator = new Emulator(canvas).setScale(scale)
//.setSplashImage(images.ia) // BORA
.setLoad(loadFiles)
.setCallbacks(callbacks);
var cfgr;
function loadFiles(fetch_file, splash) {
splash.setTitle("Downloading metadata...");
return new Promise(function (resolve, reject) {
var loading = fetch_file('Metadata',
get_meta_url(game),
'document');
loading.then(function (data) {
metadata = data;
splash.setTitle("Downloading file list...");
return fetch_file('File List',
get_files_url(game),
'document', true);
},
function () {
splash.setTitle("Failed to download IA item metadata!");
splash.failed_loading = true;
reject(1);
})
.then(function (data) {
if (splash.failed_loading) {
return null;
}
filelist = data;
splash.setTitle("Downloading emulator metadata...");
module = metadata.getElementsByTagName("emulator")
.item(0)
.textContent;
return fetch_file('Emulator Metadata',
get_emulator_config_url(module),
'text', true);
},
function () {
if (splash.failed_loading) {
return;
}
splash.setTitle("Failed to download file list!");
splash.failed_loading = true;
reject(2);
})
.then(function (data) {
if (splash.failed_loading) {
return null;
}
modulecfg = JSON.parse(data);
var get_files;
if (module && module.indexOf("dosbox") === 0) {
emulator_logo = images.dosbox;
cfgr = DosBoxLoader;
get_files = get_dosbox_files;
}
else if (module && module.indexOf("sae-") === 0) {
emulator_logo = images.sae;
cfgr = SAELoader;
get_files = get_sae_files;
}
else if (module && module.indexOf("pce-") === 0) {
emulator_logo = images.pce;
cfgr = PCELoader;
get_files = get_pce_files;
}
else if (module) {
emulator_logo = images.mame;
cfgr = MAMELoader;
get_files = get_mame_files;
}
else {
throw new Error("Unknown module type "+ module +"; cannot configure the emulator.");
}
var wantsWASM = modulecfg.wasm_filename && 'WebAssembly' in window;
var nr = modulecfg['native_resolution'];
config_args = [cfgr.emulatorJS(get_js_url(wantsWASM ? modulecfg.wasmjs_filename : modulecfg.js_filename)),
cfgr.emulatorWASM(wantsWASM && get_js_url(modulecfg.wasm_filename)),
cfgr.locateAdditionalEmulatorJS(locateAdditionalJS),
cfgr.fileSystemKey(game),
cfgr.nativeResolution(nr[0], nr[1]),
cfgr.aspectRatio(nr[0] / nr[1]),
cfgr.sampleRate(SAMPLE_RATE)];
if ('keepAspect' in cfgr) {
cfgr.keepAspect(modulecfg.keepAspect);
}
if (/archive\.org$/.test(document.location.hostname)) {
cfgr.muted(!(typeof $ !== 'undefined' && $.cookie && $.cookie('unmute')));
}
if (module && module.indexOf("dosbox") === 0) {
config_args.push(cfgr.startExe(metadata.getElementsByTagName("emulator_start")
.item(0)
.textContent));
} else if (module && module.indexOf("sae-") === 0) {
config_args.push(cfgr.model(modulecfg.driver),
cfgr.rom(modulecfg.bios_filenames));
} else if (module && module.indexOf("pce-") === 0) {
config_args.push(cfgr.model(modulecfg.driver));
} else if (module) { // MAME
config_args.push(cfgr.driver(modulecfg.driver),
cfgr.extraArgs(modulecfg.extra_args));
}
2018-09-02 09:04:52 +00:00
splash.setTitle("Downloading data...");
2018-09-01 05:07:00 +00:00
return Promise.all(get_files(cfgr, metadata, modulecfg, filelist));
},
function () {
if (splash.failed_loading) {
return;
}
splash.setTitle("Failed to download emulator metadata!");
splash.failed_loading = true;
reject(2);
})
.then(function (game_files) {
if (splash.failed_loading) {
return;
}
updateLogo();
resolve(cfgr.apply(null, extend(config_args, game_files)));
},
function () {
if (splash.failed_loading) {
return;
}
splash.setTitle("Failed to configure emulator!");
splash.failed_loading = true;
reject(3);
});
});
}
function locateAdditionalJS(filename) {
if ("file_locations" in modulecfg && filename in modulecfg.file_locations) {
return get_js_url(modulecfg.file_locations[filename]);
}
return get_js_url(filename);
}
function get_dosbox_files(cfgr, metadata, modulecfg, filelist) {
var default_drive = "c", // pick any drive letter as a default
drives = {}, files = [],
meta = dict_from_xml(metadata);
if (game && game.endsWith(".zip")) {
drives[default_drive] = game;
}
files_with_ext_from_filelist(filelist, meta.emulator_ext).forEach(function (file, i) {
drives[default_drive] = file.name;
});
meta_props_matching(meta, /^dosbox_drive_([a-zA-Z])$/).forEach(function (result) {
let key = result[0], match = result[1];
drives[match[1]] = meta[key];
});
var mounts = Object.keys(drives),
len = mounts.length;
mounts.forEach(function (drive, i) {
2018-09-02 09:04:52 +00:00
var title = "Hard Disk ("+ (i+1) +" of "+ len +")",
2018-09-01 05:07:00 +00:00
filename = drives[drive],
url = (filename.includes("/")) ? get_zip_url(filename)
: get_zip_url(filename, get_item_name(game));
if (filename.toLowerCase().endsWith(".zip")) {
files.push(cfgr.mountZip(drive,
cfgr.fetchFile(title, url)));
} else {
files.push(cfgr.mountFile('/'+ filename,
cfgr.fetchFile(title, url)));
}
});
return files;
}
function get_mame_files(cfgr, metadata, modulecfg, filelist) {
var files = [],
bios_files = modulecfg['bios_filenames'];
bios_files.forEach(function (fname, i) {
if (fname) {
var title = "Bios File ("+ (i+1) +" of "+ bios_files.length +")";
files.push(cfgr.mountFile('/'+ fname,
cfgr.fetchFile(title,
get_bios_url(fname))));
}
});
var meta = dict_from_xml(metadata),
peripherals = {},
game_files_counter = {};
files_with_ext_from_filelist(filelist, meta.emulator_ext).forEach(function (file, i) {
game_files_counter[file.name] = 1;
if (modulecfg.peripherals && modulecfg.peripherals[i]) {
peripherals[modulecfg.peripherals[i]] = file.name;
}
});
meta_props_matching(meta, /^mame_peripheral_([a-zA-Z0-9]+)$/).forEach(function (result) {
let key = result[0], match = result[1];
peripherals[match[1]] = meta[key];
game_files_counter[meta[key]] = 1;
});
var game_files = Object.keys(game_files_counter),
len = game_files.length;
game_files.forEach(function (filename, i) {
2018-09-02 09:04:52 +00:00
var title = "Hard Disk ("+ (i+1) +" of "+ len +")",
2018-09-01 05:07:00 +00:00
url = (filename.includes("/")) ? get_zip_url(filename)
: get_zip_url(filename, get_item_name(game));
files.push(cfgr.mountFile('/'+ filename,
cfgr.fetchFile(title, url)));
});
Object.keys(peripherals).forEach(function (periph) {
files.push(cfgr.peripheral(periph, // we're not pushing a 'file' here,
peripherals[periph])); // but that's ok
});
files.push(cfgr.mountFile('/'+ modulecfg['driver'] + '.cfg',
cfgr.fetchOptionalFile("CFG File",
get_other_emulator_config_url(module))));
return files;
}
function get_sae_files(cfgr, metadata, modulecfg, filelist) {
var files = [],
bios_files = modulecfg['bios_filenames'];
bios_files.forEach(function (fname, i) {
if (fname) {
var title = "Bios File ("+ (i+1) +" of "+ bios_files.length +")";
files.push(cfgr.mountFile('/'+ fname,
cfgr.fetchFile(title,
get_bios_url(fname))));
}
});
var meta = dict_from_xml(metadata),
game_files = files_with_ext_from_filelist(filelist, meta.emulator_ext);
game_files.forEach(function (file, i) {
if (file) {
2018-09-02 09:04:52 +00:00
var title = "Hard Disk ("+ (i+1) +" of "+ game_files.length +")",
2018-09-01 05:07:00 +00:00
url = (file.name.includes("/")) ? get_zip_url(file.name)
: get_zip_url(file.name, get_item_name(game));
files.push(cfgr.mountFile('/'+ file.name,
cfgr.fetchFile(title, url)));
files.push(cfgr.floppy(0, // we're not pushing a file here
file.name)); // but that's ok
}
});
files.push(cfgr.mountFile('/'+ modulecfg['driver'] + '.cfg',
cfgr.fetchOptionalFile("Config File",
get_other_emulator_config_url(module))));
return files;
}
function get_pce_files(cfgr, metadata, modulecfg, filelist) {
var files = [],
bios_files = modulecfg['bios_filenames'];
bios_files.forEach(function (fname, i) {
if (fname) {
var title = "ROM File ("+ (i+1) +" of "+ bios_files.length +")";
files.push(cfgr.mountFile('/'+ fname,
cfgr.fetchFile(title,
get_bios_url(fname))));
}
});
var meta = dict_from_xml(metadata),
game_files_counter = {};
files_with_ext_from_filelist(filelist, meta.emulator_ext).forEach(function (file, i) {
if (modulecfg.peripherals && modulecfg.peripherals[i]) {
game_files_counter[file.name] = modulecfg.peripherals[i];
}
});
meta_props_matching(meta, /^pce_drive_([a-zA-Z0-9]+)$/).forEach(function (result) {
var key = result[0], periph = result[1][1];
game_files_counter[meta[key]] = periph;
});
var game_files = Object.keys(game_files_counter),
len = game_files.length;
game_files.forEach(function (filename, i) {
2018-09-02 09:04:52 +00:00
var title = "Hard Disk ("+ (i+1) +" of "+ len +")",
2018-09-01 05:07:00 +00:00
ext = filename.match(/\.([^.]*)$/)[1],
url = (filename.includes("/")) ? get_zip_url(filename)
: get_zip_url(filename, get_item_name(game));
files.push(cfgr.mountFile('/'+ game_files_counter[filename] +'.'+ ext,
cfgr.fetchFile(title, url)));
});
files.push(cfgr.mountFile('/pce-'+ modulecfg['driver'] + '.cfg',
cfgr.fetchOptionalFile("Config File",
get_other_emulator_config_url("pce-"+ modulecfg['driver']))));
return files;
}
var get_item_name = function (game_path) {
return game_path.split('/').shift();
};
var get_game_name = function (game_path) {
return game_path.split('/').pop();
};
// NOTE: deliberately use cors.archive.org since this will 302 rewrite to iaXXXXX.us.archive.org/XX/items/...
// and need to keep that "artificial" extra domain-ish name to avoid CORS issues with IE/Safari (tracey@archive)
var get_emulator_config_url = function (module) {
return 'emularity_engine_v1/' + module + '.json';
};
var get_other_emulator_config_url = function (module) {
return 'emularity_config_v1/' + module + '.cfg';
};
var get_meta_url = function (game_path) {
var path = game_path.split('/');
return "meta.xml";
};
var get_files_url = function (game_path) {
var path = game_path.split('/');
return "files.xml";
};
var get_zip_url = function (game_path, item_path) {
if (item_path) {
return item_path +"/"+ game_path;
}
return game_path;
};
var get_js_url = function (js_filename) {
console.log(">>>>", js_filename);
if (js_filename.endsWith(".gz")) {
console.log("OKKK", "emularity_engine_v1/"+ js_filename.slice(0, -3));
return "emularity_engine_v1/"+ js_filename.slice(0, -3);
}
return "emularity_engine_v1/"+ js_filename;
};
var get_bios_url = function (bios_filename) {
return "emularity_bios_v1/"+ bios_filename;
};
function mountat (drive) {
return function (data) {
return { drive: drive,
mountpoint: "/" + drive,
data: data
};
};
}
return emulator;
}
/**
* BaseLoader
*/
function BaseLoader() {
return Array.prototype.reduce.call(arguments, extend);
}
BaseLoader.canvas = function (id) {
var elem = id instanceof Element ? id : document.getElementById(id);
return { canvas: elem };
};
BaseLoader.emulatorJS = function (url) {
return { emulatorJS: url };
};
BaseLoader.emulatorWASM = function (url) {
return { emulatorWASM: url };
};
BaseLoader.locateAdditionalEmulatorJS = function (func) {
return { locateAdditionalJS: func };
};
BaseLoader.fileSystemKey = function (key) {
return { fileSystemKey: key };
};
BaseLoader.nativeResolution = function (width, height) {
if (typeof width !== 'number' || typeof height !== 'number')
throw new Error("Width and height must be numbers");
return { nativeResolution: { width: Math.floor(width), height: Math.floor(height) } };
};
BaseLoader.aspectRatio = function (ratio) {
if (typeof ratio !== 'number')
throw new Error("Aspect ratio must be a number");
return { aspectRatio: ratio };
};
BaseLoader.sampleRate = function (rate) {
return { sample_rate: rate };
};
BaseLoader.muted = function (muted) {
return { muted: muted };
};
BaseLoader.mountZip = function (drive, file) {
return { files: [{ drive: drive,
mountpoint: "/" + drive,
file: file
}] };
};
BaseLoader.mountFile = function (filename, file) {
return { files: [{ mountpoint: filename,
file: file
}] };
};
BaseLoader.fetchFile = function (title, url) {
return { title: title, url: url, optional: false };
};
BaseLoader.fetchOptionalFile = function (title, url) {
return { title: title, url: url, optional: true };
};
BaseLoader.localFile = function (title, data) {
return { title: title, data: data };
};
/**
* DosBoxLoader
*/
function DosBoxLoader() {
var config = Array.prototype.reduce.call(arguments, extend);
config.emulator_arguments = build_dosbox_arguments(config.emulatorStart, config.files, config.extra_dosbox_args);
config.runner = EmscriptenRunner;
return config;
}
DosBoxLoader.__proto__ = BaseLoader;
DosBoxLoader.startExe = function (path) {
return { emulatorStart: path };
};
DosBoxLoader.extraArgs = function (args) {
return { extra_dosbox_args: args };
};
/**
* MAMELoader
*/
function MAMELoader() {
var config = Array.prototype.reduce.call(arguments, extend);
config.emulator_arguments = build_mame_arguments(config.muted, config.mame_driver,
config.nativeResolution, config.sample_rate,
config.peripheral, config.extra_mame_args,
config.keep_aspect);
config.runner = MAMERunner;
return config;
}
MAMELoader.__proto__ = BaseLoader;
MAMELoader.driver = function (driver) {
return { mame_driver: driver };
};
MAMELoader.peripheral = function (peripheral, game) {
var p = {};
p[peripheral] = [game];
return { peripheral: p };
};
MAMELoader.keepAspect = function (keep) {
return { keep_aspect: !!keep };
};
MAMELoader.extraArgs = function (args) {
return { extra_mame_args: args };
};
/**
* SAELoader
*/
function SAELoader() {
var config = Array.prototype.reduce.call(arguments, extend);
config.runner = SAERunner;
return config;
}
SAELoader.__proto__ = BaseLoader;
SAELoader.model = function (model) {
return { amigaModel: model };
};
SAELoader.fastMemory = function (megabytes) {
return { fast_memory: megabytes << 20 };
};
SAELoader.rom = function (filenames) {
if (typeof filenames == "string")
filenames = [filenames];
return { rom: filenames[0], extRom: filenames[1] };
};
SAELoader.floppy = function (index, filename) {
var f = {};
f[index] = filename;
return { floppy: f };
};
SAELoader.ntsc = function (v) {
return { ntsc: !!v };
};
/**
* PCELoader
*/
function PCELoader() {
var config = Array.prototype.reduce.call(arguments, extend);
config.emulator_arguments = ["-c", "/emulator/pce-"+ config.pceModel +".cfg"];
config.runner = EmscriptenRunner;
return config;
}
PCELoader.__proto__ = BaseLoader;
PCELoader.model = function (model) {
return { pceModel: model };
};
var build_mame_arguments = function (muted, driver, native_resolution, sample_rate, peripheral, extra_args, keepaspect) {
var args = [driver,
'-verbose',
'-rompath', 'emulator',
'-window',
keepaspect ? '-keepaspect' : '-nokeepaspect'];
if (native_resolution && "width" in native_resolution && "height" in native_resolution) {
args.push('-resolution', [native_resolution.width, native_resolution.height].join('x'));
}
if (muted) {
args.push('-sound', 'none');
} else if (sample_rate) {
args.push('-samplerate', sample_rate);
}
if (peripheral) {
for (var p in peripheral) {
if (Object.prototype.propertyIsEnumerable.call(peripheral, p)) {
args.push('-' + p,
'/emulator/'+ (peripheral[p][0].replace(/\//g,'_')));
}
}
}
if (extra_args) {
args = args.concat(extra_args);
}
return args;
};
var build_dosbox_arguments = function (emulator_start, files, extra_args) {
var args = ['-conf', '/emulator/dosbox.conf'];
var len = files.length;
for (var i = 0; i < len; i++) {
if ('drive' in files[i]) {
args.push('-c', 'mount '+ files[i].drive +' /emulator'+ files[i].mountpoint);
}
}
if (extra_args) {
args = args.concat(extra_args);
}
var path = emulator_start.split(/\\|\//); // I have LTS already
args.push('-c', /^[a-zA-Z]:$/.test(path[0]) ? path.shift() : 'c:');
var prog = path.pop();
if (path && path.length)
args.push('-c', 'cd '+ path.join('/'));
args.push('-c', prog);
return args;
};
/*
* EmscriptenRunner
*/
function EmscriptenRunner(canvas, game_data) {
var self = this;
this._hooks = { start: [], reset: [] };
// This is somewhat wrong, because our Emscripten-based emulators
// are currently compiled to start immediately when their js file
// is loaded.
Module = { arguments: game_data.emulator_arguments,
screenIsReadOnly: true,
print: function (text) { console.log(text); },
printErr: function (text) { console.log(text); },
canvas: canvas,
noInitialRun: false,
locateFile: game_data.locateAdditionalJS,
wasmBinary: game_data.wasmBinary,
preInit: function () {
// Re-initialize BFS to just use the writable in-memory storage.
BrowserFS.initialize(game_data.fs);
var BFS = new BrowserFS.EmscriptenFS();
// Mount the file system into Emscripten.
FS.mkdir('/emulator');
FS.mount(BFS, {root: '/'}, '/emulator');
},
preRun: [function () {
self._hooks.start.forEach(function (f) {
//try {
f && f();
//} catch(x) {
// console.warn(x);
//}
});
}]
};
}
EmscriptenRunner.prototype.start = function () {
};
EmscriptenRunner.prototype.pause = function () {
};
EmscriptenRunner.prototype.stop = function () {
};
EmscriptenRunner.prototype.mute = function () {
try {
if (!SDL_PauseAudio)
SDL_PauseAudio = Module.cwrap('SDL_PauseAudio', '', ['number']);
SDL_PauseAudio(true);
} catch (x) {
console.log("Unable to change audio state:", x);
}
};
EmscriptenRunner.prototype.unmute = function () {
try {
if (!SDL_PauseAudio)
SDL_PauseAudio = Module.cwrap('SDL_PauseAudio', '', ['number']);
SDL_PauseAudio(false);
} catch (x) {
console.log("Unable to change audio state:", x);
}
};
EmscriptenRunner.prototype.onStarted = function (func) {
this._hooks.start.push(func);
};
EmscriptenRunner.prototype.onReset = function (func) {
this._hooks.reset.push(func);
};
EmscriptenRunner.prototype.requestFullScreen = function () {
};
/*
* MAMERunner
*/
function MAMERunner() {
return EmscriptenRunner.apply(this, arguments);
}
MAMERunner.prototype = Object.create(EmscriptenRunner.prototype,
{
mute: function () {
var soundmgr = Module.__ZN15running_machine20emscripten_get_soundEv(Module.__ZN15running_machine30emscripten_get_running_machineEv());
Module.__ZN13sound_manager4muteEbh(soundmgr,
true,
0x02); // MUTE_REASON_UI
},
unmute: function () {
var soundmgr = Module.__ZN15running_machine20emscripten_get_soundEv(Module.__ZN15running_machine30emscripten_get_running_machineEv());
Module.__ZN13sound_manager4muteEbh(soundmgr,
false,
0x02); // MUTE_REASON_UI
}
});
/*
* SAERunner
*/
function SAERunner(canvas, game_data) {
this._sae = new ScriptedAmigaEmulator();
this._cfg = this._sae.getConfig();
this._canvas = canvas;
var model = null;
switch (game_data.amigaModel) {
case "A500": model = SAEC_Model_A500; break;
case "A500P": model = SAEC_Model_A500P; break;
case "A600": model = SAEC_Model_A600; break;
case "A1000": model = SAEC_Model_A1000; break;
case "A1200": model = SAEC_Model_A1200; break;
case "A2000": model = SAEC_Model_A2000; break;
case "A3000": model = SAEC_Model_A3000; break;
case "A4000": model = SAEC_Model_A4000; break;
case "A4000T": model = SAEC_Model_A4000T; break;
/* future. do not use. cd-emulation is not implemented yet.
case "CDTV": model = SAEC_Model_CDTV; break;
case "CD32": model = SAEC_Model_CD32; break; */
}
this._sae.setModel(model, 0);
this._cfg.memory.z2FastSize = game_data.fastMemory || 2 << 20;
this._cfg.floppy.speed = SAEC_Config_Floppy_Speed_Turbo;
this._cfg.video.id = canvas.getAttribute("id");
if (game_data.nativeResolution && game_data.nativeResolution.height == 360 && game_data.nativeResolution.width == 284)
{
this._cfg.video.hresolution = SAEC_Config_Video_HResolution_LoRes;
this._cfg.video.vresolution = SAEC_Config_Video_VResolution_NonDouble;
this._cfg.video.size_win.width = SAEC_Video_DEF_AMIGA_WIDTH; /* 360 */
this._cfg.video.size_win.height = SAEC_Video_DEF_AMIGA_HEIGHT; /* 284 */
}
else if (game_data.nativeResolution && game_data.nativeResolution.height == 1440 && game_data.nativeResolution.width == 568)
{
this._cfg.video.hresolution = SAEC_Config_Video_HResolution_SuperHiRes;
this._cfg.video.vresolution = SAEC_Config_Video_VResolution_Double;
this._cfg.video.size_win.width = SAEC_Video_DEF_AMIGA_WIDTH << 2; /* 1440 */
this._cfg.video.size_win.height = SAEC_Video_DEF_AMIGA_HEIGHT << 1; /* 568 */
}
else
{
this._cfg.video.hresolution = SAEC_Config_Video_HResolution_HiRes;
this._cfg.video.vresolution = SAEC_Config_Video_VResolution_Double;
this._cfg.video.size_win.width = SAEC_Video_DEF_AMIGA_WIDTH << 1; /* 720 */
this._cfg.video.size_win.height = SAEC_Video_DEF_AMIGA_HEIGHT << 1; /* 568 */
}
this._cfg.memory.rom.name = game_data.rom;
this._cfg.memory.rom.data = game_data.fs.readFileSync('/'+game_data.rom, null, flag_r);
this._cfg.memory.rom.size = this._cfg.memory.rom.data.length;
if (game_data.extRom) {
this._cfg.memory.extRom.name = game_data.extRom;
this._cfg.memory.extRom.data = game_data.fs.readFileSync('/'+game_data.extRom, null, flag_r);
this._cfg.memory.extRom.size = this._cfg.memory.extRom.data.length;
}
this._cfg.floppy.drive[0].file.name = game_data.floppy[0];
this._cfg.floppy.drive[0].file.data = game_data.fs.readFileSync('/'+game_data.floppy[0], null, flag_r);
this._cfg.floppy.drive[0].file.size = this._cfg.floppy.drive[0].file.data.length;
}
SAERunner.prototype.start = function () {
var err = this._sae.start();
};
SAERunner.prototype.pause = function () {
this._sae.pause();
};
SAERunner.prototype.stop = function () {
this._sae.stop();
};
SAERunner.prototype.mute = function () {
var err = this._sae.mute(true);
if (err) {
console.warn("unable to mute; SAE error number", err);
}
};
SAERunner.prototype.unmute = function () {
var err = this._sae.mute(false);
if (err) {
console.warn("unable to unmute; SAE error number", err);
}
};
SAERunner.prototype.onStarted = function (func) {
this._cfg.hook.event.started = func;
};
SAERunner.prototype.onReset = function (func) {
this._cfg.hook.event.reseted = func;
};
SAERunner.prototype.requestFullScreen = function () {
getfullscreenenabler().call(this._canvas);
};
/**
* Emulator
*/
function Emulator(canvas, callbacks, loadFiles) {
if (typeof callbacks !== 'object') {
callbacks = { before_emulator: null,
before_run: callbacks };
}
var js_url;
var requests = [];
var drawloadingtimer;
// TODO: Have an enum value that communicates the current state of the emulator, e.g. 'initializing', 'loading', 'running'.
var has_started = false;
var loading = false;
var defaultSplashColors = { foreground: 'white',
background: 'black',
failure: 'red' };
var splash = { loading_text: "",
spinning: true,
finished_loading: false,
colors: defaultSplashColors,
table: null,
splashimg: new Image() };
var runner;
var muted = false;
var SDL_PauseAudio;
this.isMuted = function () { return muted; };
this.mute = function () { return this.setMute(true); };
this.unmute = function () { return this.setMute(false); };
this.toggleMute = function () { return this.setMute(!muted); };
this.setMute = function (state) {
muted = state;
if (runner) {
if (state) {
runner.mute();
} else {
runner.unmute();
}
}
else {
try {
if (!SDL_PauseAudio)
SDL_PauseAudio = Module.cwrap('SDL_PauseAudio', '', ['number']);
SDL_PauseAudio(state);
} catch (x) {
console.log("Unable to change audio state:", x);
}
}
return this;
};
// This is the bare minimum that will allow gamepads to work. If
// we don't listen for them then the browser won't tell us about
// them.
// TODO: add hooks so that some kind of UI can be displayed.
window.addEventListener("gamepadconnected",
function (e) {
console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.",
e.gamepad.index, e.gamepad.id,
e.gamepad.buttons.length, e.gamepad.axes.length);
});
window.addEventListener("gamepaddisconnected",
function (e) {
console.log("Gamepad disconnected from index %d: %s",
e.gamepad.index, e.gamepad.id);
});
if (/archive\.org$/.test(document.location.hostname) && document.getElementById("gofullscreen")) {
document.getElementById("gofullscreen").addEventListener("click", this.requestFullScreen);
}
var css_resolution, scale, aspectRatio;
// right off the bat we set the canvas's inner dimensions to
// whatever it's current css dimensions are; this isn't likely to be
// the same size that dosbox/jsmame will set it to, but it avoids
// the case where the size was left at the default 300x150
if (!canvas.hasAttribute("width")) {
var style = getComputedStyle(canvas);
canvas.width = parseInt(style.width, 10);
canvas.height = parseInt(style.height, 10);
}
this.setScale = function(_scale) {
scale = _scale;
return this;
};
this.setSplashImage = function(_splashimg) {
if (_splashimg) {
if (_splashimg instanceof Image) {
if (splash.splashimg.parentNode) {
splash.splashimg.src = _splashimg.src;
} else {
splash.splashimg = _splashimg;
}
} else {
splash.splashimg.src = _splashimg;
}
}
return this;
};
this.setCSSResolution = function(_resolution) {
css_resolution = _resolution;
return this;
};
this.setAspectRatio = function(_aspectRatio) {
aspectRatio = _aspectRatio;
return this;
};
this.setCallbacks = function(_callbacks) {
if (typeof _callbacks !== 'object') {
callbacks = { before_emulator: null,
before_run: _callbacks };
} else {
callbacks = _callbacks;
}
return this;
};
this.setSplashColors = function (colors) {
splash.colors = colors;
return this;
};
this.setLoad = function (loadFunc) {
loadFiles = loadFunc;
return this;
};
var start = function (options) {
if (has_started)
return false;
has_started = true;
var defaultOptions = { waitAfterDownloading: false,
hasCustomCSS: false };
if (typeof options !== 'object') {
options = defaultOptions;
} else {
options.__proto__ = defaultOptions;
}
var k, c, game_data;
setupSplash(canvas, splash, options);
drawsplash();
var loading;
if (typeof loadFiles === 'function') {
loading = loadFiles(fetch_file, splash);
} else {
loading = Promise.resolve(loadFiles);
}
loading.then(function (_game_data) {
return new Promise(function(resolve, reject) {
var inMemoryFS = new BrowserFS.FileSystem.InMemory();
// If the browser supports IndexedDB storage, mirror writes to that storage
// for persistence purposes.
if (BrowserFS.FileSystem.IndexedDB.isAvailable()) {
var AsyncMirrorFS = BrowserFS.FileSystem.AsyncMirror,
IndexedDB = BrowserFS.FileSystem.IndexedDB;
deltaFS = new AsyncMirrorFS(inMemoryFS,
new IndexedDB(function(e, fs) {
if (e) {
// we probably weren't given access;
// private window for example.
// don't fail completely, just don't
// use indexeddb
deltaFS = inMemoryFS;
finish();
} else {
// Initialize deltaFS by copying files from async storage to sync storage.
deltaFS.initialize(function (e) {
if (e) {
reject(e);
} else {
finish();
}
});
}
},
"fileSystemKey" in _game_data ? _game_data.fileSystemKey
: "emularity"));
} else {
finish();
}
function finish() {
game_data = _game_data;
// Any file system writes to MountableFileSystem will be written to the
// deltaFS, letting us mount read-only zip files into the MountableFileSystem
// while being able to "write" to them.
game_data.fs = new BrowserFS.FileSystem.OverlayFS(deltaFS,
new BrowserFS.FileSystem.MountableFileSystem());
game_data.fs.initialize(function (e) {
if (e) {
console.error("Failed to initialize the OverlayFS:", e);
reject();
} else {
var Buffer = BrowserFS.BFSRequire('buffer').Buffer;
function fetch(file) {
if ('data' in file && file.data !== null && typeof file.data !== 'undefined') {
return Promise.resolve(file.data);
}
return fetch_file(file.title, file.url, 'arraybuffer', file.optional);
}
function mountat(drive) {
return function (data) {
if (data !== null) {
drive = drive.toLowerCase();
var mountpoint = '/'+ drive;
// Mount into RO MFS.
game_data.fs.getOverlayedFileSystems().readable.mount(mountpoint, BFSOpenZip(new Buffer(data)));
}
};
}
function saveat(filename) {
return function (data) {
if (data !== null) {
if (filename.includes('/')) {
var parts = filename.split('/');
for (var i = 1; i < parts.length; i++) {
var path = '/'+ parts.slice(0, i).join('/');
if (!game_data.fs.existsSync(path)) {
game_data.fs.mkdirSync(path);
}
}
}
game_data.fs.writeFileSync('/'+ filename, new Buffer(data), null, flag_w, 0x1a4);
}
};
}
var promises = game_data.files
.map(function (f) {
if (f && f.file) {
if (f.drive) {
return fetch(f.file).then(mountat(f.drive));
} else if (f.mountpoint) {
return fetch(f.file).then(saveat(f.mountpoint));
}
}
return null;
});
// this is kinda wrong; it really only applies when we're loading something created by Emscripten
if ('emulatorWASM' in game_data && game_data.emulatorWASM && 'WebAssembly' in window) {
promises.push(fetch({ title: "WASM Binary", url: game_data.emulatorWASM }).then(function (data) { game_data.wasmBinary = data; }));
}
Promise.all(promises).then(resolve, reject);
}
});
}
});
})
.then(function (game_files) {
if (!game_data || splash.failed_loading) {
return null;
}
if (options.waitAfterDownloading) {
return new Promise(function (resolve, reject) {
splash.setTitle("Press any key to continue...");
splash.spinning = false;
// stashes these event listeners so that we can remove them after
window.addEventListener('keypress', k = keyevent(resolve));
canvas.addEventListener('click', c = resolve);
splash.splashElt.addEventListener('click', c);
});
}
return Promise.resolve();
},
function () {
if (splash.failed_loading) {
return;
}
2018-09-02 09:04:52 +00:00
splash.setTitle("Failed to download data!");
2018-09-01 05:07:00 +00:00
splash.failed_loading = true;
})
.then(function () {
if (!game_data || splash.failed_loading) {
return null;
}
splash.spinning = true;
window.removeEventListener('keypress', k);
canvas.removeEventListener('click', c);
splash.splashElt.removeEventListener('click', c);
// Don't let arrow, pg up/down, home, end affect page position
blockSomeKeys();
setupFullScreen();
disableRightClickContextMenu(canvas);
// Emscripten doesn't use the proper prefixed functions for fullscreen requests,
// so let's map the prefixed versions to the correct function.
canvas.requestPointerLock = getpointerlockenabler();
moveConfigToRoot(game_data.fs);
if (callbacks && callbacks.before_emulator) {
try {
callbacks.before_emulator();
} catch (x) {
console.log(x);
}
}
if ("runner" in game_data) {
if (game_data.runner == EmscriptenRunner || game_data.runner == MAMERunner) {
// this is a stupid hack. Emscripten-based
// apps currently need the runner to be set
// up first, then we can attach the
// script. The others have to do it the
// other way around.
runner = setup_runner();
}
}
if (game_data.emulatorJS) {
2018-09-02 09:04:52 +00:00
splash.setTitle("Launching System 7");
2018-09-01 05:07:00 +00:00
return attach_script(game_data.emulatorJS);
} else {
splash.setTitle("Non-system disk or disk error");
}
return null;
},
function () {
if (!game_data || splash.failed_loading) {
return null;
}
splash.setTitle("Invalid media, track 0 bad or unusable");
splash.failed_loading = true;
})
.then(function () {
if (!game_data || splash.failed_loading) {
return null;
}
if ("runner" in game_data) {
if (!runner) {
runner = setup_runner();
}
runner.start();
}
});
function setup_runner() {
var runner = new game_data.runner(canvas, game_data);
resizeCanvas(canvas, 1, game_data.nativeResolution, game_data.aspectRatio);
runner.onStarted(function () {
splash.finished_loading = true;
splash.hide();
if (callbacks && callbacks.before_run) {
setTimeout(function() {
callbacks.before_run();
},
0);
}
});
runner.onReset(function () {
if (muted) {
runner.mute();
}
});
return runner;
}
return this;
};
this.start = start;
var formatSize = function (event) {
if (event.lengthComputable)
return "("+ (event.total ? (event.loaded / event.total * 100).toFixed(0)
: "100") +
"%; "+ formatBytes(event.loaded) +
" of "+ formatBytes(event.total) +")";
return "("+ formatBytes(event.loaded) +")";
};
var formatBytes = function (bytes, base10) {
if (bytes === 0)
return "0 B";
var unit = base10 ? 1000 : 1024,
units = base10 ? ["B", "kB","MB","GB","TB","PB","EB","ZB","YB"]
: ["B", "KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB"],
exp = parseInt((Math.log(bytes) / Math.log(unit))),
size = bytes / Math.pow(unit, exp);
return size.toFixed(1) +' '+ units[exp];
};
var fetch_file = function (title, url, rt, optional) {
var needsCSS = splash.table.dataset.hasCustomCSS == "false";
var row = addRow(splash.table);
var titleCell = row[0], statusCell = row[1];
titleCell.textContent = title;
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = rt || 'arraybuffer';
xhr.onprogress = function (e) {
titleCell.innerHTML = title +" <span style=\"font-size: smaller\">"+ formatSize(e) +"</span>";
};
xhr.onload = function (e) {
if (xhr.status === 200) {
success();
resolve(xhr.response);
} else if (optional) {
success();
resolve(null);
} else {
failure();
reject();
}
};
xhr.onerror = function (e) {
if (optional) {
success();
resolve(null);
} else {
failure();
reject();
}
};
function success() {
statusCell.textContent = "✔";
titleCell.parentNode.classList.add('emularity-download-success');
titleCell.textContent = title;
if (needsCSS) {
titleCell.style.fontWeight = 'bold';
titleCell.parentNode.style.backgroundColor = splash.getColor('foreground');
titleCell.parentNode.style.color = splash.getColor('background');
}
}
function failure() {
statusCell.textContent = "✘";
titleCell.parentNode.classList.add('emularity-download-failure');
titleCell.textContent = title;
if (needsCSS) {
titleCell.style.fontWeight = 'bold';
titleCell.parentNode.style.backgroundColor = splash.getColor('failure');
titleCell.parentNode.style.color = splash.getColor('background');
}
}
xhr.send();
});
};
function keyevent(resolve) {
return function (e) {
if (e.which == 32) {
e.preventDefault();
resolve();
}
};
};
var resizeCanvas = function (canvas, scale, resolution, aspectRatio) {
if (scale && resolution) {
// optimizeSpeed is the standardized value. different
// browsers support different values; they will all ignore
// values that they don't understand.
canvas.style.imageRendering = '-moz-crisp-edges';
canvas.style.imageRendering = '-o-crisp-edges';
canvas.style.imageRendering = '-webkit-optimize-contrast';
canvas.style.imageRendering = 'optimize-contrast';
canvas.style.imageRendering = 'crisp-edges';
canvas.style.imageRendering = 'pixelated';
canvas.style.imageRendering = 'optimizeSpeed';
canvas.style.width = resolution.width * scale +'px';
canvas.style.height = resolution.height * scale +'px';
canvas.width = resolution.width;
canvas.height = resolution.height;
}
};
var clearCanvas = function () {
var context = canvas.getContext('2d');
context.fillStyle = splash.getColor('background');
context.fillRect(0, 0, canvas.width, canvas.height);
console.log("canvas cleared");
};
function setupSplash(canvas, splash, globalOptions) {
splash.splashElt = document.getElementById("emularity-splash-screen");
if (!splash.splashElt) {
splash.splashElt = document.createElement('div');
splash.splashElt.classList.add("emularity-splash-screen");
if (!globalOptions.hasCustomCSS) {
splash.splashElt.style.position = 'absolute';
splash.splashElt.style.top = '0';
splash.splashElt.style.left = '0';
splash.splashElt.style.right = '0';
splash.splashElt.style.color = splash.getColor('foreground');
splash.splashElt.style.backgroundColor = splash.getColor('background');
}
canvas.parentElement.appendChild(splash.splashElt);
}
splash.splashimg.classList.add("emularity-splash-image");
if (!globalOptions.hasCustomCSS) {
splash.splashimg.style.display = 'block';
splash.splashimg.style.marginLeft = 'auto';
splash.splashimg.style.marginRight = 'auto';
}
splash.splashElt.appendChild(splash.splashimg);
splash.titleElt = document.createElement('span');
splash.titleElt.classList.add("emularity-splash-title");
if (!globalOptions.hasCustomCSS) {
splash.titleElt.style.display = 'block';
splash.titleElt.style.width = '100%';
splash.titleElt.style.marginTop = "1em";
splash.titleElt.style.marginBottom = "1em";
splash.titleElt.style.textAlign = 'center';
splash.titleElt.style.font = "24px sans-serif";
}
splash.titleElt.textContent = " ";
splash.splashElt.appendChild(splash.titleElt);
var table = document.getElementById("emularity-progress-indicator");
if (!table) {
table = document.createElement('table');
table.classList.add("emularity-progress-indicator");
table.dataset.hasCustomCSS = globalOptions.hasCustomCSS;
if (!globalOptions.hasCustomCSS) {
table.style.width = "75%";
table.style.color = splash.getColor('foreground');
table.style.backgroundColor = splash.getColor('background');
table.style.marginLeft = 'auto';
table.style.marginRight = 'auto';
table.style.borderCollapse = 'separate';
table.style.borderSpacing = "2px";
}
splash.splashElt.appendChild(table);
}
splash.table = table;
}
splash.setTitle = function (title) {
splash.titleElt.textContent = title;
};
splash.hide = function () {
splash.splashElt.style.display = 'none';
};
splash.getColor = function (name) {
return name in splash.colors ? splash.colors[name]
: defaultSplashColors[name];
};
var addRow = function (table) {
var needsCSS = table.dataset.hasCustomCSS == "false";
var row = table.insertRow(-1);
if (needsCSS) {
row.style.textAlign = 'center';
}
var cell = row.insertCell(-1);
if (needsCSS) {
cell.style.position = 'relative';
}
var titleCell = document.createElement('span');
titleCell.classList.add("emularity-download-title");
titleCell.textContent = '—';
if (needsCSS) {
titleCell.style.verticalAlign = 'center';
titleCell.style.minHeight = "24px";
titleCell.style.whiteSpace = "nowrap";
}
cell.appendChild(titleCell);
var statusCell = document.createElement('span');
statusCell.classList.add("emularity-download-status");
if (needsCSS) {
statusCell.style.position = 'absolute';
statusCell.style.left = "0";
statusCell.style.paddingLeft = "0.5em";
}
cell.appendChild(statusCell);
return [titleCell, statusCell];
};
var drawsplash = function () {
canvas.setAttribute('moz-opaque', '');
if (!splash.splashimg.src) {
splash.splashimg.src = "logo/emularity_color_small.png";
}
};
function attach_script(js_url) {
return new Promise(function (resolve, reject) {
var newScript;
function loaded(e) {
if (e.target == newScript) {
newScript.removeEventListener("load", loaded);
newScript.removeEventListener("error", failed);
resolve();
}
}
function failed(e) {
if (e.target == newScript) {
newScript.removeEventListener("load", loaded);
newScript.removeEventListener("error", failed);
reject();
}
}
if (js_url) {
var head = document.getElementsByTagName('head')[0];
newScript = document.createElement('script');
newScript.addEventListener("load", loaded);
newScript.addEventListener("error", failed);
newScript.type = 'text/javascript';
newScript.src = js_url;
head.appendChild(newScript);
}
});
}
function getpointerlockenabler() {
return canvas.requestPointerLock || canvas.mozRequestPointerLock || canvas.webkitRequestPointerLock;
}
this.isfullscreensupported = function () {
return !!(getfullscreenenabler());
};
function setupFullScreen() {
var self = this;
var fullScreenChangeHandler = function() {
if (!(document.mozFullScreenElement || document.fullScreenElement)) {
resizeCanvas(canvas, scale, css_resolution, aspectRatio);
}
};
if ('onfullscreenchange' in document) {
document.addEventListener('fullscreenchange', fullScreenChangeHandler);
} else if ('onmozfullscreenchange' in document) {
document.addEventListener('mozfullscreenchange', fullScreenChangeHandler);
} else if ('onwebkitfullscreenchange' in document) {
document.addEventListener('webkitfullscreenchange', fullScreenChangeHandler);
}
};
this.requestFullScreen = function () {
if (typeof Module == "object" && "requestFullScreen" in Module) {
Module.requestFullScreen(1, 0);
} else if (runner) {
runner.requestFullScreen();
}
};
/**
* Prevents page navigation keys such as page up/page down from
* moving the page while the user is playing.
*/
function blockSomeKeys() {
function keypress (e) {
if (e.which >= 33 && e.which <= 40) {
e.preventDefault();
return false;
}
return true;
}
window.onkeydown = keypress;
}
/**
* Disables the right click menu for the given element.
*/
function disableRightClickContextMenu(element) {
element.addEventListener('contextmenu',
function (e) {
if (e.button == 2) {
// Block right-click menu thru preventing default action.
e.preventDefault();
}
});
}
};
/**
* misc
*/
function getfullscreenenabler() {
return canvas.requestFullScreen || canvas.webkitRequestFullScreen || canvas.mozRequestFullScreen;
}
function BFSOpenZip(loadedData) {
return new BrowserFS.FileSystem.ZipFS(loadedData);
};
// This is such a hack. We're not calling the BrowserFS api
// "correctly", so we have to synthesize these flags ourselves
var flag_r = { isReadable: function() { return true; },
isWriteable: function() { return false; },
isTruncating: function() { return false; },
isAppendable: function() { return false; },
isSynchronous: function() { return false; },
isExclusive: function() { return false; },
pathExistsAction: function() { return 0; },
pathNotExistsAction: function() { return 1; }
};
var flag_w = { isReadable: function() { return false; },
isWriteable: function() { return true; },
isTruncating: function() { return false; },
isAppendable: function() { return false; },
isSynchronous: function() { return false; },
isExclusive: function() { return false; },
pathExistsAction: function() { return 0; },
pathNotExistsAction: function() { return 3; }
};
/**
* Searches for dosbox.conf, and moves it to '/dosbox.conf' so dosbox uses it.
*/
function moveConfigToRoot(fs) {
var dosboxConfPath = null;
// Recursively search for dosbox.conf.
function searchDirectory(dirPath) {
fs.readdirSync(dirPath).forEach(function(item) {
if (dosboxConfPath) {
return;
}
// Avoid infinite recursion by ignoring these entries, which exist at
// the root.
if (item === '.' || item === '..') {
return;
}
// Append '/' between dirPath and the item's name... unless dirPath
// already ends in it (which always occurs if dirPath is the root, '/').
var itemPath = dirPath + (dirPath[dirPath.length - 1] !== '/' ? "/" : "") + item,
itemStat = fs.statSync(itemPath);
if (itemStat.isDirectory(itemStat.mode)) {
searchDirectory(itemPath);
} else if (item === 'dosbox.conf') {
dosboxConfPath = itemPath;
}
});
}
searchDirectory('/');
if (dosboxConfPath !== null) {
fs.writeFileSync('/dosbox.conf',
fs.readFileSync(dosboxConfPath, null, flag_r),
null, flag_w, 0x1a4);
}
};
function extend(a, b) {
if (a === null)
return b;
if (b === null)
return a;
var ta = typeof a,
tb = typeof b;
if (ta !== tb) {
if (ta === 'undefined')
return b;
if (tb === 'undefined')
return a;
throw new Error("Cannot extend an "+ ta +" with an "+ tb);
}
if (Array.isArray(a))
return a.concat(b);
if (ta === 'object') {
Object.keys(b).forEach(function (k) {
a[k] = extend(k in a ? a[k] : undefined, b[k]);
});
return a;
}
return b;
}
function dict_from_xml(xml) {
if (xml instanceof XMLDocument) {
xml = xml.documentElement;
}
var dict = {};
var len = xml.childNodes.length, i;
for (i = 0; i < len; i++) {
var node = xml.childNodes[i];
dict[node.nodeName] = node.textContent;
}
return dict;
}
function list_from_xml(xml) {
if (xml instanceof XMLDocument) {
xml = xml.documentElement;
}
return Array.prototype.slice.call(xml.childNodes);
}
function files_from_filelist(xml) {
return list_from_xml(xml).filter(function (node) {
return "getAttribute" in node;
})
.map(function (node) {
var file = dict_from_xml(node);
file.name = node.getAttribute("name");
return file;
});
}
function files_with_ext_from_filelist(xml, ext) {
if (!ext) {
return [];
}
if (!ext.startsWith('.')) {
ext = '.'+ ext;
}
ext = ext.toLowerCase();
return files_from_filelist(xml).filter(function (file) {
return file.name.toLowerCase().endsWith(ext);
});
}
function meta_props_matching(meta, regex) {
if (typeof regex == "string")
regex = RegExp(regex);
return Object.keys(meta).map(function (k) {
let match = regex.exec(k);
if (match)
return [k, match];
return null;
})
.filter(function (result) {
return !!result;
});
}
function _SDL_CreateRGBSurfaceFrom(pixels, width, height, depth, pitch, rmask, gmask, bmask, amask) {
// TODO: Actually fill pixel data to created surface.
// TODO: Take into account depth and pitch parameters.
// console.log('TODO: Partially unimplemented SDL_CreateRGBSurfaceFrom called!');
var surface = SDL.makeSurface(width, height, 0, false, 'CreateRGBSurfaceFrom', rmask, gmask, bmask, amask);
var surfaceData = SDL.surfaces[surface];
var surfaceImageData = surfaceData.ctx.getImageData(0, 0, width, height);
var surfacePixelData = surfaceImageData.data;
// Fill pixel data to created surface.
// Supports SDL_PIXELFORMAT_RGBA8888 and SDL_PIXELFORMAT_RGB888
var channels = amask ? 4 : 3; // RGBA8888 or RGB888
for (var pixelOffset = 0; pixelOffset < width*height; pixelOffset++) {
surfacePixelData[pixelOffset*4+0] = HEAPU8[pixels + (pixelOffset*channels+0)]; // R
surfacePixelData[pixelOffset*4+1] = HEAPU8[pixels + (pixelOffset*channels+1)]; // G
surfacePixelData[pixelOffset*4+2] = HEAPU8[pixels + (pixelOffset*channels+2)]; // B
surfacePixelData[pixelOffset*4+3] = amask ? HEAPU8[pixels + (pixelOffset*channels+3)] : 0xff; // A
};
surfaceData.ctx.putImageData(surfaceImageData, 0, 0);
return surface;
}
window.IALoader = IALoader;
window.DosBoxLoader = DosBoxLoader;
window.JSMESSLoader = MAMELoader; // depreciated; just for backwards compatibility
window.JSMAMELoader = MAMELoader; // ditto
window.MAMELoader = MAMELoader;
window.SAELoader = SAELoader;
window.PCELoader = PCELoader;
window.Emulator = Emulator;
window._SDL_CreateRGBSurfaceFrom = _SDL_CreateRGBSurfaceFrom;
})(typeof Promise === 'undefined' ? ES6Promise.Promise : Promise);
// legacy
var JSMESS = JSMESS || {};
JSMESS.ready = function (f) { f(); };