From 7d4c75378cfd5b5f8203f1556d5a3d40eaf6b93e Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Fri, 16 Jan 2015 07:58:54 -0800 Subject: [PATCH 01/60] use Promises, to make extending the file dependencies easier includes a polyfill for IE; this is included in emdosbox-loader.js for the moment --- emdosbox-loader.js | 1776 ++++++++++++++++++++++++++++++++---------- es6-promise-2.0.1.js | 960 +++++++++++++++++++++++ 2 files changed, 2322 insertions(+), 414 deletions(-) create mode 100644 es6-promise-2.0.1.js diff --git a/emdosbox-loader.js b/emdosbox-loader.js index 7e00054..44d7edb 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -1,421 +1,1369 @@ -var Module = null; - -function DOSBOX(canvas, module, game, precallback, callback, scale) { - var js_url; - var moduledata; - var requests = []; - var drawloadingtimer; - var file_countdown; - var spinnerrot = 0; - var splashimg = new Image(); - var spinnerimg = new Image(); - // TODO: Have an enum value that communicates the current state of DOSBOX, e.g. 'initializing', 'loading', 'running'. - var has_started = false; - var loading = false; - var LOADING_TEXT; - - 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 can_start = function () { - return !!canvas && !!module && !!game && !!scale && !has_started; - }; - - this.setscale = function(_scale) { - scale = _scale; - try_start(); - return this; - }; - - this.setprecallback = function(_precallback) { - precallback = _precallback; - return this; - }; - - this.setcallback = function(_callback) { - callback = _callback; - return this; - }; - - this.setmodule = function(_module) { - module = _module; - try_start(); - return this; - }; - - this.setgame = function(_game) { - game = _game; - try_start(); - return this; - }; - - var draw_loading_status = function() { - var context = canvas.getContext('2d'); - context.clearRect(0, 0, canvas.width, canvas.height); - context.drawImage(splashimg, canvas.width / 2 - (splashimg.width / 2), canvas.height / 3 - (splashimg.height / 2)); - var spinnerpos = (canvas.height / 2 + splashimg.height / 2) + 16; - context.save(); - context.translate((canvas.width / 2), spinnerpos); - context.rotate(spinnerrot); - context.drawImage(spinnerimg, -(64/2), -(64/2), 64, 64); - context.restore(); - context.save(); - context.font = '18px sans-serif'; - context.fillStyle = 'Black'; - context.textAlign = 'center'; - context.fillText(LOADING_TEXT, canvas.width / 2, (canvas.height / 2) + (splashimg.height / 4)); - context.restore(); - spinnerrot += .25; - }; - - var progress_fetch_file = function(e) { - if (e.lengthComputable) { - e.target.progress = e.loaded / e.total; - e.target.loaded = e.loaded; - e.target.total = e.total; - e.target.lengthComputable = e.lengthComputable; - } - }; - - var fetch_file = function(title, url, cb, rt, raw, unmanaged) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, true); - xhr.responseType = rt ? rt : 'arraybuffer'; - xhr.onload = function(e) { - if (xhr.status != 200) { - return; - } - if (!unmanaged) { - xhr.progress = 1.0; - } - var ints = raw ? xhr.response : new Int8Array(xhr.response); - cb(ints); - }; - if (!unmanaged) { - xhr.onprogress = progress_fetch_file; - xhr.title = title; - xhr.progress = 0; - xhr.total = 0; - xhr.loaded = 0; - xhr.lengthComputable = false; - requests.push(xhr); - } - xhr.send(); - }; - - var update_countdown = function() { - file_countdown -= 1; - if (file_countdown <= 0) { - loading = false; - - if (js_url) { - var head = document.getElementsByTagName('head')[0]; - var newScript = document.createElement('script'); - newScript.type = 'text/javascript'; - newScript.src = get_js_url(js_url); - head.appendChild(newScript); - } - - // see archive.js for the mute/unmute button/JS - if (!($.cookie && $.cookie('unmute'))){ - setTimeout(function(){ - // someone moved it from 1st to 2nd! - if (DOSBOX && typeof(DOSBOX.sdl_pauseaudio)!='undefined') - DOSBOX.sdl_pauseaudio(1); - else if (typeof _SDL_PauseAudio !== "undefined") - _SDL_PauseAudio(1); - }, 3000); - } - } - }; - - var build_dosbox_arguments = function (config, emulator_start) { - LOADING_TEXT = 'Building arguments'; - return ['/dosprogram/'+ emulator_start]; - }; - - var get_game_name = function (game_path) { - return game_path.split('/').pop(); - }; - - var get_meta_url = function (game_path) { - var path = game_path.split('/'); - // 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 - return "//cors.archive.org/cors/"+ path[4] +"/"+ path[4] +"_meta.xml"; - }; - - var get_js_url = function (js_filename) { - // 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 - return "//cors.archive.org/cors/jsmess_engine_v2/"+ js_filename; - }; - - var init_module = function() { - if (moduledata == null) { - // HACK: Module data isn't ready yet. It'll call us once loaded. - return; - } - LOADING_TEXT = 'Loading Program'; - var modulecfg = JSON.parse(moduledata); - js_url = modulecfg['js_filename']; - - var game_file = null, - meta_file = null; - - var nr = modulecfg['native_resolution']; - DOSBOX.width = nr[0] * scale; - DOSBOX.height = nr[1] * scale; - - // Makes the keyboard 'focusable' to let the canvas accept keyboard input. - // http://gamedev.stackexchange.com/questions/50223/receiving-keyboard-events-on-a-canvas-in-javascript - canvas.setAttribute('tabindex', '0'); - // Emscripten blocks the 'default action' of all mouse events, which - // prevents users from selecting the canvas for keyboard input! - // Prevent Emscripten from blocking users from selecting the canvas by - // manually 'focusing' the canvas when it is clicked. - canvas.addEventListener('mousedown', function() { - canvas.focus(); - }); - // Start the canvas focused. - canvas.focus(); - - Module = { - arguments: undefined, - screenIsReadOnly: true, - print: (function() { - return function(text) { - console.log(text); - }; - })(), - canvas: canvas, - // Prevent Emscripten from listening / blocking key events to the rest of the page by - // isolating keyboard input to the canvas. - keyboardListeningElement: canvas, - noInitialRun: false, - preInit: function() { - Module.arguments = build_dosbox_arguments(modulecfg, - meta_file.getElementsByTagName("emulator_start") - .item(0) - .textContent);; - LOADING_TEXT = 'Loading game file into file system'; - DOSBOX.BFSMountZip(game_file); - DOSBOX.moveConfigToRoot(); - window.clearInterval(drawloadingtimer); - if (callback) { - modulecfg.canvas = canvas; - window.setTimeout(function() { callback(modulecfg); }, 0); - } - } - }; - - file_countdown = 2; - - fetch_file('Metadata', - get_meta_url(game), - function(data) { - meta_file = data; - update_countdown(); - }, - 'document', true); - fetch_file('Game', - game, - function(data) { - game_file = new BrowserFS.BFSRequire('buffer').Buffer(data); - update_countdown(); - }); - }; - - var keyevent = function(e) { - if (typeof(loader_game)=='object') return; // game will start with click-to-play instead of [SPACE] char - if (e.which == 32) { - e.preventDefault(); - start(); - } - }; - - var start = function() { - // Prevent loading the game multiple times. - if (loading) { - return false; - } - window.removeEventListener('keypress', keyevent); - canvas.removeEventListener('click', start); - loading = true; - drawloadingtimer = window.setInterval(draw_loading_status, 1000/60); - if (precallback) { - window.setTimeout(precallback, 0); - } - init_module(); - return this; - }; - this.start = start; - window.DOSBOXstart = start;//global hook to method (so can be invoked with a "click to play" image being clicked) - - var drawsplash = function() { - var context = canvas.getContext('2d'); - splashimg.onload = function(){ - context.clearRect(0, 0, canvas.width, canvas.height); - context.save(); - context.drawImage(splashimg, canvas.width / 2 - (splashimg.width / 2), canvas.height / 3 - (splashimg.height / 2)); - context.font = '18px sans-serif'; - context.fillStyle = 'Black'; - context.textAlign = 'center'; - context.fillText('Click here to start', canvas.width / 2, (canvas.height / 2) + (splashimg.height / 2)); - context.textAlign = 'start'; - context.restore(); - }; - spinnerimg.onload = function() { - splashimg.src = '/images/dosbox.png';; - }; - spinnerimg.src = '/images/spinner.png'; - }; - - var configLoaded = function (data) { - moduledata = data; - window.addEventListener('keypress', keyevent); - canvas.addEventListener('click', start); - drawsplash(); - if (loading) { - // HACK: User clicked play before module metadata loaded, and play aborted. - // Now that metadata is ready, begin playing. - init_module(); - } - }; - - function try_start () { - if (!can_start()) { - return; - } - LOADING_TEXT = "Fetching item metadata..."; - has_started = true; - // NOTE: deliberately use cors.archive.org since this will 302 rewrite to iaXXXXX.us.archive.org/XX/items/jsmess_engine_v2/...json - // and need to keep that "artificial" extra domain-ish name to avoid CORS issues with IE/Safari - fetch_file('ModuleInfo', '//cors.archive.org/cors/jsmess_engine_v2/' + module + '.json', configLoaded, 'text', true, true); - } - - try_start(); -} - -DOSBOX._readySet = false; - -DOSBOX._readyList = []; - -DOSBOX._runReadies = function() { - if (DOSBOX._readyList) { - for (var r=0; r < DOSBOX._readyList.length; r++) { - DOSBOX._readyList[r].call(window, []); - }; - DOSBOX._readyList = []; - }; -}; - -DOSBOX._readyCheck = function() { - if (DOSBOX.running) { - DOSBOX._runReadies(); - } else { - DOSBOX._readySet = setTimeout(DOSBOX._readyCheck, 10); - }; -}; - -DOSBOX.ready = function(r) { - if (DOSBOX.running) { - r.call(window, []); - } else { - DOSBOX._readyList.push(function() { canvas.style.width = DOSBOX.width + 'px'; canvas.style.height = DOSBOX.height + 'px'; } ); - if (!(DOSBOX._readySet)) { - DOSBOX._readyCheck(); - } - }; -} - -DOSBOX.setScale = function() { - Module.canvas.style.width = DOSBOX.width + 'px'; - Module.canvas.style.height = DOSBOX.height + 'px'; -}; - -DOSBOX.fullScreenChangeHandler = function() { - if (!(document.mozFullScreenElement || document.fullScreenElement)) { - setTimeout(DOSBOX.setScale, 0); - } -}; - -DOSBOX.BFSMountZip = function BFSMount(loadedData) { - var zipfs = new BrowserFS.FileSystem.ZipFS(loadedData), - mfs = new BrowserFS.FileSystem.MountableFileSystem(), - memfs = new BrowserFS.FileSystem.InMemory(); - mfs.mount('/zip', zipfs); - mfs.mount('/mem', memfs); - BrowserFS.initialize(mfs); - // Copy the read-only zip file contents to a writable in-memory storage. - this.recursiveCopy('/zip', '/mem'); - // Re-initialize BFS to just use the writable in-memory storage. - BrowserFS.initialize(memfs); - // Mount the file system into Emscripten. - var BFS = new BrowserFS.EmscriptenFS(); - FS.mkdir('/dosprogram'); - FS.mount(BFS, {root: '/'}, '/dosprogram'); -}; - -// Helper function: Recursively copies contents from one folder to another. -DOSBOX.recursiveCopy = function recursiveCopy(oldDir, newDir) { - var path = BrowserFS.BFSRequire('path'), - fs = BrowserFS.BFSRequire('fs'); - copyDirectory(oldDir, newDir); - function copyDirectory(oldDir, newDir) { - if (!fs.existsSync(newDir)) { - fs.mkdirSync(newDir); - } - fs.readdirSync(oldDir).forEach(function(item) { - var p = path.resolve(oldDir, item), - newP = path.resolve(newDir, item); - if (fs.statSync(p).isDirectory()) { - copyDirectory(p, newP); - } else { - copyFile(p, newP); - } - }); - } - function copyFile(oldFile, newFile) { - fs.writeFileSync(newFile, fs.readFileSync(oldFile)); - } -}; - -/** - * Searches for dosbox.conf, and moves it to '/dosbox.conf' so dosbox uses it. +/*! + * @overview es6-promise - a tiny implementation of Promises/A+. + * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) + * @license Licensed under MIT license + * See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE + * @version 2.0.0 */ -DOSBOX.moveConfigToRoot = function moveConfigToRoot() { - if (typeof FS !== 'undefined') { - var dosboxConfPath = null; - // Recursively search for dosbox.conf. - function searchDirectory(dirPath) { - FS.readdir(dirPath).forEach(function(item) { - // Avoid infinite recursion by ignoring these entries, which exist at - // the root. - if (item === '.' || item === '..') { + +(function() { + "use strict"; + + function $$utils$$objectOrFunction(x) { + return typeof x === 'function' || (typeof x === 'object' && x !== null); + } + + function $$utils$$isFunction(x) { + return typeof x === 'function'; + } + + function $$utils$$isMaybeThenable(x) { + return typeof x === 'object' && x !== null; + } + + var $$utils$$_isArray; + + if (!Array.isArray) { + $$utils$$_isArray = function (x) { + return Object.prototype.toString.call(x) === '[object Array]'; + }; + } else { + $$utils$$_isArray = Array.isArray; + } + + var $$utils$$isArray = $$utils$$_isArray; + var $$utils$$now = Date.now || function() { return new Date().getTime(); }; + function $$utils$$F() { } + + var $$utils$$o_create = (Object.create || function (o) { + if (arguments.length > 1) { + throw new Error('Second argument not supported'); + } + if (typeof o !== 'object') { + throw new TypeError('Argument must be an object'); + } + $$utils$$F.prototype = o; + return new $$utils$$F(); + }); + + var $$asap$$len = 0; + + var $$asap$$default = function asap(callback, arg) { + $$asap$$queue[$$asap$$len] = callback; + $$asap$$queue[$$asap$$len + 1] = arg; + $$asap$$len += 2; + if ($$asap$$len === 2) { + // If len is 1, that means that we need to schedule an async flush. + // If additional callbacks are queued before the queue is flushed, they + // will be processed by this flush that we are scheduling. + $$asap$$scheduleFlush(); + } + }; + + var $$asap$$browserGlobal = (typeof window !== 'undefined') ? window : {}; + var $$asap$$BrowserMutationObserver = $$asap$$browserGlobal.MutationObserver || $$asap$$browserGlobal.WebKitMutationObserver; + + // test for web worker but not in IE10 + var $$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' && + typeof importScripts !== 'undefined' && + typeof MessageChannel !== 'undefined'; + + // node + function $$asap$$useNextTick() { + return function() { + process.nextTick($$asap$$flush); + }; + } + + function $$asap$$useMutationObserver() { + var iterations = 0; + var observer = new $$asap$$BrowserMutationObserver($$asap$$flush); + var node = document.createTextNode(''); + observer.observe(node, { characterData: true }); + + return function() { + node.data = (iterations = ++iterations % 2); + }; + } + + // web worker + function $$asap$$useMessageChannel() { + var channel = new MessageChannel(); + channel.port1.onmessage = $$asap$$flush; + return function () { + channel.port2.postMessage(0); + }; + } + + function $$asap$$useSetTimeout() { + return function() { + setTimeout($$asap$$flush, 1); + }; + } + + var $$asap$$queue = new Array(1000); + + function $$asap$$flush() { + for (var i = 0; i < $$asap$$len; i+=2) { + var callback = $$asap$$queue[i]; + var arg = $$asap$$queue[i+1]; + + callback(arg); + + $$asap$$queue[i] = undefined; + $$asap$$queue[i+1] = undefined; + } + + $$asap$$len = 0; + } + + var $$asap$$scheduleFlush; + + // Decide what async method to use to triggering processing of queued callbacks: + if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') { + $$asap$$scheduleFlush = $$asap$$useNextTick(); + } else if ($$asap$$BrowserMutationObserver) { + $$asap$$scheduleFlush = $$asap$$useMutationObserver(); + } else if ($$asap$$isWorker) { + $$asap$$scheduleFlush = $$asap$$useMessageChannel(); + } else { + $$asap$$scheduleFlush = $$asap$$useSetTimeout(); + } + + function $$$internal$$noop() {} + var $$$internal$$PENDING = void 0; + var $$$internal$$FULFILLED = 1; + var $$$internal$$REJECTED = 2; + var $$$internal$$GET_THEN_ERROR = new $$$internal$$ErrorObject(); + + function $$$internal$$selfFullfillment() { + return new TypeError("You cannot resolve a promise with itself"); + } + + function $$$internal$$cannotReturnOwn() { + return new TypeError('A promises callback cannot return that same promise.') + } + + function $$$internal$$getThen(promise) { + try { + return promise.then; + } catch(error) { + $$$internal$$GET_THEN_ERROR.error = error; + return $$$internal$$GET_THEN_ERROR; + } + } + + function $$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) { + try { + then.call(value, fulfillmentHandler, rejectionHandler); + } catch(e) { + return e; + } + } + + function $$$internal$$handleForeignThenable(promise, thenable, then) { + $$asap$$default(function(promise) { + var sealed = false; + var error = $$$internal$$tryThen(then, thenable, function(value) { + if (sealed) { return; } + sealed = true; + if (thenable !== value) { + $$$internal$$resolve(promise, value); + } else { + $$$internal$$fulfill(promise, value); + } + }, function(reason) { + if (sealed) { return; } + sealed = true; + + $$$internal$$reject(promise, reason); + }, 'Settle: ' + (promise._label || ' unknown promise')); + + if (!sealed && error) { + sealed = true; + $$$internal$$reject(promise, error); + } + }, promise); + } + + function $$$internal$$handleOwnThenable(promise, thenable) { + if (thenable._state === $$$internal$$FULFILLED) { + $$$internal$$fulfill(promise, thenable._result); + } else if (promise._state === $$$internal$$REJECTED) { + $$$internal$$reject(promise, thenable._result); + } else { + $$$internal$$subscribe(thenable, undefined, function(value) { + $$$internal$$resolve(promise, value); + }, function(reason) { + $$$internal$$reject(promise, reason); + }); + } + } + + function $$$internal$$handleMaybeThenable(promise, maybeThenable) { + if (maybeThenable.constructor === promise.constructor) { + $$$internal$$handleOwnThenable(promise, maybeThenable); + } else { + var then = $$$internal$$getThen(maybeThenable); + + if (then === $$$internal$$GET_THEN_ERROR) { + $$$internal$$reject(promise, $$$internal$$GET_THEN_ERROR.error); + } else if (then === undefined) { + $$$internal$$fulfill(promise, maybeThenable); + } else if ($$utils$$isFunction(then)) { + $$$internal$$handleForeignThenable(promise, maybeThenable, then); + } else { + $$$internal$$fulfill(promise, maybeThenable); + } + } + } + + function $$$internal$$resolve(promise, value) { + if (promise === value) { + $$$internal$$reject(promise, $$$internal$$selfFullfillment()); + } else if ($$utils$$objectOrFunction(value)) { + $$$internal$$handleMaybeThenable(promise, value); + } else { + $$$internal$$fulfill(promise, value); + } + } + + function $$$internal$$publishRejection(promise) { + if (promise._onerror) { + promise._onerror(promise._result); + } + + $$$internal$$publish(promise); + } + + function $$$internal$$fulfill(promise, value) { + if (promise._state !== $$$internal$$PENDING) { return; } + + promise._result = value; + promise._state = $$$internal$$FULFILLED; + + if (promise._subscribers.length === 0) { + } else { + $$asap$$default($$$internal$$publish, promise); + } + } + + function $$$internal$$reject(promise, reason) { + if (promise._state !== $$$internal$$PENDING) { return; } + promise._state = $$$internal$$REJECTED; + promise._result = reason; + + $$asap$$default($$$internal$$publishRejection, promise); + } + + function $$$internal$$subscribe(parent, child, onFulfillment, onRejection) { + var subscribers = parent._subscribers; + var length = subscribers.length; + + parent._onerror = null; + + subscribers[length] = child; + subscribers[length + $$$internal$$FULFILLED] = onFulfillment; + subscribers[length + $$$internal$$REJECTED] = onRejection; + + if (length === 0 && parent._state) { + $$asap$$default($$$internal$$publish, parent); + } + } + + function $$$internal$$publish(promise) { + var subscribers = promise._subscribers; + var settled = promise._state; + + if (subscribers.length === 0) { return; } + + var child, callback, detail = promise._result; + + for (var i = 0; i < subscribers.length; i += 3) { + child = subscribers[i]; + callback = subscribers[i + settled]; + + if (child) { + $$$internal$$invokeCallback(settled, child, callback, detail); + } else { + callback(detail); + } + } + + promise._subscribers.length = 0; + } + + function $$$internal$$ErrorObject() { + this.error = null; + } + + var $$$internal$$TRY_CATCH_ERROR = new $$$internal$$ErrorObject(); + + function $$$internal$$tryCatch(callback, detail) { + try { + return callback(detail); + } catch(e) { + $$$internal$$TRY_CATCH_ERROR.error = e; + return $$$internal$$TRY_CATCH_ERROR; + } + } + + function $$$internal$$invokeCallback(settled, promise, callback, detail) { + var hasCallback = $$utils$$isFunction(callback), + value, error, succeeded, failed; + + if (hasCallback) { + value = $$$internal$$tryCatch(callback, detail); + + if (value === $$$internal$$TRY_CATCH_ERROR) { + failed = true; + error = value.error; + value = null; + } else { + succeeded = true; + } + + if (promise === value) { + $$$internal$$reject(promise, $$$internal$$cannotReturnOwn()); 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.stat(itemPath); - if (FS.isDir(itemStat.mode)) { - searchDirectory(itemPath); - } else if (item === 'dosbox.conf') { - dosboxConfPath = itemPath; + + } else { + value = detail; + succeeded = true; + } + + if (promise._state !== $$$internal$$PENDING) { + // noop + } else if (hasCallback && succeeded) { + $$$internal$$resolve(promise, value); + } else if (failed) { + $$$internal$$reject(promise, error); + } else if (settled === $$$internal$$FULFILLED) { + $$$internal$$fulfill(promise, value); + } else if (settled === $$$internal$$REJECTED) { + $$$internal$$reject(promise, value); + } + } + + function $$$internal$$initializePromise(promise, resolver) { + try { + resolver(function resolvePromise(value){ + $$$internal$$resolve(promise, value); + }, function rejectPromise(reason) { + $$$internal$$reject(promise, reason); + }); + } catch(e) { + $$$internal$$reject(promise, e); + } + } + + function $$$enumerator$$makeSettledResult(state, position, value) { + if (state === $$$internal$$FULFILLED) { + return { + state: 'fulfilled', + value: value + }; + } else { + return { + state: 'rejected', + reason: value + }; + } + } + + function $$$enumerator$$Enumerator(Constructor, input, abortOnReject, label) { + this._instanceConstructor = Constructor; + this.promise = new Constructor($$$internal$$noop, label); + this._abortOnReject = abortOnReject; + + if (this._validateInput(input)) { + this._input = input; + this.length = input.length; + this._remaining = input.length; + + this._init(); + + if (this.length === 0) { + $$$internal$$fulfill(this.promise, this._result); + } else { + this.length = this.length || 0; + this._enumerate(); + if (this._remaining === 0) { + $$$internal$$fulfill(this.promise, this._result); + } + } + } else { + $$$internal$$reject(this.promise, this._validationError()); + } + } + + $$$enumerator$$Enumerator.prototype._validateInput = function(input) { + return $$utils$$isArray(input); + }; + + $$$enumerator$$Enumerator.prototype._validationError = function() { + return new Error('Array Methods must be provided an Array'); + }; + + $$$enumerator$$Enumerator.prototype._init = function() { + this._result = new Array(this.length); + }; + + var $$$enumerator$$default = $$$enumerator$$Enumerator; + + $$$enumerator$$Enumerator.prototype._enumerate = function() { + var length = this.length; + var promise = this.promise; + var input = this._input; + + for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) { + this._eachEntry(input[i], i); + } + }; + + $$$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) { + var c = this._instanceConstructor; + if ($$utils$$isMaybeThenable(entry)) { + if (entry.constructor === c && entry._state !== $$$internal$$PENDING) { + entry._onerror = null; + this._settledAt(entry._state, i, entry._result); + } else { + this._willSettleAt(c.resolve(entry), i); + } + } else { + this._remaining--; + this._result[i] = this._makeResult($$$internal$$FULFILLED, i, entry); + } + }; + + $$$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) { + var promise = this.promise; + + if (promise._state === $$$internal$$PENDING) { + this._remaining--; + + if (this._abortOnReject && state === $$$internal$$REJECTED) { + $$$internal$$reject(promise, value); + } else { + this._result[i] = this._makeResult(state, i, value); + } + } + + if (this._remaining === 0) { + $$$internal$$fulfill(promise, this._result); + } + }; + + $$$enumerator$$Enumerator.prototype._makeResult = function(state, i, value) { + return value; + }; + + $$$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) { + var enumerator = this; + + $$$internal$$subscribe(promise, undefined, function(value) { + enumerator._settledAt($$$internal$$FULFILLED, i, value); + }, function(reason) { + enumerator._settledAt($$$internal$$REJECTED, i, reason); + }); + }; + + var $$promise$all$$default = function all(entries, label) { + return new $$$enumerator$$default(this, entries, true /* abort on reject */, label).promise; + }; + + var $$promise$race$$default = function race(entries, label) { + /*jshint validthis:true */ + var Constructor = this; + + var promise = new Constructor($$$internal$$noop, label); + + if (!$$utils$$isArray(entries)) { + $$$internal$$reject(promise, new TypeError('You must pass an array to race.')); + return promise; + } + + var length = entries.length; + + function onFulfillment(value) { + $$$internal$$resolve(promise, value); + } + + function onRejection(reason) { + $$$internal$$reject(promise, reason); + } + + for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) { + $$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection); + } + + return promise; + }; + + var $$promise$resolve$$default = function resolve(object, label) { + /*jshint validthis:true */ + var Constructor = this; + + if (object && typeof object === 'object' && object.constructor === Constructor) { + return object; + } + + var promise = new Constructor($$$internal$$noop, label); + $$$internal$$resolve(promise, object); + return promise; + }; + + var $$promise$reject$$default = function reject(reason, label) { + /*jshint validthis:true */ + var Constructor = this; + var promise = new Constructor($$$internal$$noop, label); + $$$internal$$reject(promise, reason); + return promise; + }; + + var $$es6$promise$promise$$counter = 0; + + function $$es6$promise$promise$$needsResolver() { + throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); + } + + function $$es6$promise$promise$$needsNew() { + throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); + } + + var $$es6$promise$promise$$default = $$es6$promise$promise$$Promise; + + /** + Promise objects represent the eventual result of an asynchronous operation. The + primary way of interacting with a promise is through its `then` method, which + registers callbacks to receive either a promise’s eventual value or the reason + why the promise cannot be fulfilled. + + Terminology + ----------- + + - `promise` is an object or function with a `then` method whose behavior conforms to this specification. + - `thenable` is an object or function that defines a `then` method. + - `value` is any legal JavaScript value (including undefined, a thenable, or a promise). + - `exception` is a value that is thrown using the throw statement. + - `reason` is a value that indicates why a promise was rejected. + - `settled` the final resting state of a promise, fulfilled or rejected. + + A promise can be in one of three states: pending, fulfilled, or rejected. + + Promises that are fulfilled have a fulfillment value and are in the fulfilled + state. Promises that are rejected have a rejection reason and are in the + rejected state. A fulfillment value is never a thenable. + + Promises can also be said to *resolve* a value. If this value is also a + promise, then the original promise's settled state will match the value's + settled state. So a promise that *resolves* a promise that rejects will + itself reject, and a promise that *resolves* a promise that fulfills will + itself fulfill. + + + Basic Usage: + ------------ + + ```js + var promise = new Promise(function(resolve, reject) { + // on success + resolve(value); + + // on failure + reject(reason); + }); + + promise.then(function(value) { + // on fulfillment + }, function(reason) { + // on rejection + }); + ``` + + Advanced Usage: + --------------- + + Promises shine when abstracting away asynchronous interactions such as + `XMLHttpRequest`s. + + ```js + function getJSON(url) { + return new Promise(function(resolve, reject){ + var xhr = new XMLHttpRequest(); + + xhr.open('GET', url); + xhr.onreadystatechange = handler; + xhr.responseType = 'json'; + xhr.setRequestHeader('Accept', 'application/json'); + xhr.send(); + + function handler() { + if (this.readyState === this.DONE) { + if (this.status === 200) { + resolve(this.response); + } else { + reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']')); + } + } + }; + }); + } + + getJSON('/posts.json').then(function(json) { + // on fulfillment + }, function(reason) { + // on rejection + }); + ``` + + Unlike callbacks, promises are great composable primitives. + + ```js + Promise.all([ + getJSON('/posts'), + getJSON('/comments') + ]).then(function(values){ + values[0] // => postsJSON + values[1] // => commentsJSON + + return values; + }); + ``` + + @class Promise + @param {function} resolver + Useful for tooling. + @constructor + */ + function $$es6$promise$promise$$Promise(resolver) { + this._id = $$es6$promise$promise$$counter++; + this._state = undefined; + this._result = undefined; + this._subscribers = []; + + if ($$$internal$$noop !== resolver) { + if (!$$utils$$isFunction(resolver)) { + $$es6$promise$promise$$needsResolver(); + } + + if (!(this instanceof $$es6$promise$promise$$Promise)) { + $$es6$promise$promise$$needsNew(); + } + + $$$internal$$initializePromise(this, resolver); + } + } + + $$es6$promise$promise$$Promise.all = $$promise$all$$default; + $$es6$promise$promise$$Promise.race = $$promise$race$$default; + $$es6$promise$promise$$Promise.resolve = $$promise$resolve$$default; + $$es6$promise$promise$$Promise.reject = $$promise$reject$$default; + + $$es6$promise$promise$$Promise.prototype = { + constructor: $$es6$promise$promise$$Promise, + + /** + The primary way of interacting with a promise is through its `then` method, + which registers callbacks to receive either a promise's eventual value or the + reason why the promise cannot be fulfilled. + + ```js + findUser().then(function(user){ + // user is available + }, function(reason){ + // user is unavailable, and you are given the reason why + }); + ``` + + Chaining + -------- + + The return value of `then` is itself a promise. This second, 'downstream' + promise is resolved with the return value of the first promise's fulfillment + or rejection handler, or rejected if the handler throws an exception. + + ```js + findUser().then(function (user) { + return user.name; + }, function (reason) { + return 'default name'; + }).then(function (userName) { + // If `findUser` fulfilled, `userName` will be the user's name, otherwise it + // will be `'default name'` + }); + + findUser().then(function (user) { + throw new Error('Found user, but still unhappy'); + }, function (reason) { + throw new Error('`findUser` rejected and we're unhappy'); + }).then(function (value) { + // never reached + }, function (reason) { + // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'. + // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'. + }); + ``` + If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream. + + ```js + findUser().then(function (user) { + throw new PedagogicalException('Upstream error'); + }).then(function (value) { + // never reached + }).then(function (value) { + // never reached + }, function (reason) { + // The `PedgagocialException` is propagated all the way down to here + }); + ``` + + Assimilation + ------------ + + Sometimes the value you want to propagate to a downstream promise can only be + retrieved asynchronously. This can be achieved by returning a promise in the + fulfillment or rejection handler. The downstream promise will then be pending + until the returned promise is settled. This is called *assimilation*. + + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // The user's comments are now available + }); + ``` + + If the assimliated promise rejects, then the downstream promise will also reject. + + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // If `findCommentsByAuthor` fulfills, we'll have the value here + }, function (reason) { + // If `findCommentsByAuthor` rejects, we'll have the reason here + }); + ``` + + Simple Example + -------------- + + Synchronous Example + + ```javascript + var result; + + try { + result = findResult(); + // success + } catch(reason) { + // failure + } + ``` + + Errback Example + + ```js + findResult(function(result, err){ + if (err) { + // failure + } else { + // success } }); - } - searchDirectory('/'); + ``` - if (dosboxConfPath !== null) { - FS.writeFile('/dosbox.conf', FS.readFile(dosboxConfPath), { encoding: 'binary' }); + Promise Example; + + ```javascript + findResult().then(function(result){ + // success + }, function(reason){ + // failure + }); + ``` + + Advanced Example + -------------- + + Synchronous Example + + ```javascript + var author, books; + + try { + author = findAuthor(); + books = findBooksByAuthor(author); + // success + } catch(reason) { + // failure + } + ``` + + Errback Example + + ```js + + function foundBooks(books) { + + } + + function failure(reason) { + + } + + findAuthor(function(author, err){ + if (err) { + failure(err); + // failure + } else { + try { + findBoooksByAuthor(author, function(books, err) { + if (err) { + failure(err); + } else { + try { + foundBooks(books); + } catch(reason) { + failure(reason); + } + } + }); + } catch(error) { + failure(err); + } + // success + } + }); + ``` + + Promise Example; + + ```javascript + findAuthor(). + then(findBooksByAuthor). + then(function(books){ + // found books + }).catch(function(reason){ + // something went wrong + }); + ``` + + @method then + @param {Function} onFulfilled + @param {Function} onRejected + Useful for tooling. + @return {Promise} + */ + then: function(onFulfillment, onRejection) { + var parent = this; + var state = parent._state; + + if (state === $$$internal$$FULFILLED && !onFulfillment || state === $$$internal$$REJECTED && !onRejection) { + return this; + } + + var child = new this.constructor($$$internal$$noop); + var result = parent._result; + + if (state) { + var callback = arguments[state - 1]; + $$asap$$default(function(){ + $$$internal$$invokeCallback(state, child, callback, result); + }); + } else { + $$$internal$$subscribe(parent, child, onFulfillment, onRejection); + } + + return child; + }, + + /** + `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same + as the catch block of a try/catch statement. + + ```js + function findAuthor(){ + throw new Error('couldn't find that author'); + } + + // synchronous + try { + findAuthor(); + } catch(reason) { + // something went wrong + } + + // async with promises + findAuthor().catch(function(reason){ + // something went wrong + }); + ``` + + @method catch + @param {Function} onRejection + Useful for tooling. + @return {Promise} + */ + 'catch': function(onRejection) { + return this.then(null, onRejection); + } + }; + + var $$es6$promise$polyfill$$default = function polyfill() { + var local; + + if (typeof global !== 'undefined') { + local = global; + } else if (typeof window !== 'undefined' && window.document) { + local = window; + } else { + local = self; + } + + var es6PromiseSupport = + "Promise" in local && + // Some of these methods are missing from + // Firefox/Chrome experimental implementations + "resolve" in local.Promise && + "reject" in local.Promise && + "all" in local.Promise && + "race" in local.Promise && + // Older version of the spec had a resolver object + // as the arg rather than a function + (function() { + var resolve; + new local.Promise(function(r) { resolve = r; }); + return $$utils$$isFunction(resolve); + }()); + + if (!es6PromiseSupport) { + local.Promise = $$es6$promise$promise$$default; + } + }; + + var es6$promise$umd$$ES6Promise = { + 'Promise': $$es6$promise$promise$$default, + 'polyfill': $$es6$promise$polyfill$$default + }; + + /* global define:true module:true window: true */ + if (typeof define === 'function' && define['amd']) { + define(function() { return es6$promise$umd$$ES6Promise; }); + } else if (typeof module !== 'undefined' && module['exports']) { + module['exports'] = es6$promise$umd$$ES6Promise; + } else if (typeof this !== 'undefined') { + this['ES6Promise'] = es6$promise$umd$$ES6Promise; } - } -}; +}).call(this); + +var Module = null; + +(function (Promise) { + function DOSBOX(canvas, module, game, precallback, callback, scale) { + var js_url; + var moduledata; + var requests = []; + var drawloadingtimer; + var file_countdown; + var spinnerrot = 0; + var splashimg = new Image(); + var spinnerimg = new Image(); + // TODO: Have an enum value that communicates the current state of DOSBOX, e.g. 'initializing', 'loading', 'running'. + var has_started = false; + var loading = false; + var LOADING_TEXT; + + 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 can_start = function () { + return !!canvas && !!module && !!game && !!scale && !has_started; + }; + + this.setscale = function(_scale) { + scale = _scale; + try_start(); + return this; + }; + + this.setprecallback = function(_precallback) { + precallback = _precallback; + return this; + }; + + this.setcallback = function(_callback) { + callback = _callback; + return this; + }; + + this.setmodule = function(_module) { + module = _module; + try_start(); + return this; + }; + + this.setgame = function(_game) { + game = _game; + try_start(); + return this; + }; + + var draw_loading_status = function() { + var context = canvas.getContext('2d'); + context.clearRect(0, 0, canvas.width, canvas.height); + context.drawImage(splashimg, canvas.width / 2 - (splashimg.width / 2), canvas.height / 3 - (splashimg.height / 2)); + var spinnerpos = (canvas.height / 2 + splashimg.height / 2) + 16; + context.save(); + context.translate((canvas.width / 2), spinnerpos); + context.rotate(spinnerrot); + context.drawImage(spinnerimg, -(64/2), -(64/2), 64, 64); + context.restore(); + context.save(); + context.font = '18px sans-serif'; + context.fillStyle = 'Black'; + context.textAlign = 'center'; + context.fillText(LOADING_TEXT, canvas.width / 2, (canvas.height / 2) + (splashimg.height / 4)); + context.restore(); + spinnerrot += .25; + }; + + var progress_fetch_file = function(e) { + if (e.lengthComputable) { + e.target.progress = e.loaded / e.total; + e.target.loaded = e.loaded; + e.target.total = e.total; + e.target.lengthComputable = e.lengthComputable; + } + }; + + var fetch_file = function(title, url, rt, raw, unmanaged) { + return new Promise(function (resolve, reject) { + var xhr = new XMLHttpRequest(); + xhr.open('GET', url, true); + xhr.responseType = rt ? rt : 'arraybuffer'; + xhr.onload = function(e) { + if (xhr.status === 200) { + if (!unmanaged) { + xhr.progress = 1.0; + } + resolve(raw ? xhr.response + : new Int8Array(xhr.response)); + } + }; + xhr.onerror = reject; + if (!unmanaged) { + xhr.onprogress = progress_fetch_file; + xhr.title = title; + xhr.progress = 0; + xhr.total = 0; + xhr.loaded = 0; + xhr.lengthComputable = false; + requests.push(xhr); + } + xhr.send(); + }); + }; + + var update_countdown = function() { + file_countdown -= 1; + if (file_countdown <= 0) { + loading = false; + + // see archive.js for the mute/unmute button/JS + if (!($.cookie && $.cookie('unmute'))){ + setTimeout(function(){ + // someone moved it from 1st to 2nd! + if (DOSBOX && typeof(DOSBOX.sdl_pauseaudio)!='undefined') + DOSBOX.sdl_pauseaudio(1); + else if (typeof _SDL_PauseAudio !== "undefined") + _SDL_PauseAudio(1); + }, 3000); + } + } + }; + + var build_dosbox_arguments = function (config, emulator_start) { + LOADING_TEXT = 'Building arguments'; + return ['/dosprogram/'+ emulator_start]; + }; + + var get_game_name = function (game_path) { + return game_path.split('/').pop(); + }; + + var get_meta_url = function (game_path) { + var path = game_path.split('/'); + // 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 + return "//cors.archive.org/cors/"+ path[4] +"/"+ path[4] +"_meta.xml"; + }; + + var get_js_url = function (js_filename) { + // 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 + return "//cors.archive.org/cors/jsmess_engine_v2/"+ js_filename; + }; + + var keyevent = function(e) { + if (typeof(loader_game)=='object') return; // game will start with click-to-play instead of [SPACE] char + if (e.which == 32) { + e.preventDefault(); + start(); + } + }; + + var start = function() { + // Prevent loading the game multiple times. + if (loading) { + return false; + } + window.removeEventListener('keypress', keyevent); + canvas.removeEventListener('click', start); + loading = true; + drawloadingtimer = window.setInterval(draw_loading_status, 1000/60); + if (precallback) { + window.setTimeout(precallback, 0); + } + + LOADING_TEXT = 'Parsing config'; + var modulecfg = JSON.parse(moduledata); + + var nr = modulecfg['native_resolution']; + DOSBOX.width = nr[0] * scale; + DOSBOX.height = nr[1] * scale; + + file_countdown = 2; + + LOADING_TEXT = 'Downloading game data...'; + Promise.all([fetch_file('Metadata', + get_meta_url(game), + 'document', true), + fetch_file('Game', + game)]) + .then(function(game_data) { + Module = init_module(modulecfg, game_data[0], game_data[1]); + if (modulecfg['js_filename']) { + LOADING_TEXT = 'Launching DosBox'; + attach_script(modulecfg['js_filename']); + } else { + LOADING_TEXT = 'Invalid System Disk'; + } + }); + return this; + }; + this.start = start; + window.DOSBOXstart = start;//global hook to method (so can be invoked with a "click to play" image being clicked) + + var init_module = function(modulecfg, meta_file, game_file) { + if (moduledata == null) { + // HACK: Module data isn't ready yet. It'll call us once loaded. + return null; + } + return { arguments: build_dosbox_arguments(modulecfg, + meta_file.getElementsByTagName("emulator_start") + .item(0) + .textContent), + screenIsReadOnly: true, + print: function (text) { console.log(text); }, + canvas: canvas, + noInitialRun: false, + preInit: function () { + LOADING_TEXT = 'Loading game file into file system'; + DOSBOX.BFSMountZip(new BrowserFS.BFSRequire('buffer').Buffer(game_file)); + DOSBOX.moveConfigToRoot(); + window.clearInterval(drawloadingtimer); + if (callback) { + modulecfg.canvas = canvas; + window.setTimeout(function() { + callback(modulecfg); + }, + 0); + } + } + }; + }; + + var drawsplash = function() { + var context = canvas.getContext('2d'); + splashimg.onload = function(){ + context.clearRect(0, 0, canvas.width, canvas.height); + context.save(); + context.drawImage(splashimg, canvas.width / 2 - (splashimg.width / 2), canvas.height / 3 - (splashimg.height / 2)); + context.font = '18px sans-serif'; + context.fillStyle = 'Black'; + context.textAlign = 'center'; + context.fillText('Click here to start', canvas.width / 2, (canvas.height / 2) + (splashimg.height / 2)); + context.textAlign = 'start'; + context.restore(); + }; + spinnerimg.onload = function() { + splashimg.src = '/images/dosbox.png';; + }; + spinnerimg.src = '/images/spinner.png'; + }; + + function try_start () { + if (!can_start()) { + return; + } + LOADING_TEXT = "Fetching item metadata..."; + has_started = true; + // NOTE: deliberately use cors.archive.org since this will 302 rewrite to iaXXXXX.us.archive.org/XX/items/jsmess_engine_v2/...json + // and need to keep that "artificial" extra domain-ish name to avoid CORS issues with IE/Safari + var config = fetch_file('Module Info', + '//archive.org/cors/jsmess_engine_v2/' + module + '.json', + 'text', true, true); + config.then(function (data) { + moduledata = data; + window.addEventListener('keypress', keyevent); + canvas.addEventListener('click', start); + drawsplash(); + if (loading) { + // HACK: User clicked play before module metadata loaded, and play aborted. + // Now that metadata is ready, begin playing. + init_module(); + } + }); + } + + function attach_script(js_url) { + if (js_url) { + var head = document.getElementsByTagName('head')[0]; + var newScript = document.createElement('script'); + newScript.type = 'text/javascript'; + newScript.src = get_js_url(js_url); + head.appendChild(newScript); + } + } + + try_start(); + } + + DOSBOX._readySet = false; + + DOSBOX._readyList = []; + + DOSBOX._runReadies = function() { + if (DOSBOX._readyList) { + for (var r=0; r < DOSBOX._readyList.length; r++) { + DOSBOX._readyList[r].call(window, []); + }; + DOSBOX._readyList = []; + }; + }; + + DOSBOX._readyCheck = function() { + if (DOSBOX.running) { + DOSBOX._runReadies(); + } else { + DOSBOX._readySet = setTimeout(DOSBOX._readyCheck, 10); + }; + }; + + DOSBOX.ready = function(r) { + if (DOSBOX.running) { + r.call(window, []); + } else { + DOSBOX._readyList.push(function() { canvas.style.width = DOSBOX.width + 'px'; canvas.style.height = DOSBOX.height + 'px'; } ); + if (!(DOSBOX._readySet)) { + DOSBOX._readyCheck(); + } + }; + }; + + DOSBOX.setScale = function() { + Module.canvas.style.width = DOSBOX.width + 'px'; + Module.canvas.style.height = DOSBOX.height + 'px'; + }; + + DOSBOX.fullScreenChangeHandler = function() { + if (!(document.mozFullScreenElement || document.fullScreenElement)) { + setTimeout(DOSBOX.setScale, 0); + } + }; + + DOSBOX.BFSMountZip = function BFSMount(loadedData) { + var zipfs = new BrowserFS.FileSystem.ZipFS(loadedData), + mfs = new BrowserFS.FileSystem.MountableFileSystem(), + memfs = new BrowserFS.FileSystem.InMemory(); + mfs.mount('/zip', zipfs); + mfs.mount('/mem', memfs); + BrowserFS.initialize(mfs); + // Copy the read-only zip file contents to a writable in-memory storage. + this.recursiveCopy('/zip', '/mem'); + // Re-initialize BFS to just use the writable in-memory storage. + BrowserFS.initialize(memfs); + // Mount the file system into Emscripten. + var BFS = new BrowserFS.EmscriptenFS(); + FS.mkdir('/dosprogram'); + FS.mount(BFS, {root: '/'}, '/dosprogram'); + }; + + // Helper function: Recursively copies contents from one folder to another. + DOSBOX.recursiveCopy = function recursiveCopy(oldDir, newDir) { + var path = BrowserFS.BFSRequire('path'), + fs = BrowserFS.BFSRequire('fs'); + copyDirectory(oldDir, newDir); + function copyDirectory(oldDir, newDir) { + if (!fs.existsSync(newDir)) { + fs.mkdirSync(newDir); + } + fs.readdirSync(oldDir).forEach(function(item) { + var p = path.resolve(oldDir, item), + newP = path.resolve(newDir, item); + if (fs.statSync(p).isDirectory()) { + copyDirectory(p, newP); + } else { + copyFile(p, newP); + } + }); + } + function copyFile(oldFile, newFile) { + fs.writeFileSync(newFile, fs.readFileSync(oldFile)); + } + }; + + /** + * Searches for dosbox.conf, and moves it to '/dosbox.conf' so dosbox uses it. + */ + DOSBOX.moveConfigToRoot = function moveConfigToRoot() { + if (typeof FS !== 'undefined') { + var dosboxConfPath = null; + // Recursively search for dosbox.conf. + function searchDirectory(dirPath) { + FS.readdir(dirPath).forEach(function(item) { + // 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.stat(itemPath); + if (FS.isDir(itemStat.mode)) { + searchDirectory(itemPath); + } else if (item === 'dosbox.conf') { + dosboxConfPath = itemPath; + } + }); + } + searchDirectory('/'); + + if (dosboxConfPath !== null) { + FS.writeFile('/dosbox.conf', FS.readFile(dosboxConfPath), { encoding: 'binary' }); + } + } + }; + + window.DOSBOX = DOSBOX; + })(typeof Promise === 'undefined' ? ES6Promise.Promise : Promise); diff --git a/es6-promise-2.0.1.js b/es6-promise-2.0.1.js new file mode 100644 index 0000000..ee1ba96 --- /dev/null +++ b/es6-promise-2.0.1.js @@ -0,0 +1,960 @@ +/*! + * @overview es6-promise - a tiny implementation of Promises/A+. + * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) + * @license Licensed under MIT license + * See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE + * @version 2.0.0 + */ + +(function() { + "use strict"; + + function $$utils$$objectOrFunction(x) { + return typeof x === 'function' || (typeof x === 'object' && x !== null); + } + + function $$utils$$isFunction(x) { + return typeof x === 'function'; + } + + function $$utils$$isMaybeThenable(x) { + return typeof x === 'object' && x !== null; + } + + var $$utils$$_isArray; + + if (!Array.isArray) { + $$utils$$_isArray = function (x) { + return Object.prototype.toString.call(x) === '[object Array]'; + }; + } else { + $$utils$$_isArray = Array.isArray; + } + + var $$utils$$isArray = $$utils$$_isArray; + var $$utils$$now = Date.now || function() { return new Date().getTime(); }; + function $$utils$$F() { } + + var $$utils$$o_create = (Object.create || function (o) { + if (arguments.length > 1) { + throw new Error('Second argument not supported'); + } + if (typeof o !== 'object') { + throw new TypeError('Argument must be an object'); + } + $$utils$$F.prototype = o; + return new $$utils$$F(); + }); + + var $$asap$$len = 0; + + var $$asap$$default = function asap(callback, arg) { + $$asap$$queue[$$asap$$len] = callback; + $$asap$$queue[$$asap$$len + 1] = arg; + $$asap$$len += 2; + if ($$asap$$len === 2) { + // If len is 1, that means that we need to schedule an async flush. + // If additional callbacks are queued before the queue is flushed, they + // will be processed by this flush that we are scheduling. + $$asap$$scheduleFlush(); + } + }; + + var $$asap$$browserGlobal = (typeof window !== 'undefined') ? window : {}; + var $$asap$$BrowserMutationObserver = $$asap$$browserGlobal.MutationObserver || $$asap$$browserGlobal.WebKitMutationObserver; + + // test for web worker but not in IE10 + var $$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' && + typeof importScripts !== 'undefined' && + typeof MessageChannel !== 'undefined'; + + // node + function $$asap$$useNextTick() { + return function() { + process.nextTick($$asap$$flush); + }; + } + + function $$asap$$useMutationObserver() { + var iterations = 0; + var observer = new $$asap$$BrowserMutationObserver($$asap$$flush); + var node = document.createTextNode(''); + observer.observe(node, { characterData: true }); + + return function() { + node.data = (iterations = ++iterations % 2); + }; + } + + // web worker + function $$asap$$useMessageChannel() { + var channel = new MessageChannel(); + channel.port1.onmessage = $$asap$$flush; + return function () { + channel.port2.postMessage(0); + }; + } + + function $$asap$$useSetTimeout() { + return function() { + setTimeout($$asap$$flush, 1); + }; + } + + var $$asap$$queue = new Array(1000); + + function $$asap$$flush() { + for (var i = 0; i < $$asap$$len; i+=2) { + var callback = $$asap$$queue[i]; + var arg = $$asap$$queue[i+1]; + + callback(arg); + + $$asap$$queue[i] = undefined; + $$asap$$queue[i+1] = undefined; + } + + $$asap$$len = 0; + } + + var $$asap$$scheduleFlush; + + // Decide what async method to use to triggering processing of queued callbacks: + if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') { + $$asap$$scheduleFlush = $$asap$$useNextTick(); + } else if ($$asap$$BrowserMutationObserver) { + $$asap$$scheduleFlush = $$asap$$useMutationObserver(); + } else if ($$asap$$isWorker) { + $$asap$$scheduleFlush = $$asap$$useMessageChannel(); + } else { + $$asap$$scheduleFlush = $$asap$$useSetTimeout(); + } + + function $$$internal$$noop() {} + var $$$internal$$PENDING = void 0; + var $$$internal$$FULFILLED = 1; + var $$$internal$$REJECTED = 2; + var $$$internal$$GET_THEN_ERROR = new $$$internal$$ErrorObject(); + + function $$$internal$$selfFullfillment() { + return new TypeError("You cannot resolve a promise with itself"); + } + + function $$$internal$$cannotReturnOwn() { + return new TypeError('A promises callback cannot return that same promise.') + } + + function $$$internal$$getThen(promise) { + try { + return promise.then; + } catch(error) { + $$$internal$$GET_THEN_ERROR.error = error; + return $$$internal$$GET_THEN_ERROR; + } + } + + function $$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) { + try { + then.call(value, fulfillmentHandler, rejectionHandler); + } catch(e) { + return e; + } + } + + function $$$internal$$handleForeignThenable(promise, thenable, then) { + $$asap$$default(function(promise) { + var sealed = false; + var error = $$$internal$$tryThen(then, thenable, function(value) { + if (sealed) { return; } + sealed = true; + if (thenable !== value) { + $$$internal$$resolve(promise, value); + } else { + $$$internal$$fulfill(promise, value); + } + }, function(reason) { + if (sealed) { return; } + sealed = true; + + $$$internal$$reject(promise, reason); + }, 'Settle: ' + (promise._label || ' unknown promise')); + + if (!sealed && error) { + sealed = true; + $$$internal$$reject(promise, error); + } + }, promise); + } + + function $$$internal$$handleOwnThenable(promise, thenable) { + if (thenable._state === $$$internal$$FULFILLED) { + $$$internal$$fulfill(promise, thenable._result); + } else if (promise._state === $$$internal$$REJECTED) { + $$$internal$$reject(promise, thenable._result); + } else { + $$$internal$$subscribe(thenable, undefined, function(value) { + $$$internal$$resolve(promise, value); + }, function(reason) { + $$$internal$$reject(promise, reason); + }); + } + } + + function $$$internal$$handleMaybeThenable(promise, maybeThenable) { + if (maybeThenable.constructor === promise.constructor) { + $$$internal$$handleOwnThenable(promise, maybeThenable); + } else { + var then = $$$internal$$getThen(maybeThenable); + + if (then === $$$internal$$GET_THEN_ERROR) { + $$$internal$$reject(promise, $$$internal$$GET_THEN_ERROR.error); + } else if (then === undefined) { + $$$internal$$fulfill(promise, maybeThenable); + } else if ($$utils$$isFunction(then)) { + $$$internal$$handleForeignThenable(promise, maybeThenable, then); + } else { + $$$internal$$fulfill(promise, maybeThenable); + } + } + } + + function $$$internal$$resolve(promise, value) { + if (promise === value) { + $$$internal$$reject(promise, $$$internal$$selfFullfillment()); + } else if ($$utils$$objectOrFunction(value)) { + $$$internal$$handleMaybeThenable(promise, value); + } else { + $$$internal$$fulfill(promise, value); + } + } + + function $$$internal$$publishRejection(promise) { + if (promise._onerror) { + promise._onerror(promise._result); + } + + $$$internal$$publish(promise); + } + + function $$$internal$$fulfill(promise, value) { + if (promise._state !== $$$internal$$PENDING) { return; } + + promise._result = value; + promise._state = $$$internal$$FULFILLED; + + if (promise._subscribers.length === 0) { + } else { + $$asap$$default($$$internal$$publish, promise); + } + } + + function $$$internal$$reject(promise, reason) { + if (promise._state !== $$$internal$$PENDING) { return; } + promise._state = $$$internal$$REJECTED; + promise._result = reason; + + $$asap$$default($$$internal$$publishRejection, promise); + } + + function $$$internal$$subscribe(parent, child, onFulfillment, onRejection) { + var subscribers = parent._subscribers; + var length = subscribers.length; + + parent._onerror = null; + + subscribers[length] = child; + subscribers[length + $$$internal$$FULFILLED] = onFulfillment; + subscribers[length + $$$internal$$REJECTED] = onRejection; + + if (length === 0 && parent._state) { + $$asap$$default($$$internal$$publish, parent); + } + } + + function $$$internal$$publish(promise) { + var subscribers = promise._subscribers; + var settled = promise._state; + + if (subscribers.length === 0) { return; } + + var child, callback, detail = promise._result; + + for (var i = 0; i < subscribers.length; i += 3) { + child = subscribers[i]; + callback = subscribers[i + settled]; + + if (child) { + $$$internal$$invokeCallback(settled, child, callback, detail); + } else { + callback(detail); + } + } + + promise._subscribers.length = 0; + } + + function $$$internal$$ErrorObject() { + this.error = null; + } + + var $$$internal$$TRY_CATCH_ERROR = new $$$internal$$ErrorObject(); + + function $$$internal$$tryCatch(callback, detail) { + try { + return callback(detail); + } catch(e) { + $$$internal$$TRY_CATCH_ERROR.error = e; + return $$$internal$$TRY_CATCH_ERROR; + } + } + + function $$$internal$$invokeCallback(settled, promise, callback, detail) { + var hasCallback = $$utils$$isFunction(callback), + value, error, succeeded, failed; + + if (hasCallback) { + value = $$$internal$$tryCatch(callback, detail); + + if (value === $$$internal$$TRY_CATCH_ERROR) { + failed = true; + error = value.error; + value = null; + } else { + succeeded = true; + } + + if (promise === value) { + $$$internal$$reject(promise, $$$internal$$cannotReturnOwn()); + return; + } + + } else { + value = detail; + succeeded = true; + } + + if (promise._state !== $$$internal$$PENDING) { + // noop + } else if (hasCallback && succeeded) { + $$$internal$$resolve(promise, value); + } else if (failed) { + $$$internal$$reject(promise, error); + } else if (settled === $$$internal$$FULFILLED) { + $$$internal$$fulfill(promise, value); + } else if (settled === $$$internal$$REJECTED) { + $$$internal$$reject(promise, value); + } + } + + function $$$internal$$initializePromise(promise, resolver) { + try { + resolver(function resolvePromise(value){ + $$$internal$$resolve(promise, value); + }, function rejectPromise(reason) { + $$$internal$$reject(promise, reason); + }); + } catch(e) { + $$$internal$$reject(promise, e); + } + } + + function $$$enumerator$$makeSettledResult(state, position, value) { + if (state === $$$internal$$FULFILLED) { + return { + state: 'fulfilled', + value: value + }; + } else { + return { + state: 'rejected', + reason: value + }; + } + } + + function $$$enumerator$$Enumerator(Constructor, input, abortOnReject, label) { + this._instanceConstructor = Constructor; + this.promise = new Constructor($$$internal$$noop, label); + this._abortOnReject = abortOnReject; + + if (this._validateInput(input)) { + this._input = input; + this.length = input.length; + this._remaining = input.length; + + this._init(); + + if (this.length === 0) { + $$$internal$$fulfill(this.promise, this._result); + } else { + this.length = this.length || 0; + this._enumerate(); + if (this._remaining === 0) { + $$$internal$$fulfill(this.promise, this._result); + } + } + } else { + $$$internal$$reject(this.promise, this._validationError()); + } + } + + $$$enumerator$$Enumerator.prototype._validateInput = function(input) { + return $$utils$$isArray(input); + }; + + $$$enumerator$$Enumerator.prototype._validationError = function() { + return new Error('Array Methods must be provided an Array'); + }; + + $$$enumerator$$Enumerator.prototype._init = function() { + this._result = new Array(this.length); + }; + + var $$$enumerator$$default = $$$enumerator$$Enumerator; + + $$$enumerator$$Enumerator.prototype._enumerate = function() { + var length = this.length; + var promise = this.promise; + var input = this._input; + + for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) { + this._eachEntry(input[i], i); + } + }; + + $$$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) { + var c = this._instanceConstructor; + if ($$utils$$isMaybeThenable(entry)) { + if (entry.constructor === c && entry._state !== $$$internal$$PENDING) { + entry._onerror = null; + this._settledAt(entry._state, i, entry._result); + } else { + this._willSettleAt(c.resolve(entry), i); + } + } else { + this._remaining--; + this._result[i] = this._makeResult($$$internal$$FULFILLED, i, entry); + } + }; + + $$$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) { + var promise = this.promise; + + if (promise._state === $$$internal$$PENDING) { + this._remaining--; + + if (this._abortOnReject && state === $$$internal$$REJECTED) { + $$$internal$$reject(promise, value); + } else { + this._result[i] = this._makeResult(state, i, value); + } + } + + if (this._remaining === 0) { + $$$internal$$fulfill(promise, this._result); + } + }; + + $$$enumerator$$Enumerator.prototype._makeResult = function(state, i, value) { + return value; + }; + + $$$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) { + var enumerator = this; + + $$$internal$$subscribe(promise, undefined, function(value) { + enumerator._settledAt($$$internal$$FULFILLED, i, value); + }, function(reason) { + enumerator._settledAt($$$internal$$REJECTED, i, reason); + }); + }; + + var $$promise$all$$default = function all(entries, label) { + return new $$$enumerator$$default(this, entries, true /* abort on reject */, label).promise; + }; + + var $$promise$race$$default = function race(entries, label) { + /*jshint validthis:true */ + var Constructor = this; + + var promise = new Constructor($$$internal$$noop, label); + + if (!$$utils$$isArray(entries)) { + $$$internal$$reject(promise, new TypeError('You must pass an array to race.')); + return promise; + } + + var length = entries.length; + + function onFulfillment(value) { + $$$internal$$resolve(promise, value); + } + + function onRejection(reason) { + $$$internal$$reject(promise, reason); + } + + for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) { + $$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection); + } + + return promise; + }; + + var $$promise$resolve$$default = function resolve(object, label) { + /*jshint validthis:true */ + var Constructor = this; + + if (object && typeof object === 'object' && object.constructor === Constructor) { + return object; + } + + var promise = new Constructor($$$internal$$noop, label); + $$$internal$$resolve(promise, object); + return promise; + }; + + var $$promise$reject$$default = function reject(reason, label) { + /*jshint validthis:true */ + var Constructor = this; + var promise = new Constructor($$$internal$$noop, label); + $$$internal$$reject(promise, reason); + return promise; + }; + + var $$es6$promise$promise$$counter = 0; + + function $$es6$promise$promise$$needsResolver() { + throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); + } + + function $$es6$promise$promise$$needsNew() { + throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); + } + + var $$es6$promise$promise$$default = $$es6$promise$promise$$Promise; + + /** + Promise objects represent the eventual result of an asynchronous operation. The + primary way of interacting with a promise is through its `then` method, which + registers callbacks to receive either a promise’s eventual value or the reason + why the promise cannot be fulfilled. + + Terminology + ----------- + + - `promise` is an object or function with a `then` method whose behavior conforms to this specification. + - `thenable` is an object or function that defines a `then` method. + - `value` is any legal JavaScript value (including undefined, a thenable, or a promise). + - `exception` is a value that is thrown using the throw statement. + - `reason` is a value that indicates why a promise was rejected. + - `settled` the final resting state of a promise, fulfilled or rejected. + + A promise can be in one of three states: pending, fulfilled, or rejected. + + Promises that are fulfilled have a fulfillment value and are in the fulfilled + state. Promises that are rejected have a rejection reason and are in the + rejected state. A fulfillment value is never a thenable. + + Promises can also be said to *resolve* a value. If this value is also a + promise, then the original promise's settled state will match the value's + settled state. So a promise that *resolves* a promise that rejects will + itself reject, and a promise that *resolves* a promise that fulfills will + itself fulfill. + + + Basic Usage: + ------------ + + ```js + var promise = new Promise(function(resolve, reject) { + // on success + resolve(value); + + // on failure + reject(reason); + }); + + promise.then(function(value) { + // on fulfillment + }, function(reason) { + // on rejection + }); + ``` + + Advanced Usage: + --------------- + + Promises shine when abstracting away asynchronous interactions such as + `XMLHttpRequest`s. + + ```js + function getJSON(url) { + return new Promise(function(resolve, reject){ + var xhr = new XMLHttpRequest(); + + xhr.open('GET', url); + xhr.onreadystatechange = handler; + xhr.responseType = 'json'; + xhr.setRequestHeader('Accept', 'application/json'); + xhr.send(); + + function handler() { + if (this.readyState === this.DONE) { + if (this.status === 200) { + resolve(this.response); + } else { + reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']')); + } + } + }; + }); + } + + getJSON('/posts.json').then(function(json) { + // on fulfillment + }, function(reason) { + // on rejection + }); + ``` + + Unlike callbacks, promises are great composable primitives. + + ```js + Promise.all([ + getJSON('/posts'), + getJSON('/comments') + ]).then(function(values){ + values[0] // => postsJSON + values[1] // => commentsJSON + + return values; + }); + ``` + + @class Promise + @param {function} resolver + Useful for tooling. + @constructor + */ + function $$es6$promise$promise$$Promise(resolver) { + this._id = $$es6$promise$promise$$counter++; + this._state = undefined; + this._result = undefined; + this._subscribers = []; + + if ($$$internal$$noop !== resolver) { + if (!$$utils$$isFunction(resolver)) { + $$es6$promise$promise$$needsResolver(); + } + + if (!(this instanceof $$es6$promise$promise$$Promise)) { + $$es6$promise$promise$$needsNew(); + } + + $$$internal$$initializePromise(this, resolver); + } + } + + $$es6$promise$promise$$Promise.all = $$promise$all$$default; + $$es6$promise$promise$$Promise.race = $$promise$race$$default; + $$es6$promise$promise$$Promise.resolve = $$promise$resolve$$default; + $$es6$promise$promise$$Promise.reject = $$promise$reject$$default; + + $$es6$promise$promise$$Promise.prototype = { + constructor: $$es6$promise$promise$$Promise, + + /** + The primary way of interacting with a promise is through its `then` method, + which registers callbacks to receive either a promise's eventual value or the + reason why the promise cannot be fulfilled. + + ```js + findUser().then(function(user){ + // user is available + }, function(reason){ + // user is unavailable, and you are given the reason why + }); + ``` + + Chaining + -------- + + The return value of `then` is itself a promise. This second, 'downstream' + promise is resolved with the return value of the first promise's fulfillment + or rejection handler, or rejected if the handler throws an exception. + + ```js + findUser().then(function (user) { + return user.name; + }, function (reason) { + return 'default name'; + }).then(function (userName) { + // If `findUser` fulfilled, `userName` will be the user's name, otherwise it + // will be `'default name'` + }); + + findUser().then(function (user) { + throw new Error('Found user, but still unhappy'); + }, function (reason) { + throw new Error('`findUser` rejected and we're unhappy'); + }).then(function (value) { + // never reached + }, function (reason) { + // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'. + // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'. + }); + ``` + If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream. + + ```js + findUser().then(function (user) { + throw new PedagogicalException('Upstream error'); + }).then(function (value) { + // never reached + }).then(function (value) { + // never reached + }, function (reason) { + // The `PedgagocialException` is propagated all the way down to here + }); + ``` + + Assimilation + ------------ + + Sometimes the value you want to propagate to a downstream promise can only be + retrieved asynchronously. This can be achieved by returning a promise in the + fulfillment or rejection handler. The downstream promise will then be pending + until the returned promise is settled. This is called *assimilation*. + + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // The user's comments are now available + }); + ``` + + If the assimliated promise rejects, then the downstream promise will also reject. + + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // If `findCommentsByAuthor` fulfills, we'll have the value here + }, function (reason) { + // If `findCommentsByAuthor` rejects, we'll have the reason here + }); + ``` + + Simple Example + -------------- + + Synchronous Example + + ```javascript + var result; + + try { + result = findResult(); + // success + } catch(reason) { + // failure + } + ``` + + Errback Example + + ```js + findResult(function(result, err){ + if (err) { + // failure + } else { + // success + } + }); + ``` + + Promise Example; + + ```javascript + findResult().then(function(result){ + // success + }, function(reason){ + // failure + }); + ``` + + Advanced Example + -------------- + + Synchronous Example + + ```javascript + var author, books; + + try { + author = findAuthor(); + books = findBooksByAuthor(author); + // success + } catch(reason) { + // failure + } + ``` + + Errback Example + + ```js + + function foundBooks(books) { + + } + + function failure(reason) { + + } + + findAuthor(function(author, err){ + if (err) { + failure(err); + // failure + } else { + try { + findBoooksByAuthor(author, function(books, err) { + if (err) { + failure(err); + } else { + try { + foundBooks(books); + } catch(reason) { + failure(reason); + } + } + }); + } catch(error) { + failure(err); + } + // success + } + }); + ``` + + Promise Example; + + ```javascript + findAuthor(). + then(findBooksByAuthor). + then(function(books){ + // found books + }).catch(function(reason){ + // something went wrong + }); + ``` + + @method then + @param {Function} onFulfilled + @param {Function} onRejected + Useful for tooling. + @return {Promise} + */ + then: function(onFulfillment, onRejection) { + var parent = this; + var state = parent._state; + + if (state === $$$internal$$FULFILLED && !onFulfillment || state === $$$internal$$REJECTED && !onRejection) { + return this; + } + + var child = new this.constructor($$$internal$$noop); + var result = parent._result; + + if (state) { + var callback = arguments[state - 1]; + $$asap$$default(function(){ + $$$internal$$invokeCallback(state, child, callback, result); + }); + } else { + $$$internal$$subscribe(parent, child, onFulfillment, onRejection); + } + + return child; + }, + + /** + `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same + as the catch block of a try/catch statement. + + ```js + function findAuthor(){ + throw new Error('couldn't find that author'); + } + + // synchronous + try { + findAuthor(); + } catch(reason) { + // something went wrong + } + + // async with promises + findAuthor().catch(function(reason){ + // something went wrong + }); + ``` + + @method catch + @param {Function} onRejection + Useful for tooling. + @return {Promise} + */ + 'catch': function(onRejection) { + return this.then(null, onRejection); + } + }; + + var $$es6$promise$polyfill$$default = function polyfill() { + var local; + + if (typeof global !== 'undefined') { + local = global; + } else if (typeof window !== 'undefined' && window.document) { + local = window; + } else { + local = self; + } + + var es6PromiseSupport = + "Promise" in local && + // Some of these methods are missing from + // Firefox/Chrome experimental implementations + "resolve" in local.Promise && + "reject" in local.Promise && + "all" in local.Promise && + "race" in local.Promise && + // Older version of the spec had a resolver object + // as the arg rather than a function + (function() { + var resolve; + new local.Promise(function(r) { resolve = r; }); + return $$utils$$isFunction(resolve); + }()); + + if (!es6PromiseSupport) { + local.Promise = $$es6$promise$promise$$default; + } + }; + + var es6$promise$umd$$ES6Promise = { + 'Promise': $$es6$promise$promise$$default, + 'polyfill': $$es6$promise$polyfill$$default + }; + + /* global define:true module:true window: true */ + if (typeof define === 'function' && define['amd']) { + define(function() { return es6$promise$umd$$ES6Promise; }); + } else if (typeof module !== 'undefined' && module['exports']) { + module['exports'] = es6$promise$umd$$ES6Promise; + } else if (typeof this !== 'undefined') { + this['ES6Promise'] = es6$promise$umd$$ES6Promise; + } +}).call(this); \ No newline at end of file From 88226ba09f04119de5e5fe483996dab5b1ca1067 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Sat, 17 Jan 2015 02:08:16 -0800 Subject: [PATCH 02/60] gather the skeins By combining start and try_start into a single promise chain, and straightening out the logic a bit, we simultaneously make the sequence of events much clearer, and eliminate several possibilities for bugs (nothing can be called out of order). --- emdosbox-launcher.js | 3 +- emdosbox-loader.js | 144 +++++++++++++++++++------------------------ 2 files changed, 64 insertions(+), 83 deletions(-) diff --git a/emdosbox-launcher.js b/emdosbox-launcher.js index 8260db5..7e3fa9f 100644 --- a/emdosbox-launcher.js +++ b/emdosbox-launcher.js @@ -70,7 +70,8 @@ window.onkeydown = keypress; var canvas = document.getElementById('canvas'); emulator = new DOSBOX(canvas).setscale(get('scale') ? parseFloat(get('scale')) : 1) .setmodule(module) - .setgame(getgameurl(loader_game)); + .setgame(getgameurl(loader_game)) + .start(); disableRightClickContextMenu(canvas); // Emscripten doesn't use the proper prefixed functions for fullscreen requests, diff --git a/emdosbox-loader.js b/emdosbox-loader.js index 44d7edb..43fa12b 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -964,7 +964,6 @@ var Module = null; (function (Promise) { function DOSBOX(canvas, module, game, precallback, callback, scale) { var js_url; - var moduledata; var requests = []; var drawloadingtimer; var file_countdown; @@ -985,13 +984,8 @@ var Module = null; return sample.sampleRate.toString(); }()); - var can_start = function () { - return !!canvas && !!module && !!game && !!scale && !has_started; - }; - this.setscale = function(_scale) { scale = _scale; - try_start(); return this; }; @@ -1007,13 +1001,11 @@ var Module = null; this.setmodule = function(_module) { module = _module; - try_start(); return this; }; this.setgame = function(_game) { game = _game; - try_start(); return this; }; @@ -1113,61 +1105,64 @@ var Module = null; return "//cors.archive.org/cors/jsmess_engine_v2/"+ js_filename; }; - var keyevent = function(e) { - if (typeof(loader_game)=='object') return; // game will start with click-to-play instead of [SPACE] char - if (e.which == 32) { - e.preventDefault(); - start(); - } - }; - var start = function() { - // Prevent loading the game multiple times. - if (loading) { + if (has_started) return false; - } - window.removeEventListener('keypress', keyevent); - canvas.removeEventListener('click', start); - loading = true; - drawloadingtimer = window.setInterval(draw_loading_status, 1000/60); - if (precallback) { - window.setTimeout(precallback, 0); - } - - LOADING_TEXT = 'Parsing config'; - var modulecfg = JSON.parse(moduledata); - - var nr = modulecfg['native_resolution']; - DOSBOX.width = nr[0] * scale; - DOSBOX.height = nr[1] * scale; - - file_countdown = 2; - - LOADING_TEXT = 'Downloading game data...'; - Promise.all([fetch_file('Metadata', - get_meta_url(game), - 'document', true), - fetch_file('Game', - game)]) - .then(function(game_data) { - Module = init_module(modulecfg, game_data[0], game_data[1]); - if (modulecfg['js_filename']) { - LOADING_TEXT = 'Launching DosBox'; - attach_script(modulecfg['js_filename']); - } else { - LOADING_TEXT = 'Invalid System Disk'; - } - }); + has_started = true; + + var k, c, modulecfg; + + // NOTE: deliberately use cors.archive.org since this will 302 rewrite to iaXXXXX.us.archive.org/XX/items/jsmess_engine_v2/...json + // and need to keep that "artificial" extra domain-ish name to avoid CORS issues with IE/Safari + var steps = fetch_file('Module Info', + '//archive.org/cors/jsmess_engine_v2/' + module + '.json', + 'text', true, true); + steps.then(function (data) { + return new Promise(function (resolve, reject) { + modulecfg = JSON.parse(data); + + var nr = modulecfg['native_resolution']; + DOSBOX.width = nr[0] * scale; + DOSBOX.height = nr[1] * scale; + + window.addEventListener('keypress', k = keyevent(resolve)); + canvas.addEventListener('click', c = resolve); + drawsplash(); + }); + }) + .then(function () { + window.removeEventListener('keypress', k); + canvas.removeEventListener('click', c); + loading = true; + drawloadingtimer = window.setInterval(draw_loading_status, 1000/60); + if (precallback) { + window.setTimeout(precallback, 0); + } + + file_countdown = 2; + + LOADING_TEXT = 'Downloading game data...'; + return Promise.all([fetch_file('Metadata', + get_meta_url(game), + 'document', true), + fetch_file('Game', + game)]); + }) + .then(function(game_data) { + Module = init_module(modulecfg, game_data[0], game_data[1]); + if (modulecfg['js_filename']) { + LOADING_TEXT = 'Launching DosBox'; + attach_script(modulecfg['js_filename']); + } else { + LOADING_TEXT = 'Invalid System Disk'; + } + }); return this; }; this.start = start; window.DOSBOXstart = start;//global hook to method (so can be invoked with a "click to play" image being clicked) - + var init_module = function(modulecfg, meta_file, game_file) { - if (moduledata == null) { - // HACK: Module data isn't ready yet. It'll call us once loaded. - return null; - } return { arguments: build_dosbox_arguments(modulecfg, meta_file.getElementsByTagName("emulator_start") .item(0) @@ -1192,6 +1187,17 @@ var Module = null; }; }; + function keyevent(resolve) { + return function (e) { + if (typeof loader_game === 'object') + return; // game will start with click-to-play instead of [SPACE] char + if (e.which == 32) { + e.preventDefault(); + resolve(); + } + }; + }; + var drawsplash = function() { var context = canvas.getContext('2d'); splashimg.onload = function(){ @@ -1211,30 +1217,6 @@ var Module = null; spinnerimg.src = '/images/spinner.png'; }; - function try_start () { - if (!can_start()) { - return; - } - LOADING_TEXT = "Fetching item metadata..."; - has_started = true; - // NOTE: deliberately use cors.archive.org since this will 302 rewrite to iaXXXXX.us.archive.org/XX/items/jsmess_engine_v2/...json - // and need to keep that "artificial" extra domain-ish name to avoid CORS issues with IE/Safari - var config = fetch_file('Module Info', - '//archive.org/cors/jsmess_engine_v2/' + module + '.json', - 'text', true, true); - config.then(function (data) { - moduledata = data; - window.addEventListener('keypress', keyevent); - canvas.addEventListener('click', start); - drawsplash(); - if (loading) { - // HACK: User clicked play before module metadata loaded, and play aborted. - // Now that metadata is ready, begin playing. - init_module(); - } - }); - } - function attach_script(js_url) { if (js_url) { var head = document.getElementsByTagName('head')[0]; @@ -1244,8 +1226,6 @@ var Module = null; head.appendChild(newScript); } } - - try_start(); } DOSBOX._readySet = false; From d308ad8d615d64ff32ed644e2be268e75750d643 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Sat, 17 Jan 2015 02:15:35 -0800 Subject: [PATCH 03/60] move that url to a getter like the rest also whitespace, because reasons --- emdosbox-loader.js | 72 +++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/emdosbox-loader.js b/emdosbox-loader.js index 43fa12b..67a2236 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -974,7 +974,7 @@ var Module = null; var has_started = false; var loading = false; var LOADING_TEXT; - + var SAMPLE_RATE = (function () { var audio_ctx = window.AudioContext || window.webkitAudioContext || false; if (!audio_ctx) { @@ -983,32 +983,32 @@ var Module = null; var sample = new audio_ctx; return sample.sampleRate.toString(); }()); - + this.setscale = function(_scale) { scale = _scale; return this; }; - + this.setprecallback = function(_precallback) { precallback = _precallback; return this; }; - + this.setcallback = function(_callback) { callback = _callback; return this; }; - + this.setmodule = function(_module) { module = _module; return this; }; - + this.setgame = function(_game) { game = _game; return this; }; - + var draw_loading_status = function() { var context = canvas.getContext('2d'); context.clearRect(0, 0, canvas.width, canvas.height); @@ -1027,7 +1027,7 @@ var Module = null; context.restore(); spinnerrot += .25; }; - + var progress_fetch_file = function(e) { if (e.lengthComputable) { e.target.progress = e.loaded / e.total; @@ -1036,7 +1036,7 @@ var Module = null; e.target.lengthComputable = e.lengthComputable; } }; - + var fetch_file = function(title, url, rt, raw, unmanaged) { return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest(); @@ -1064,12 +1064,12 @@ var Module = null; xhr.send(); }); }; - + var update_countdown = function() { file_countdown -= 1; if (file_countdown <= 0) { loading = false; - + // see archive.js for the mute/unmute button/JS if (!($.cookie && $.cookie('unmute'))){ setTimeout(function(){ @@ -1082,29 +1082,31 @@ var Module = null; } } }; - + var build_dosbox_arguments = function (config, emulator_start) { LOADING_TEXT = 'Building arguments'; return ['/dosprogram/'+ emulator_start]; }; - + 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 + var get_emulator_config_url = function (module) { + return '//archive.org/cors/jsmess_engine_v2/' + module + '.json'; + }; + var get_meta_url = function (game_path) { var path = game_path.split('/'); - // 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 return "//cors.archive.org/cors/"+ path[4] +"/"+ path[4] +"_meta.xml"; }; - + var get_js_url = function (js_filename) { - // 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 return "//cors.archive.org/cors/jsmess_engine_v2/"+ js_filename; }; - + var start = function() { if (has_started) return false; @@ -1112,10 +1114,8 @@ var Module = null; var k, c, modulecfg; - // NOTE: deliberately use cors.archive.org since this will 302 rewrite to iaXXXXX.us.archive.org/XX/items/jsmess_engine_v2/...json - // and need to keep that "artificial" extra domain-ish name to avoid CORS issues with IE/Safari var steps = fetch_file('Module Info', - '//archive.org/cors/jsmess_engine_v2/' + module + '.json', + get_emulator_config_url(module), 'text', true, true); steps.then(function (data) { return new Promise(function (resolve, reject) { @@ -1186,7 +1186,7 @@ var Module = null; } }; }; - + function keyevent(resolve) { return function (e) { if (typeof loader_game === 'object') @@ -1216,7 +1216,7 @@ var Module = null; }; spinnerimg.src = '/images/spinner.png'; }; - + function attach_script(js_url) { if (js_url) { var head = document.getElementsByTagName('head')[0]; @@ -1227,11 +1227,11 @@ var Module = null; } } } - + DOSBOX._readySet = false; - + DOSBOX._readyList = []; - + DOSBOX._runReadies = function() { if (DOSBOX._readyList) { for (var r=0; r < DOSBOX._readyList.length; r++) { @@ -1240,7 +1240,7 @@ var Module = null; DOSBOX._readyList = []; }; }; - + DOSBOX._readyCheck = function() { if (DOSBOX.running) { DOSBOX._runReadies(); @@ -1248,7 +1248,7 @@ var Module = null; DOSBOX._readySet = setTimeout(DOSBOX._readyCheck, 10); }; }; - + DOSBOX.ready = function(r) { if (DOSBOX.running) { r.call(window, []); @@ -1259,18 +1259,18 @@ var Module = null; } }; }; - + DOSBOX.setScale = function() { Module.canvas.style.width = DOSBOX.width + 'px'; Module.canvas.style.height = DOSBOX.height + 'px'; }; - + DOSBOX.fullScreenChangeHandler = function() { if (!(document.mozFullScreenElement || document.fullScreenElement)) { setTimeout(DOSBOX.setScale, 0); } }; - + DOSBOX.BFSMountZip = function BFSMount(loadedData) { var zipfs = new BrowserFS.FileSystem.ZipFS(loadedData), mfs = new BrowserFS.FileSystem.MountableFileSystem(), @@ -1287,7 +1287,7 @@ var Module = null; FS.mkdir('/dosprogram'); FS.mount(BFS, {root: '/'}, '/dosprogram'); }; - + // Helper function: Recursively copies contents from one folder to another. DOSBOX.recursiveCopy = function recursiveCopy(oldDir, newDir) { var path = BrowserFS.BFSRequire('path'), @@ -1311,7 +1311,7 @@ var Module = null; fs.writeFileSync(newFile, fs.readFileSync(oldFile)); } }; - + /** * Searches for dosbox.conf, and moves it to '/dosbox.conf' so dosbox uses it. */ @@ -1338,7 +1338,7 @@ var Module = null; }); } searchDirectory('/'); - + if (dosboxConfPath !== null) { FS.writeFile('/dosbox.conf', FS.readFile(dosboxConfPath), { encoding: 'binary' }); } From 94bb759d924d13c791a155ee866cb389144dc3c0 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Sat, 24 Jan 2015 08:18:49 -0800 Subject: [PATCH 04/60] Allow loading multiple zips, as separate drives This is keyed off of the item metadata; use dosbox_drive_d=item/path/to/file.zip to mount a zip file from an item as drive D. --- emdosbox-launcher.js | 8 -- emdosbox-loader.js | 183 +++++++++++++++++++++++++++---------------- 2 files changed, 115 insertions(+), 76 deletions(-) diff --git a/emdosbox-launcher.js b/emdosbox-launcher.js index 7e3fa9f..d987acf 100644 --- a/emdosbox-launcher.js +++ b/emdosbox-launcher.js @@ -41,15 +41,8 @@ window.onkeydown = keypress; var games; var emulator; - var module; - - function getmodule() { - module = get('module'); - module = module ? module : 'test'; - } function init() { - getmodule(); ready(); } @@ -69,7 +62,6 @@ window.onkeydown = keypress; } var canvas = document.getElementById('canvas'); emulator = new DOSBOX(canvas).setscale(get('scale') ? parseFloat(get('scale')) : 1) - .setmodule(module) .setgame(getgameurl(loader_game)) .start(); disableRightClickContextMenu(canvas); diff --git a/emdosbox-loader.js b/emdosbox-loader.js index 67a2236..774f677 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -1029,12 +1029,6 @@ var Module = null; }; var progress_fetch_file = function(e) { - if (e.lengthComputable) { - e.target.progress = e.loaded / e.total; - e.target.loaded = e.loaded; - e.target.total = e.total; - e.target.lengthComputable = e.lengthComputable; - } }; var fetch_file = function(title, url, rt, raw, unmanaged) { @@ -1048,7 +1042,7 @@ var Module = null; xhr.progress = 1.0; } resolve(raw ? xhr.response - : new Int8Array(xhr.response)); + : new Int8Array(xhr.response)); } }; xhr.onerror = reject; @@ -1083,9 +1077,21 @@ var Module = null; } }; - var build_dosbox_arguments = function (config, emulator_start) { + var build_dosbox_arguments = function (config, emulator_start, game_files) { LOADING_TEXT = 'Building arguments'; - return ['/dosprogram/'+ emulator_start]; + var args = []; + + //args.push(emulator_start); + + var len = game_files.length; + for (var i = 0; i < len; i++) { + args.push('-c', 'mount '+ game_files[i].drive +' '+ game_files[i].mountpoint); + } + + args.push('-c', 'c:'); + args.push('-c', emulator_start); + + return args; }; var get_game_name = function (game_path) { @@ -1103,6 +1109,10 @@ var Module = null; return "//cors.archive.org/cors/"+ path[4] +"/"+ path[4] +"_meta.xml"; }; + var get_zip_url = function (game_path) { + return "//cors.archive.org/cors/"+ game_path; + }; + var get_js_url = function (js_filename) { return "//cors.archive.org/cors/jsmess_engine_v2/"+ js_filename; }; @@ -1112,78 +1122,115 @@ var Module = null; return false; has_started = true; - var k, c, modulecfg; + var k, c, modulecfg, metadata; - var steps = fetch_file('Module Info', - get_emulator_config_url(module), - 'text', true, true); - steps.then(function (data) { - return new Promise(function (resolve, reject) { - modulecfg = JSON.parse(data); + var loading = fetch_file('Metadata', + get_meta_url(game), + 'document', true); + loading.then(function (data) { + metadata = data; + var module = metadata.getElementsByTagName("emulator") + .item(0) + .textContent; + return fetch_file('Module Info', + get_emulator_config_url(module), + 'text', true, true); + }) + .then(function (data) { + return new Promise(function (resolve, reject) { + modulecfg = JSON.parse(data); - var nr = modulecfg['native_resolution']; - DOSBOX.width = nr[0] * scale; - DOSBOX.height = nr[1] * scale; + var nr = modulecfg['native_resolution']; + DOSBOX.width = nr[0] * scale; + DOSBOX.height = nr[1] * scale; - window.addEventListener('keypress', k = keyevent(resolve)); - canvas.addEventListener('click', c = resolve); - drawsplash(); - }); - }) - .then(function () { - window.removeEventListener('keypress', k); - canvas.removeEventListener('click', c); - loading = true; - drawloadingtimer = window.setInterval(draw_loading_status, 1000/60); - if (precallback) { - window.setTimeout(precallback, 0); - } + // stashes these event listeners so that we can remove them after + window.addEventListener('keypress', k = keyevent(resolve)); + canvas.addEventListener('click', c = resolve); + drawsplash(); + }); + }) + .then(function () { + window.removeEventListener('keypress', k); + canvas.removeEventListener('click', c); + loading = true; + drawloadingtimer = window.setInterval(draw_loading_status, 1000/60); + if (precallback) { + window.setTimeout(precallback, 0); + } - file_countdown = 2; + function mountat (drive) { + return function (data) { + return { drive: drive, + mountpoint: "/" + drive, + data: data + }; + }; + } - LOADING_TEXT = 'Downloading game data...'; - return Promise.all([fetch_file('Metadata', - get_meta_url(game), - 'document', true), - fetch_file('Game', - game)]); - }) - .then(function(game_data) { - Module = init_module(modulecfg, game_data[0], game_data[1]); - if (modulecfg['js_filename']) { - LOADING_TEXT = 'Launching DosBox'; - attach_script(modulecfg['js_filename']); - } else { - LOADING_TEXT = 'Invalid System Disk'; - } - }); + var files = []; + if (game) { + files.push(fetch_file('Game', game).then(mountat("c"))); + } + + var len = metadata.documentElement.childNodes.length; + for (var i = 0; i < len; i++) { + var node = metadata.documentElement.childNodes[i]; + var m = node.nodeName.match(/^dosbox_drive_([a-zA-Z])$/); + if (m) { + var file = fetch_file('Game File: '+ node.textContent, + get_zip_url(node.textContent)); + file.then(mountat(m[1])); + files.append(file); + } + } + + file_countdown = files.length; + LOADING_TEXT = 'Downloading game data...'; + + return Promise.all(files); + }) + .then(function(game_data) { + Module = init_module(modulecfg, metadata, game_data); + if (modulecfg['js_filename']) { + LOADING_TEXT = 'Launching DosBox'; + attach_script(modulecfg['js_filename']); + } else { + LOADING_TEXT = 'Invalid System Disk'; + } + }); return this; }; this.start = start; window.DOSBOXstart = start;//global hook to method (so can be invoked with a "click to play" image being clicked) - var init_module = function(modulecfg, meta_file, game_file) { + var init_module = function(modulecfg, metadata, game_files) { return { arguments: build_dosbox_arguments(modulecfg, - meta_file.getElementsByTagName("emulator_start") - .item(0) - .textContent), + metadata.getElementsByTagName("emulator_start") + .item(0) + .textContent, + game_files), screenIsReadOnly: true, print: function (text) { console.log(text); }, canvas: canvas, noInitialRun: false, preInit: function () { - LOADING_TEXT = 'Loading game file into file system'; - DOSBOX.BFSMountZip(new BrowserFS.BFSRequire('buffer').Buffer(game_file)); - DOSBOX.moveConfigToRoot(); - window.clearInterval(drawloadingtimer); - if (callback) { - modulecfg.canvas = canvas; - window.setTimeout(function() { - callback(modulecfg); - }, - 0); - } - } + LOADING_TEXT = 'Loading game file(s) into file system'; + var len = game_files.length; + for (var i = 0; i < len; i++) { + DOSBOX.BFSMountZip(game_files[i].mountpoint, + new BrowserFS.BFSRequire('buffer').Buffer(game_files[i].data)); + } + DOSBOX.moveConfigToRoot(); + window.clearInterval(drawloadingtimer); + if (callback) { + modulecfg.canvas = canvas; + window.setTimeout(function() { + callback(modulecfg, metadata, game_files); + }, + 0); + } + } }; }; @@ -1271,7 +1318,7 @@ var Module = null; } }; - DOSBOX.BFSMountZip = function BFSMount(loadedData) { + DOSBOX.BFSMountZip = function BFSMount(path, loadedData) { var zipfs = new BrowserFS.FileSystem.ZipFS(loadedData), mfs = new BrowserFS.FileSystem.MountableFileSystem(), memfs = new BrowserFS.FileSystem.InMemory(); @@ -1284,8 +1331,8 @@ var Module = null; BrowserFS.initialize(memfs); // Mount the file system into Emscripten. var BFS = new BrowserFS.EmscriptenFS(); - FS.mkdir('/dosprogram'); - FS.mount(BFS, {root: '/'}, '/dosprogram'); + FS.mkdir(path); + FS.mount(BFS, {root: '/'}, path); }; // Helper function: Recursively copies contents from one folder to another. From 996671b8546b74a2dfd7971aff780cd73cfd717a Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Sat, 24 Jan 2015 10:59:40 -0800 Subject: [PATCH 05/60] show loading animation right away Also clean up the animation code a bit, and use requestAnimationFrame if it's available. --- emdosbox-loader.js | 129 ++++++++++++++++++++++++++++++--------------- 1 file changed, 86 insertions(+), 43 deletions(-) diff --git a/emdosbox-loader.js b/emdosbox-loader.js index 774f677..7e83246 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -967,13 +967,15 @@ var Module = null; var requests = []; var drawloadingtimer; var file_countdown; - var spinnerrot = 0; var splashimg = new Image(); var spinnerimg = new Image(); // TODO: Have an enum value that communicates the current state of DOSBOX, e.g. 'initializing', 'loading', 'running'. var has_started = false; var loading = false; - var LOADING_TEXT; + var splash = { loading_text: "", + spinning: true, + spinner_rotation: 0, + finished_loading: false }; var SAMPLE_RATE = (function () { var audio_ctx = window.AudioContext || window.webkitAudioContext || false; @@ -1009,25 +1011,6 @@ var Module = null; return this; }; - var draw_loading_status = function() { - var context = canvas.getContext('2d'); - context.clearRect(0, 0, canvas.width, canvas.height); - context.drawImage(splashimg, canvas.width / 2 - (splashimg.width / 2), canvas.height / 3 - (splashimg.height / 2)); - var spinnerpos = (canvas.height / 2 + splashimg.height / 2) + 16; - context.save(); - context.translate((canvas.width / 2), spinnerpos); - context.rotate(spinnerrot); - context.drawImage(spinnerimg, -(64/2), -(64/2), 64, 64); - context.restore(); - context.save(); - context.font = '18px sans-serif'; - context.fillStyle = 'Black'; - context.textAlign = 'center'; - context.fillText(LOADING_TEXT, canvas.width / 2, (canvas.height / 2) + (splashimg.height / 4)); - context.restore(); - spinnerrot += .25; - }; - var progress_fetch_file = function(e) { }; @@ -1078,7 +1061,7 @@ var Module = null; }; var build_dosbox_arguments = function (config, emulator_start, game_files) { - LOADING_TEXT = 'Building arguments'; + splash.loading_text = 'Building arguments'; var args = []; //args.push(emulator_start); @@ -1123,11 +1106,13 @@ var Module = null; has_started = true; var k, c, modulecfg, metadata; + drawsplash(); var loading = fetch_file('Metadata', get_meta_url(game), 'document', true); loading.then(function (data) { + splash.loading_text = 'Downloading game metadata...'; metadata = data; var module = metadata.getElementsByTagName("emulator") .item(0) @@ -1144,17 +1129,19 @@ var Module = null; DOSBOX.width = nr[0] * scale; DOSBOX.height = nr[1] * scale; + splash.loading_text = '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); - drawsplash(); }); }) .then(function () { window.removeEventListener('keypress', k); canvas.removeEventListener('click', c); + splash.spinning = true; loading = true; - drawloadingtimer = window.setInterval(draw_loading_status, 1000/60); if (precallback) { window.setTimeout(precallback, 0); } @@ -1186,17 +1173,17 @@ var Module = null; } file_countdown = files.length; - LOADING_TEXT = 'Downloading game data...'; + splash.loading_text = 'Downloading game data...'; return Promise.all(files); }) .then(function(game_data) { Module = init_module(modulecfg, metadata, game_data); if (modulecfg['js_filename']) { - LOADING_TEXT = 'Launching DosBox'; + splash.loading_text = 'Launching DosBox'; attach_script(modulecfg['js_filename']); } else { - LOADING_TEXT = 'Invalid System Disk'; + splash.loading_text = 'Invalid System Disk'; } }); return this; @@ -1215,14 +1202,14 @@ var Module = null; canvas: canvas, noInitialRun: false, preInit: function () { - LOADING_TEXT = 'Loading game file(s) into file system'; + splash.loading_text = 'Loading game file(s) into file system'; var len = game_files.length; for (var i = 0; i < len; i++) { DOSBOX.BFSMountZip(game_files[i].mountpoint, new BrowserFS.BFSRequire('buffer').Buffer(game_files[i].data)); } DOSBOX.moveConfigToRoot(); - window.clearInterval(drawloadingtimer); + splash.finished_loading = true; if (callback) { modulecfg.canvas = canvas; window.setTimeout(function() { @@ -1245,25 +1232,42 @@ var Module = null; }; }; - var drawsplash = function() { + var drawsplash = function () { var context = canvas.getContext('2d'); - splashimg.onload = function(){ - context.clearRect(0, 0, canvas.width, canvas.height); - context.save(); - context.drawImage(splashimg, canvas.width / 2 - (splashimg.width / 2), canvas.height / 3 - (splashimg.height / 2)); - context.font = '18px sans-serif'; - context.fillStyle = 'Black'; - context.textAlign = 'center'; - context.fillText('Click here to start', canvas.width / 2, (canvas.height / 2) + (splashimg.height / 2)); - context.textAlign = 'start'; - context.restore(); - }; - spinnerimg.onload = function() { - splashimg.src = '/images/dosbox.png';; + splashimg.onload = function (){ + draw_loading_status(0); + animLoop(draw_loading_status); }; + splashimg.src = '/images/dosbox.png'; spinnerimg.src = '/images/spinner.png'; }; + var draw_loading_status = function (deltaT) { + var context = canvas.getContext('2d'); + context.clearRect(0, 0, canvas.width, canvas.height); + context.drawImage(splashimg, canvas.width / 2 - (splashimg.width / 2), canvas.height / 3 - (splashimg.height / 2)); + + if (splash.spinning) { + var spinnerpos = (canvas.height / 2 + splashimg.height / 2) + 16; + context.save(); + context.translate((canvas.width / 2), spinnerpos); + context.rotate(splash.spinner_rotation += 2 * (2*Math.PI/1000) * deltaT); + context.drawImage(spinnerimg, -(64/2), -(64/2), 64, 64); + context.restore(); + } + + context.save(); + context.font = '18px sans-serif'; + context.fillStyle = 'Black'; + context.textAlign = 'center'; + context.fillText(splash.loading_text, canvas.width / 2, (canvas.height / 2) + (splashimg.height / 4)); + context.restore(); + + if (splash.finished_loading) + return false; + return true; + }; + function attach_script(js_url) { if (js_url) { var head = document.getElementsByTagName('head')[0]; @@ -1394,3 +1398,42 @@ var Module = null; window.DOSBOX = DOSBOX; })(typeof Promise === 'undefined' ? ES6Promise.Promise : Promise); + +// Cross browser, backward compatible solution +(function(window, Date) { + // feature testing + var raf = window.requestAnimationFrame || + window.mozRequestAnimationFrame || + window.webkitRequestAnimationFrame || + window.msRequestAnimationFrame || + window.oRequestAnimationFrame; + + window.animLoop = function (render, element) { + var running, lastFrame = +new Date; + function loop (now) { + if (running !== false) { + // fallback to setTimeout if requestAnimationFrame wasn't found + raf ? raf(loop, element) + : setTimeout(loop, 1000 / 60); + // Make sure to use a valid time, since: + // - Chrome 10 doesn't return it at all + // - setTimeout returns the actual timeout + now = now && now > 1E4 ? now : +new Date; + var deltaT = now - lastFrame; + // do not render frame when deltaT is too high + if (deltaT < 160) { + running = render(deltaT, now); + } + lastFrame = now; + } + } + loop(); + }; +})(window, Date); + +// Usage +//animLoop(function (deltaT, now) { +// // rendering code goes here +// // return false; will stop the loop +// }, +// animWrapper); From 8e64147290907090fadbcda84b8d31842fc65198 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Sat, 24 Jan 2015 11:09:21 -0800 Subject: [PATCH 06/60] wait for key press after all downloads are complete but still before launching the emulator. Technically we "launch" the emulator by adding a script tag to the document, so it may still have to download that. It'd be nice if we could include that earlier and call a method to start things up. --- emdosbox-loader.js | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/emdosbox-loader.js b/emdosbox-loader.js index 7e83246..4fb91bd 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -1105,7 +1105,7 @@ var Module = null; return false; has_started = true; - var k, c, modulecfg, metadata; + var k, c, modulecfg, metadata, game_files; drawsplash(); var loading = fetch_file('Metadata', @@ -1122,26 +1122,12 @@ var Module = null; 'text', true, true); }) .then(function (data) { - return new Promise(function (resolve, reject) { - modulecfg = JSON.parse(data); + modulecfg = JSON.parse(data); - var nr = modulecfg['native_resolution']; - DOSBOX.width = nr[0] * scale; - DOSBOX.height = nr[1] * scale; + var nr = modulecfg['native_resolution']; + DOSBOX.width = nr[0] * scale; + DOSBOX.height = nr[1] * scale; - splash.loading_text = '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); - }); - }) - .then(function () { - window.removeEventListener('keypress', k); - canvas.removeEventListener('click', c); - splash.spinning = true; - loading = true; if (precallback) { window.setTimeout(precallback, 0); } @@ -1177,8 +1163,24 @@ var Module = null; return Promise.all(files); }) - .then(function(game_data) { - Module = init_module(modulecfg, metadata, game_data); + .then(function (game_data) { + game_files = game_data; + return new Promise(function (resolve, reject) { + splash.loading_text = '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); + }); + }) + .then(function () { + splash.spinning = true; + window.removeEventListener('keypress', k); + canvas.removeEventListener('click', c); + + Module = init_module(modulecfg, metadata, game_files); + if (modulecfg['js_filename']) { splash.loading_text = 'Launching DosBox'; attach_script(modulecfg['js_filename']); From 4c3a89752c41707a3a9fa14504b973e0846a1b6a Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Sat, 24 Jan 2015 11:54:33 -0800 Subject: [PATCH 07/60] correctly handle executables inside subdirectories --- emdosbox-loader.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/emdosbox-loader.js b/emdosbox-loader.js index 4fb91bd..f2e5069 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -1071,8 +1071,12 @@ var Module = null; args.push('-c', 'mount '+ game_files[i].drive +' '+ game_files[i].mountpoint); } - args.push('-c', 'c:'); - args.push('-c', emulator_start); + 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; }; From 9df9b0bab10a15aae8cd6b9394d331a02bbce284 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Sat, 24 Jan 2015 13:13:50 -0800 Subject: [PATCH 08/60] Remove unused line --- emdosbox-loader.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/emdosbox-loader.js b/emdosbox-loader.js index f2e5069..e2045e9 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -1064,8 +1064,6 @@ var Module = null; splash.loading_text = 'Building arguments'; var args = []; - //args.push(emulator_start); - var len = game_files.length; for (var i = 0; i < len; i++) { args.push('-c', 'mount '+ game_files[i].drive +' '+ game_files[i].mountpoint); From 0ef8e8332cd5b4dd0122d888b7cb249de19afeaa Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Sun, 25 Jan 2015 06:32:15 -0800 Subject: [PATCH 09/60] show basic download status information --- emdosbox-launcher.js | 10 ++------ emdosbox-loader.js | 57 +++++++++++++++++++++++++++++++------------- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/emdosbox-launcher.js b/emdosbox-launcher.js index d987acf..e0e5407 100644 --- a/emdosbox-launcher.js +++ b/emdosbox-launcher.js @@ -62,7 +62,8 @@ window.onkeydown = keypress; } var canvas = document.getElementById('canvas'); emulator = new DOSBOX(canvas).setscale(get('scale') ? parseFloat(get('scale')) : 1) - .setgame(getgameurl(loader_game)) + .setgame(loader_game === 'NONE' ? null + : loader_game) .start(); disableRightClickContextMenu(canvas); @@ -88,13 +89,6 @@ window.onkeydown = keypress; } } - function getgameurl(game) { - // 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 - return (game === 'NONE') ? undefined - : ('//cors.archive.org/cors/'+ game); - } - /** * Disables the right click menu for the given element. */ diff --git a/emdosbox-loader.js b/emdosbox-loader.js index e2045e9..03772f1 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -1011,10 +1011,26 @@ var Module = null; return this; }; - var progress_fetch_file = function(e) { + var progress_fetch_file = function (e) { + }; var fetch_file = function(title, url, rt, raw, unmanaged) { + var table = document.getElementById("dosbox-progress-indicator"); + var row, cell; + if (!table) { + table = document.createElement('table'); + table.setAttribute('id', "dosbox-progress-indicator"); + table.style.position = 'absolute'; + table.style.top = (canvas.offsetTop + (canvas.height / 2 + splashimg.height / 2) + 16 - (64/2)) +'px'; + table.style.left = canvas.offsetLeft + (64 + 32) +'px'; + document.documentElement.appendChild(table); + } + row = table.insertRow(-1); + cell = row.insertCell(-1); + cell.textContent = '—'; + row.insertCell(-1).textContent = title; + return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); @@ -1024,11 +1040,15 @@ var Module = null; if (!unmanaged) { xhr.progress = 1.0; } + cell.textContent = '✔'; resolve(raw ? xhr.response : new Int8Array(xhr.response)); } }; - xhr.onerror = reject; + xhr.onerror = function (e) { + cell.textContent = '✘'; + reject(); + }; if (!unmanaged) { xhr.onprogress = progress_fetch_file; xhr.title = title; @@ -1091,7 +1111,7 @@ var Module = null; var get_meta_url = function (game_path) { var path = game_path.split('/'); - return "//cors.archive.org/cors/"+ path[4] +"/"+ path[4] +"_meta.xml"; + return "//cors.archive.org/cors/"+ path[0] +"/"+ path[0] +"_meta.xml"; }; var get_zip_url = function (game_path) { @@ -1110,16 +1130,17 @@ var Module = null; var k, c, modulecfg, metadata, game_files; drawsplash(); - var loading = fetch_file('Metadata', + splash.loading_text = 'Downloading game metadata...'; + var loading = fetch_file('Game Metadata', get_meta_url(game), 'document', true); loading.then(function (data) { - splash.loading_text = 'Downloading game metadata...'; metadata = data; + splash.loading_text = 'Downloading emulator metadata...'; var module = metadata.getElementsByTagName("emulator") .item(0) .textContent; - return fetch_file('Module Info', + return fetch_file('Emulator Metadata', get_emulator_config_url(module), 'text', true, true); }) @@ -1145,7 +1166,7 @@ var Module = null; var files = []; if (game) { - files.push(fetch_file('Game', game).then(mountat("c"))); + files.push(fetch_file('Game File: '+ game, get_zip_url(game)).then(mountat("c"))); } var len = metadata.documentElement.childNodes.length; @@ -1251,24 +1272,26 @@ var Module = null; context.clearRect(0, 0, canvas.width, canvas.height); context.drawImage(splashimg, canvas.width / 2 - (splashimg.width / 2), canvas.height / 3 - (splashimg.height / 2)); - if (splash.spinning) { - var spinnerpos = (canvas.height / 2 + splashimg.height / 2) + 16; - context.save(); - context.translate((canvas.width / 2), spinnerpos); - context.rotate(splash.spinner_rotation += 2 * (2*Math.PI/1000) * deltaT); - context.drawImage(spinnerimg, -(64/2), -(64/2), 64, 64); - context.restore(); - } + var spinnerpos = (canvas.height / 2 + splashimg.height / 2) + 16; + context.save(); + context.translate((64/2) + 16, spinnerpos); + context.rotate(splash.spinning ? (splash.spinner_rotation += 2 * (2*Math.PI/1000) * deltaT) + : 0); + context.drawImage(spinnerimg, -(64/2), -(64/2), 64, 64); + context.restore(); context.save(); context.font = '18px sans-serif'; context.fillStyle = 'Black'; context.textAlign = 'center'; context.fillText(splash.loading_text, canvas.width / 2, (canvas.height / 2) + (splashimg.height / 4)); + context.restore(); - if (splash.finished_loading) + if (splash.finished_loading) { + document.getElementById("dosbox-progress-indicator").style.display = 'none'; return false; + } return true; }; @@ -1281,7 +1304,7 @@ var Module = null; head.appendChild(newScript); } } - } + }; DOSBOX._readySet = false; From 09db127d957f186a8673e74770bdd68117b32d66 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Sun, 25 Jan 2015 06:39:24 -0800 Subject: [PATCH 10/60] add some error messages for download failures --- emdosbox-loader.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/emdosbox-loader.js b/emdosbox-loader.js index 03772f1..dfe0243 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -1143,6 +1143,10 @@ var Module = null; return fetch_file('Emulator Metadata', get_emulator_config_url(module), 'text', true, true); + }, + function () { + splash.loading_text = 'Failed to download metadata!'; + splash.finished = true; }) .then(function (data) { modulecfg = JSON.parse(data); @@ -1185,6 +1189,10 @@ var Module = null; splash.loading_text = 'Downloading game data...'; return Promise.all(files); + }, + function () { + splash.loading_text = 'Failed to download metadata!'; + splash.finished = true; }) .then(function (game_data) { game_files = game_data; @@ -1196,6 +1204,10 @@ var Module = null; window.addEventListener('keypress', k = keyevent(resolve)); canvas.addEventListener('click', c = resolve); }); + }, + function () { + splash.loading_text = 'Failed to download game data!'; + splash.finished = true; }) .then(function () { splash.spinning = true; @@ -1208,8 +1220,12 @@ var Module = null; splash.loading_text = 'Launching DosBox'; attach_script(modulecfg['js_filename']); } else { - splash.loading_text = 'Invalid System Disk'; + splash.loading_text = 'Non-system disk or disk error'; } + }, + function () { + splash.loading_text = 'Invalid media, track 0 bad or unusable'; + splash.finished = true; }); return this; }; From 165570103456cd080676ffa1f20f8b7e14ee30fa Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Sun, 25 Jan 2015 06:40:00 -0800 Subject: [PATCH 11/60] add local emacs settings --- .dir-locals.el | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .dir-locals.el diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..1f84612 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,5 @@ +;;; Directory Local Variables +;;; For more information see (info "(emacs) Directory Variables") + +((js-mode + (js2-basic-offset . 2))) From e3eb04894a89f3752343522ba2959054d5403be2 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Sun, 25 Jan 2015 06:41:19 -0800 Subject: [PATCH 12/60] fix property name --- emdosbox-loader.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/emdosbox-loader.js b/emdosbox-loader.js index dfe0243..e7d2efa 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -1146,7 +1146,7 @@ var Module = null; }, function () { splash.loading_text = 'Failed to download metadata!'; - splash.finished = true; + splash.finished_loading = true; }) .then(function (data) { modulecfg = JSON.parse(data); @@ -1192,7 +1192,7 @@ var Module = null; }, function () { splash.loading_text = 'Failed to download metadata!'; - splash.finished = true; + splash.finished_loading = true; }) .then(function (game_data) { game_files = game_data; @@ -1207,7 +1207,7 @@ var Module = null; }, function () { splash.loading_text = 'Failed to download game data!'; - splash.finished = true; + splash.finished_loading = true; }) .then(function () { splash.spinning = true; @@ -1225,7 +1225,7 @@ var Module = null; }, function () { splash.loading_text = 'Invalid media, track 0 bad or unusable'; - splash.finished = true; + splash.finished_loading = true; }); return this; }; From d6a77de91cb1e3e048d1b8c3da72e97d6c4d8c04 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Sun, 25 Jan 2015 06:46:32 -0800 Subject: [PATCH 13/60] don't hide download status on failure --- emdosbox-loader.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/emdosbox-loader.js b/emdosbox-loader.js index e7d2efa..d64c781 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -1146,7 +1146,7 @@ var Module = null; }, function () { splash.loading_text = 'Failed to download metadata!'; - splash.finished_loading = true; + splash.failed_loading = true; }) .then(function (data) { modulecfg = JSON.parse(data); @@ -1192,7 +1192,7 @@ var Module = null; }, function () { splash.loading_text = 'Failed to download metadata!'; - splash.finished_loading = true; + splash.failed_loading = true; }) .then(function (game_data) { game_files = game_data; @@ -1207,7 +1207,7 @@ var Module = null; }, function () { splash.loading_text = 'Failed to download game data!'; - splash.finished_loading = true; + splash.failed_loading = true; }) .then(function () { splash.spinning = true; @@ -1225,7 +1225,7 @@ var Module = null; }, function () { splash.loading_text = 'Invalid media, track 0 bad or unusable'; - splash.finished_loading = true; + splash.failed_loading = true; }); return this; }; @@ -1306,6 +1306,8 @@ var Module = null; if (splash.finished_loading) { document.getElementById("dosbox-progress-indicator").style.display = 'none'; + } + if (splash.finished_loading || splash.failed_loading) { return false; } return true; From eefef077c09fae4e5f980b89d095f4865e50a70e Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 27 Jan 2015 09:52:46 -0800 Subject: [PATCH 14/60] show 'n of m' to differentiate game files instead of their url --- emdosbox-loader.js | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/emdosbox-loader.js b/emdosbox-loader.js index d64c781..a1b7436 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -1168,21 +1168,28 @@ var Module = null; }; } - var files = []; + // first get the urls + var urls = []; if (game) { - files.push(fetch_file('Game File: '+ game, get_zip_url(game)).then(mountat("c"))); + // ugh, such a hack + urls.push({ nodeName: 'dosbox_drive_c', 'textContent': game}); + } + var len = metadata.documentElement.childNodes.length, i; + for (i = 0; i < len; i++) { + var node = metadata.documentElement.childNodes[i]; + var m = node.nodeName.match(/^dosbox_drive_[a-zA-Z]$/); + if (m) { + urls.push(node); + } } - var len = metadata.documentElement.childNodes.length; - for (var i = 0; i < len; i++) { - var node = metadata.documentElement.childNodes[i]; - var m = node.nodeName.match(/^dosbox_drive_([a-zA-Z])$/); - if (m) { - var file = fetch_file('Game File: '+ node.textContent, - get_zip_url(node.textContent)); - file.then(mountat(m[1])); - files.append(file); - } + // and a count, then fetch them in + var files = [], + len = urls.length; + for (i = 0; i < len; i++) { + var node = urls[i], + drive = node.nodeName.split('_')[2] + files.push(fetch_file('Game File ('+ (i+1) +' of '+ len +')', get_zip_url(node.textContent)).then(mountat(drive))); } file_countdown = files.length; From e84c39cc0c767705c2621de87a0afc8af1a197bf Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Wed, 28 Jan 2015 19:05:48 -0800 Subject: [PATCH 15/60] Allow changing the colors used in the splash screen --- emdosbox-loader.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/emdosbox-loader.js b/emdosbox-loader.js index 215ca81..435acc5 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -975,7 +975,9 @@ var Module = null; var splash = { loading_text: "", spinning: true, spinner_rotation: 0, - finished_loading: false }; + finished_loading: false, + colors: { foreground: 'black', + background: 'white' } }; var SAMPLE_RATE = (function () { var audio_ctx = window.AudioContext || window.webkitAudioContext || false; @@ -1011,6 +1013,11 @@ var Module = null; return this; }; + this.setSplashColors = function (colors) { + this.splash.colors = colors; + return this; + }; + var progress_fetch_file = function (e) { }; @@ -1024,6 +1031,7 @@ var Module = null; table.style.position = 'absolute'; table.style.top = (canvas.offsetTop + (canvas.height / 2 + splashimg.height / 2) + 16 - (64/2)) +'px'; table.style.left = canvas.offsetLeft + (64 + 32) +'px'; + table.style.color = 'foreground' in splash.colors ? splash.colors.foreground : 'black'; document.documentElement.appendChild(table); } row = table.insertRow(-1); @@ -1287,6 +1295,7 @@ var Module = null; }; var drawsplash = function () { + canvas.setAttribute('moz-opaque', ''); var context = canvas.getContext('2d'); splashimg.onload = function (){ draw_loading_status(0); @@ -1298,7 +1307,8 @@ var Module = null; var draw_loading_status = function (deltaT) { var context = canvas.getContext('2d'); - context.clearRect(0, 0, canvas.width, canvas.height); + context.fillStyle = "background" in splash.colors ? splash.colors.background : 'white'; + context.fillRect(0, 0, canvas.width, canvas.height); context.drawImage(splashimg, canvas.width / 2 - (splashimg.width / 2), canvas.height / 3 - (splashimg.height / 2)); var spinnerpos = (canvas.height / 2 + splashimg.height / 2) + 16; @@ -1311,7 +1321,7 @@ var Module = null; context.save(); context.font = '18px sans-serif'; - context.fillStyle = 'Black'; + context.fillStyle = "foreground" in splash.colors ? splash.colors.foreground : 'black'; context.textAlign = 'center'; context.fillText(splash.loading_text, canvas.width / 2, (canvas.height / 2) + (splashimg.height / 4)); From 6732de204afa311b6abfba88a2f520abee23d760 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 3 Feb 2015 22:19:30 -0800 Subject: [PATCH 16/60] move a bunch of random stuff into the loader It's not all in it's final form yet, but most of this will be methods called by the user of the loader. --- emdosbox-launcher.js | 79 ++++-------------------------------------- emdosbox-loader.js | 81 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 77 insertions(+), 83 deletions(-) diff --git a/emdosbox-launcher.js b/emdosbox-launcher.js index e0e5407..aad84cc 100644 --- a/emdosbox-launcher.js +++ b/emdosbox-launcher.js @@ -1,35 +1,3 @@ -var ar = new Array(33,34,35,36,37,38,39,40); - -function getfullscreenenabler() { - return canvas.webkitRequestFullScreen || canvas.mozRequestFullScreen || canvas.requestFullScreen; -} - -function getpointerlockenabler() { - return canvas.requestPointerLock || canvas.mozRequestPointerLock || canvas.webkitRequestPointerLock; -} - -function isfullscreensupported() { - return !!(getfullscreenenabler()); -} - -function gofullscreen() { - Module.requestFullScreen(1,0); -} - -function keypress(e) { - if (typeof(loader_game)=='object' && !loader_game.started) - return true; // Don't ignore certain keys yet (until game started by "click to play") - - var key = e.which; - if($.inArray(key,ar) > -1) { - e.preventDefault(); //Don't let arrow, pg up/down, home, end affect page position - return false; - } - return true; -} - -window.onkeydown = keypress; - (function() { function get(name) { if (typeof(loader_game)=='object') @@ -42,38 +10,20 @@ window.onkeydown = keypress; var games; var emulator; - function init() { - ready(); - } - function ready() { - var fullscreenbutton = document.getElementById('gofullscreen'); - if (isfullscreensupported()) { - fullscreenbutton.addEventListener('click', gofullscreen); - if ('onfullscreenchange' in document) { - document.addEventListener('fullscreenchange', DOSBOX.fullScreenChangeHandler); - } else if ('onmozfullscreenchange' in document) { - document.addEventListener('mozfullscreenchange', DOSBOX.fullScreenChangeHandler); - } else if ('onwebkitfullscreenchange' in document) { - document.addEventListener('webkitfullscreenchange', DOSBOX.fullScreenChangeHandler); - } - } else { - fullscreenbutton.disabled = true; - } var canvas = document.getElementById('canvas'); emulator = new DOSBOX(canvas).setscale(get('scale') ? parseFloat(get('scale')) : 1) .setgame(loader_game === 'NONE' ? null : loader_game) .start(); - 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(); - - if (get('autostart')) { - emulator.start(); + var fullscreenbutton = document.getElementById('gofullscreen'); + if (emulator.isfullscreensupported()) { + fullscreenbutton.addEventListener('click', function () { emulator.requestFullScreen(); }); + } else { + fullscreenbutton.disabled = true; } + // Gamepad text if (detectgamepadsupport()) { var gamepadDiv = document.getElementById('gamepadtext'); @@ -89,21 +39,6 @@ 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(); - } - }); - } - - function switchgame(e) { - emulator.setgame(getgameurl(e.target.value)); - } // Firefox will not give us Joystick data unless we register this NOP // callback. @@ -172,5 +107,5 @@ window.onkeydown = keypress; }, freq); } - window.addEventListener('load', init); + window.addEventListener('load', ready); })(); diff --git a/emdosbox-loader.js b/emdosbox-loader.js index 435acc5..d38e460 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -1229,6 +1229,15 @@ var Module = null; window.removeEventListener('keypress', k); canvas.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(); + Module = init_module(modulecfg, metadata, game_files); if (modulecfg['js_filename']) { @@ -1345,6 +1354,67 @@ var Module = null; head.appendChild(newScript); } } + + function getpointerlockenabler() { + return canvas.requestPointerLock || canvas.mozRequestPointerLock || canvas.webkitRequestPointerLock; + } + + function getfullscreenenabler() { + return canvas.webkitRequestFullScreen || canvas.mozRequestFullScreen || canvas.requestFullScreen; + } + + function isfullscreensupported() { + return !!(getfullscreenenabler()); + } + + function setupFullScreen() { + var fullScreenChangeHandler = function() { + if (!(document.mozFullScreenElement || document.fullScreenElement)) { + canvas.style.width = DOSBOX.width + 'px'; + canvas.style.height = DOSBOX.height + 'px'; + } + }; + 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 () { + Module.requestFullScreen(1, 0); + }; + + /** + * Prevents page navigation keys such as page up/page down from + * moving the page while the user is playing. + */ + function blockSomeKeys() { + var blocked_keys = [33, 34, 35, 36, 37, 38, 39, 40]; + function keypress (e) { + if (blocked_keys.indexOf(e.which) >= 0) { + 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(); + } + }); + } }; DOSBOX._readySet = false; @@ -1379,17 +1449,6 @@ var Module = null; }; }; - DOSBOX.setScale = function() { - Module.canvas.style.width = DOSBOX.width + 'px'; - Module.canvas.style.height = DOSBOX.height + 'px'; - }; - - DOSBOX.fullScreenChangeHandler = function() { - if (!(document.mozFullScreenElement || document.fullScreenElement)) { - setTimeout(DOSBOX.setScale, 0); - } - }; - DOSBOX.BFSMountZip = function BFSMount(path, loadedData) { var zipfs = new BrowserFS.FileSystem.ZipFS(loadedData), mfs = new BrowserFS.FileSystem.MountableFileSystem(), From b76a0be35f00eb24bb06d47cfe155a57421620a8 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Wed, 4 Feb 2015 00:36:21 -0800 Subject: [PATCH 17/60] Move all IA code into a different constructor --- emdosbox-launcher.js | 12 +- emdosbox-loader.js | 378 ++++++++++++++++++++++--------------------- 2 files changed, 203 insertions(+), 187 deletions(-) diff --git a/emdosbox-launcher.js b/emdosbox-launcher.js index aad84cc..ad7542e 100644 --- a/emdosbox-launcher.js +++ b/emdosbox-launcher.js @@ -1,4 +1,4 @@ -(function() { +(function () { function get(name) { if (typeof(loader_game)=='object') return loader_game[name]; //alternate case where dont have CGI args to parse... @@ -11,11 +11,11 @@ var emulator; function ready() { - var canvas = document.getElementById('canvas'); - emulator = new DOSBOX(canvas).setscale(get('scale') ? parseFloat(get('scale')) : 1) - .setgame(loader_game === 'NONE' ? null - : loader_game) - .start(); + var game = loader_game === 'NONE' ? null : loader_game, + scale = get('scale') ? parseFloat(get('scale')) : 1, + canvas = document.getElementById('canvas'); + + emulator = new IALoader(canvas, game, null, scale).start(); var fullscreenbutton = document.getElementById('gofullscreen'); if (emulator.isfullscreensupported()) { diff --git a/emdosbox-loader.js b/emdosbox-loader.js index d38e460..7dd117f 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -962,7 +962,137 @@ var Module = null; (function (Promise) { - function DOSBOX(canvas, module, game, precallback, callback, scale) { + function IALoader(canvas, game, callback, scale) { + var metadata, modulecfg, + emulator = new DOSBOX(canvas).setmodule("dosbox") + .setscale(scale) + .setLoad(loadFiles) + .setcallback(function (module) { + var nr = modulecfg['native_resolution']; + emulator.width = nr[0] * scale; + emulator.height = nr[1] * scale; + callback(module); + }); + + function loadFiles(fetch_file, splash) { + splash.loading_text = 'Downloading game metadata...'; + return new Promise(function (resolve, reject) { + var loading = fetch_file('Game Metadata', + get_meta_url(game), + 'document', true); + loading.then(function (data) { + metadata = data; + splash.loading_text = 'Downloading emulator metadata...'; + var module = metadata.getElementsByTagName("emulator") + .item(0) + .textContent; + return fetch_file('Emulator Metadata', + get_emulator_config_url(module), + 'text', true, true); + }, + function () { + splash.loading_text = 'Failed to download metadata!'; + splash.failed_loading = true; + reject(); + }) + .then(function (data) { + modulecfg = JSON.parse(data); + + // first get the urls + var urls = []; + if (game) { + // ugh, such a hack + urls.push({ nodeName: 'dosbox_drive_c', 'textContent': game}); + } + var len = metadata.documentElement.childNodes.length, i; + for (i = 0; i < len; i++) { + var node = metadata.documentElement.childNodes[i]; + var m = node.nodeName.match(/^dosbox_drive_[a-zA-Z]$/); + if (m) { + urls.push(node); + } + } + + // and a count, then fetch them in + var files = [], + len = urls.length; + for (i = 0; i < len; i++) { + var node = urls[i], + drive = node.nodeName.split('_')[2], + title = 'Game File ('+ (i+1) +' of '+ len +')', + url = get_zip_url(node.textContent); + files.push(fetch_file(title, url).then(mountat(drive))); + } + + splash.loading_text = 'Downloading game data...'; + + return Promise.all(files); + }, + function () { + splash.loading_text = 'Failed to download metadata!'; + splash.failed_loading = true; + reject(); + }) + .then(function (game_files) { + function locateAdditionalJS(filename) { + if ("file_locations" in modulecfg && filename in modulecfg.file_locations) { + return get_js_url(modulecfg.file_locations[filename]); + } + throw new Error("Don't know how to find file: "+ filename); + } + + resolve({ files: game_files, + jsFilename: get_js_url(modulecfg.js_filename), + locateAdditionalJS: locateAdditionalJS, + emulatorStart: metadata.getElementsByTagName("emulator_start") + .item(0) + .textContent + }); + }, + function () { + splash.loading_text = 'Failed to download game data!'; + splash.failed_loading = true; + reject(); + }); + }); + } + + 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 + var get_emulator_config_url = function (module) { + return '//archive.org/cors/jsmess_engine_v2/' + module + '.json'; + }; + + var get_meta_url = function (game_path) { + var path = game_path.split('/'); + return "//cors.archive.org/cors/"+ path[0] +"/"+ path[0] +"_meta.xml"; + }; + + var get_zip_url = function (game_path) { + return "//cors.archive.org/cors/"+ game_path; + }; + + var get_js_url = function (js_filename) { + return "//cors.archive.org/cors/jsmess_engine_v2/"+ js_filename; + }; + + function mountat (drive) { + return function (data) { + return { drive: drive, + mountpoint: "/" + drive, + data: data + }; + }; + } + + return emulator; + } + + function DOSBOX(canvas, module, callback, scale, loadFiles) { var js_url; var requests = []; var drawloadingtimer; @@ -993,11 +1123,6 @@ var Module = null; return this; }; - this.setprecallback = function(_precallback) { - precallback = _precallback; - return this; - }; - this.setcallback = function(_callback) { callback = _callback; return this; @@ -1008,16 +1133,68 @@ var Module = null; return this; }; - this.setgame = function(_game) { - game = _game; - return this; - }; - this.setSplashColors = function (colors) { this.splash.colors = colors; return this; }; + this.setLoad = function (loadFunc) { + loadFiles = loadFunc; + return this; + }; + + var start = function () { + if (has_started) + return false; + has_started = true; + + var k, c, game_data; + drawsplash(); + + var loading = loadFiles(fetch_file, splash); + loading.then(function (_game_data) { + game_data = _game_data; + return new Promise(function (resolve, reject) { + splash.loading_text = '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); + }); + }) + .then(function () { + file_countdown = game_data.files.length; + splash.spinning = true; + window.removeEventListener('keypress', k); + canvas.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(); + + Module = init_module(game_data.emulatorStart, game_data.files, game_data.locateAdditionalJS); + + if (game_data.jsFilename) { + splash.loading_text = 'Launching DosBox'; + attach_script(game_data.jsFilename); + } else { + splash.loading_text = 'Non-system disk or disk error'; + } + }, + function () { + splash.loading_text = 'Invalid media, track 0 bad or unusable'; + splash.failed_loading = true; + }); + return this; + }; + this.start = start; + var progress_fetch_file = function (e) { }; @@ -1088,7 +1265,7 @@ var Module = null; } }; - var build_dosbox_arguments = function (config, emulator_start, game_files) { + var build_dosbox_arguments = function (emulator_start, game_files) { splash.loading_text = 'Building arguments'; var args = []; @@ -1107,171 +1284,13 @@ var Module = null; return args; }; - 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 - var get_emulator_config_url = function (module) { - return '//archive.org/cors/jsmess_engine_v2/' + module + '.json'; - }; - - var get_meta_url = function (game_path) { - var path = game_path.split('/'); - return "//cors.archive.org/cors/"+ path[0] +"/"+ path[0] +"_meta.xml"; - }; - - var get_zip_url = function (game_path) { - return "//cors.archive.org/cors/"+ game_path; - }; - - var get_js_url = function (js_filename) { - return "//cors.archive.org/cors/jsmess_engine_v2/"+ js_filename; - }; - - var start = function() { - if (has_started) - return false; - has_started = true; - - var k, c, modulecfg, metadata, game_files; - drawsplash(); - - splash.loading_text = 'Downloading game metadata...'; - var loading = fetch_file('Game Metadata', - get_meta_url(game), - 'document', true); - loading.then(function (data) { - metadata = data; - splash.loading_text = 'Downloading emulator metadata...'; - var module = metadata.getElementsByTagName("emulator") - .item(0) - .textContent; - return fetch_file('Emulator Metadata', - get_emulator_config_url(module), - 'text', true, true); - }, - function () { - splash.loading_text = 'Failed to download metadata!'; - splash.failed_loading = true; - }) - .then(function (data) { - modulecfg = JSON.parse(data); - - var nr = modulecfg['native_resolution']; - DOSBOX.width = nr[0] * scale; - DOSBOX.height = nr[1] * scale; - - if (precallback) { - window.setTimeout(precallback, 0); - } - - function mountat (drive) { - return function (data) { - return { drive: drive, - mountpoint: "/" + drive, - data: data - }; - }; - } - - // first get the urls - var urls = []; - if (game) { - // ugh, such a hack - urls.push({ nodeName: 'dosbox_drive_c', 'textContent': game}); - } - var len = metadata.documentElement.childNodes.length, i; - for (i = 0; i < len; i++) { - var node = metadata.documentElement.childNodes[i]; - var m = node.nodeName.match(/^dosbox_drive_[a-zA-Z]$/); - if (m) { - urls.push(node); - } - } - - // and a count, then fetch them in - var files = [], - len = urls.length; - for (i = 0; i < len; i++) { - var node = urls[i], - drive = node.nodeName.split('_')[2] - files.push(fetch_file('Game File ('+ (i+1) +' of '+ len +')', get_zip_url(node.textContent)).then(mountat(drive))); - } - - file_countdown = files.length; - splash.loading_text = 'Downloading game data...'; - - return Promise.all(files); - }, - function () { - splash.loading_text = 'Failed to download metadata!'; - splash.failed_loading = true; - }) - .then(function (game_data) { - game_files = game_data; - return new Promise(function (resolve, reject) { - splash.loading_text = '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); - }); - }, - function () { - splash.loading_text = 'Failed to download game data!'; - splash.failed_loading = true; - }) - .then(function () { - splash.spinning = true; - window.removeEventListener('keypress', k); - canvas.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(); - - Module = init_module(modulecfg, metadata, game_files); - - if (modulecfg['js_filename']) { - splash.loading_text = 'Launching DosBox'; - attach_script(modulecfg['js_filename']); - } else { - splash.loading_text = 'Non-system disk or disk error'; - } - }, - function () { - splash.loading_text = 'Invalid media, track 0 bad or unusable'; - splash.failed_loading = true; - }); - return this; - }; - this.start = start; - window.DOSBOXstart = start;//global hook to method (so can be invoked with a "click to play" image being clicked) - - var init_module = function(modulecfg, metadata, game_files) { - return { arguments: build_dosbox_arguments(modulecfg, - metadata.getElementsByTagName("emulator_start") - .item(0) - .textContent, - game_files), + var init_module = function(emulator_start, game_files, locateAdditionalJS) { + return { arguments: build_dosbox_arguments(emulator_start, game_files), screenIsReadOnly: true, print: function (text) { console.log(text); }, canvas: canvas, noInitialRun: false, - locateFile: function (file) { - if ("file_locations" in modulecfg && file in modulecfg.file_locations) { - return get_js_url(modulecfg.file_locations[file]); - } - throw new Error("Don't know how to find file: "+ file); - }, + locateFile: locateAdditionalJS, preInit: function () { splash.loading_text = 'Loading game file(s) into file system'; var len = game_files.length; @@ -1282,11 +1301,7 @@ var Module = null; DOSBOX.moveConfigToRoot(); splash.finished_loading = true; if (callback) { - modulecfg.canvas = canvas; - window.setTimeout(function() { - callback(modulecfg, metadata, game_files); - }, - 0); + window.setTimeout(function() { callback(this); }, 0); } } }; @@ -1350,7 +1365,7 @@ var Module = null; var head = document.getElementsByTagName('head')[0]; var newScript = document.createElement('script'); newScript.type = 'text/javascript'; - newScript.src = get_js_url(js_url); + newScript.src = js_url; head.appendChild(newScript); } } @@ -1363,9 +1378,9 @@ var Module = null; return canvas.webkitRequestFullScreen || canvas.mozRequestFullScreen || canvas.requestFullScreen; } - function isfullscreensupported() { + this.isfullscreensupported = function () { return !!(getfullscreenenabler()); - } + }; function setupFullScreen() { var fullScreenChangeHandler = function() { @@ -1523,6 +1538,7 @@ var Module = null; } }; + window.IALoader = IALoader; window.DOSBOX = DOSBOX; })(typeof Promise === 'undefined' ? ES6Promise.Promise : Promise); From a64a797dae6c03a8ca3990be101dc254d060660d Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Wed, 4 Feb 2015 03:13:56 -0800 Subject: [PATCH 18/60] set up resolution/scale/aspect ratio api --- emdosbox-loader.js | 123 ++++++++++++++++----------------------------- 1 file changed, 43 insertions(+), 80 deletions(-) diff --git a/emdosbox-loader.js b/emdosbox-loader.js index 7dd117f..eb9d82a 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -963,16 +963,10 @@ var Module = null; (function (Promise) { function IALoader(canvas, game, callback, scale) { - var metadata, modulecfg, - emulator = new DOSBOX(canvas).setmodule("dosbox") - .setscale(scale) + var metadata, module, modulecfg, + emulator = new DOSBOX(canvas).setscale(scale) .setLoad(loadFiles) - .setcallback(function (module) { - var nr = modulecfg['native_resolution']; - emulator.width = nr[0] * scale; - emulator.height = nr[1] * scale; - callback(module); - }); + .setcallback(callback); function loadFiles(fetch_file, splash) { splash.loading_text = 'Downloading game metadata...'; @@ -983,10 +977,10 @@ var Module = null; loading.then(function (data) { metadata = data; splash.loading_text = 'Downloading emulator metadata...'; - var module = metadata.getElementsByTagName("emulator") - .item(0) - .textContent; - return fetch_file('Emulator Metadata', + module = metadata.getElementsByTagName("emulator") + .item(0) + .textContent; + return fetch_file('DOSBOX Metadata', get_emulator_config_url(module), 'text', true, true); }, @@ -1041,12 +1035,17 @@ var Module = null; throw new Error("Don't know how to find file: "+ filename); } + var nr = modulecfg['native_resolution']; resolve({ files: game_files, + emulatorType: module, jsFilename: get_js_url(modulecfg.js_filename), locateAdditionalJS: locateAdditionalJS, emulatorStart: metadata.getElementsByTagName("emulator_start") .item(0) - .textContent + .textContent, + nativeResolution: { width: nr[0], + height: nr[1] }, + aspectRatio: nr[0] / nr[1] }); }, function () { @@ -1092,11 +1091,10 @@ var Module = null; return emulator; } - function DOSBOX(canvas, module, callback, scale, loadFiles) { + function DOSBOX(canvas, callback, loadFiles) { var js_url; var requests = []; var drawloadingtimer; - var file_countdown; var splashimg = new Image(); var spinnerimg = new Image(); // TODO: Have an enum value that communicates the current state of DOSBOX, e.g. 'initializing', 'loading', 'running'. @@ -1118,21 +1116,28 @@ var Module = null; return sample.sampleRate.toString(); }()); - this.setscale = function(_scale) { + var css_resolution, scale, aspectRatio; + + this.setScale = function(_scale) { scale = _scale; return this; }; + this.setCSSResolution = function(_resolution) { + css_resolution = _resolution; + return this; + }; + + this.setAspectRatio = function(_aspectRatio) { + aspectRatio = _aspectRatio; + return this; + }; + this.setcallback = function(_callback) { callback = _callback; return this; }; - this.setmodule = function(_module) { - module = _module; - return this; - }; - this.setSplashColors = function (colors) { this.splash.colors = colors; return this; @@ -1164,7 +1169,6 @@ var Module = null; }); }) .then(function () { - file_countdown = game_data.files.length; splash.spinning = true; window.removeEventListener('keypress', k); canvas.removeEventListener('click', c); @@ -1173,6 +1177,10 @@ var Module = null; blockSomeKeys(); setupFullScreen(); disableRightClickContextMenu(canvas); + resizeCanvas(canvas, + scale = game_data.scale || scale, + css_resolution = game_data.nativeResolution || css_resolution, + aspectRatio = game_data.aspectRatio || aspectRatio); // Emscripten doesn't use the proper prefixed functions for fullscreen requests, // so let's map the prefixed versions to the correct function. @@ -1247,24 +1255,6 @@ var Module = null; }); }; - var update_countdown = function() { - file_countdown -= 1; - if (file_countdown <= 0) { - loading = false; - - // see archive.js for the mute/unmute button/JS - if (!($.cookie && $.cookie('unmute'))){ - setTimeout(function(){ - // someone moved it from 1st to 2nd! - if (DOSBOX && typeof(DOSBOX.sdl_pauseaudio)!='undefined') - DOSBOX.sdl_pauseaudio(1); - else if (typeof _SDL_PauseAudio !== "undefined") - _SDL_PauseAudio(1); - }, 3000); - } - } - }; - var build_dosbox_arguments = function (emulator_start, game_files) { splash.loading_text = 'Building arguments'; var args = []; @@ -1296,7 +1286,7 @@ var Module = null; var len = game_files.length; for (var i = 0; i < len; i++) { DOSBOX.BFSMountZip(game_files[i].mountpoint, - new BrowserFS.BFSRequire('buffer').Buffer(game_files[i].data)); + new BrowserFS.BFSRequire('buffer').Buffer(game_files[i].data)); } DOSBOX.moveConfigToRoot(); splash.finished_loading = true; @@ -1318,13 +1308,18 @@ var Module = null; }; }; + var resizeCanvas = function (canvas, scale, resolution, aspectRatio) { + canvas.style.width = resolution.css_width * scale +'px'; + canvas.style.height = resolution.css_height * scale +'px'; + }; + var drawsplash = function () { canvas.setAttribute('moz-opaque', ''); var context = canvas.getContext('2d'); - splashimg.onload = function (){ - draw_loading_status(0); - animLoop(draw_loading_status); - }; + splashimg.onload = function () { + draw_loading_status(0); + animLoop(draw_loading_status); + }; splashimg.src = '/images/dosbox.png'; spinnerimg.src = '/images/spinner.png'; }; @@ -1383,10 +1378,10 @@ var Module = null; }; function setupFullScreen() { + var self = this; var fullScreenChangeHandler = function() { if (!(document.mozFullScreenElement || document.fullScreenElement)) { - canvas.style.width = DOSBOX.width + 'px'; - canvas.style.height = DOSBOX.height + 'px'; + resizeCanvas(canvas, scale, css_resolution, aspectRatio); } }; if ('onfullscreenchange' in document) { @@ -1432,38 +1427,6 @@ var Module = null; } }; - DOSBOX._readySet = false; - - DOSBOX._readyList = []; - - DOSBOX._runReadies = function() { - if (DOSBOX._readyList) { - for (var r=0; r < DOSBOX._readyList.length; r++) { - DOSBOX._readyList[r].call(window, []); - }; - DOSBOX._readyList = []; - }; - }; - - DOSBOX._readyCheck = function() { - if (DOSBOX.running) { - DOSBOX._runReadies(); - } else { - DOSBOX._readySet = setTimeout(DOSBOX._readyCheck, 10); - }; - }; - - DOSBOX.ready = function(r) { - if (DOSBOX.running) { - r.call(window, []); - } else { - DOSBOX._readyList.push(function() { canvas.style.width = DOSBOX.width + 'px'; canvas.style.height = DOSBOX.height + 'px'; } ); - if (!(DOSBOX._readySet)) { - DOSBOX._readyCheck(); - } - }; - }; - DOSBOX.BFSMountZip = function BFSMount(path, loadedData) { var zipfs = new BrowserFS.FileSystem.ZipFS(loadedData), mfs = new BrowserFS.FileSystem.MountableFileSystem(), From 767d04119fba9417404e7894ad6cbd1b2b7a4931 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Wed, 4 Feb 2015 03:30:31 -0800 Subject: [PATCH 19/60] rename to Emulator, so that we can recombine it with the jsmess loader --- emdosbox-loader.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/emdosbox-loader.js b/emdosbox-loader.js index eb9d82a..2488c62 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -964,9 +964,9 @@ var Module = null; (function (Promise) { function IALoader(canvas, game, callback, scale) { var metadata, module, modulecfg, - emulator = new DOSBOX(canvas).setscale(scale) - .setLoad(loadFiles) - .setcallback(callback); + emulator = new Emulator(canvas).setscale(scale) + .setLoad(loadFiles) + .setcallback(callback); function loadFiles(fetch_file, splash) { splash.loading_text = 'Downloading game metadata...'; @@ -980,7 +980,7 @@ var Module = null; module = metadata.getElementsByTagName("emulator") .item(0) .textContent; - return fetch_file('DOSBOX Metadata', + return fetch_file('Emulator Metadata', get_emulator_config_url(module), 'text', true, true); }, @@ -1091,13 +1091,13 @@ var Module = null; return emulator; } - function DOSBOX(canvas, callback, loadFiles) { + function Emulator(canvas, callback, loadFiles) { var js_url; var requests = []; var drawloadingtimer; var splashimg = new Image(); var spinnerimg = new Image(); - // TODO: Have an enum value that communicates the current state of DOSBOX, e.g. 'initializing', 'loading', 'running'. + // 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 splash = { loading_text: "", @@ -1285,10 +1285,10 @@ var Module = null; splash.loading_text = 'Loading game file(s) into file system'; var len = game_files.length; for (var i = 0; i < len; i++) { - DOSBOX.BFSMountZip(game_files[i].mountpoint, + Emulator.BFSMountZip(game_files[i].mountpoint, new BrowserFS.BFSRequire('buffer').Buffer(game_files[i].data)); } - DOSBOX.moveConfigToRoot(); + Emulator.moveConfigToRoot(); splash.finished_loading = true; if (callback) { window.setTimeout(function() { callback(this); }, 0); @@ -1427,7 +1427,7 @@ var Module = null; } }; - DOSBOX.BFSMountZip = function BFSMount(path, loadedData) { + Emulator.BFSMountZip = function BFSMount(path, loadedData) { var zipfs = new BrowserFS.FileSystem.ZipFS(loadedData), mfs = new BrowserFS.FileSystem.MountableFileSystem(), memfs = new BrowserFS.FileSystem.InMemory(); @@ -1445,7 +1445,7 @@ var Module = null; }; // Helper function: Recursively copies contents from one folder to another. - DOSBOX.recursiveCopy = function recursiveCopy(oldDir, newDir) { + Emulator.recursiveCopy = function recursiveCopy(oldDir, newDir) { var path = BrowserFS.BFSRequire('path'), fs = BrowserFS.BFSRequire('fs'); copyDirectory(oldDir, newDir); @@ -1471,7 +1471,7 @@ var Module = null; /** * Searches for dosbox.conf, and moves it to '/dosbox.conf' so dosbox uses it. */ - DOSBOX.moveConfigToRoot = function moveConfigToRoot() { + Emulator.moveConfigToRoot = function moveConfigToRoot() { if (typeof FS !== 'undefined') { var dosboxConfPath = null; // Recursively search for dosbox.conf. @@ -1502,7 +1502,7 @@ var Module = null; }; window.IALoader = IALoader; - window.DOSBOX = DOSBOX; + window.Emulator = Emulator; })(typeof Promise === 'undefined' ? ES6Promise.Promise : Promise); // Cross browser, backward compatible solution From d616d4c055482f2e7e8d2459f51ef24c2116d6bc Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Thu, 5 Feb 2015 22:43:33 -0800 Subject: [PATCH 20/60] add a mute method --- emdosbox-loader.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/emdosbox-loader.js b/emdosbox-loader.js index 2488c62..a2b0cb1 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -1116,6 +1116,18 @@ var Module = null; return sample.sampleRate.toString(); }()); + var SDL_PauseAudio; + this.mute = function (state) { + 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; + }; + var css_resolution, scale, aspectRatio; this.setScale = function(_scale) { From 66d87856bcdc612d0b4de3b255a9832523e9fa35 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Fri, 6 Feb 2015 01:15:24 -0800 Subject: [PATCH 21/60] call the method using the right name --- emdosbox-loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emdosbox-loader.js b/emdosbox-loader.js index a2b0cb1..8478823 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -964,7 +964,7 @@ var Module = null; (function (Promise) { function IALoader(canvas, game, callback, scale) { var metadata, module, modulecfg, - emulator = new Emulator(canvas).setscale(scale) + emulator = new Emulator(canvas).setScale(scale) .setLoad(loadFiles) .setcallback(callback); From 960c6adef5e8f3014b33eb091ae091685951c047 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Fri, 6 Feb 2015 01:17:11 -0800 Subject: [PATCH 22/60] use less of a hack when dealing with the 'game' argument --- emdosbox-loader.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/emdosbox-loader.js b/emdosbox-loader.js index 8478823..083965d 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -994,10 +994,6 @@ var Module = null; // first get the urls var urls = []; - if (game) { - // ugh, such a hack - urls.push({ nodeName: 'dosbox_drive_c', 'textContent': game}); - } var len = metadata.documentElement.childNodes.length, i; for (i = 0; i < len; i++) { var node = metadata.documentElement.childNodes[i]; @@ -1013,13 +1009,19 @@ var Module = null; for (i = 0; i < len; i++) { var node = urls[i], drive = node.nodeName.split('_')[2], - title = 'Game File ('+ (i+1) +' of '+ len +')', + title = 'Game File ('+ (i+1) +' of '+ (game ? len+1 : len) +')', url = get_zip_url(node.textContent); files.push(fetch_file(title, url).then(mountat(drive))); } - splash.loading_text = 'Downloading game data...'; + if (game) { + var drive = 'c', + title = 'Game File ('+ (i+1) +' of '+ (game ? len+1 : len) +')', + url = get_zip_url(game); + files.push(fetch_file(title, url).then(mountat(drive))); + } + splash.loading_text = 'Downloading game data...'; return Promise.all(files); }, function () { From 9f7c543924a1bffe2d9fc34ee724d66ef1b0bbec Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Mon, 9 Feb 2015 15:42:57 -0800 Subject: [PATCH 23/60] new config functions This simplifies the work that the user needs to do, especially for downloading game files and unpacking them. --- emdosbox-loader.js | 281 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 212 insertions(+), 69 deletions(-) diff --git a/emdosbox-loader.js b/emdosbox-loader.js index 083965d..2061b0a 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -967,7 +967,7 @@ var Module = null; emulator = new Emulator(canvas).setScale(scale) .setLoad(loadFiles) .setcallback(callback); - + var cfgr; function loadFiles(fetch_file, splash) { splash.loading_text = 'Downloading game metadata...'; return new Promise(function (resolve, reject) { @@ -980,6 +980,10 @@ var Module = null; module = metadata.getElementsByTagName("emulator") .item(0) .textContent; + if (module.indexOf("dosbox") === 0) + cfgr = DosBoxLoader; + else + throw new Error("Unknown module type "+ module +"; cannot configure the emulator."); return fetch_file('Emulator Metadata', get_emulator_config_url(module), 'text', true, true); @@ -1011,14 +1015,14 @@ var Module = null; drive = node.nodeName.split('_')[2], title = 'Game File ('+ (i+1) +' of '+ (game ? len+1 : len) +')', url = get_zip_url(node.textContent); - files.push(fetch_file(title, url).then(mountat(drive))); + files.push(cfgr.mountZip(drive, cfgr.fetchFile(title, url))); } if (game) { var drive = 'c', title = 'Game File ('+ (i+1) +' of '+ (game ? len+1 : len) +')', url = get_zip_url(game); - files.push(fetch_file(title, url).then(mountat(drive))); + files.push(cfgr.mountZip(drive, cfgr.fetchFile(title, url))); } splash.loading_text = 'Downloading game data...'; @@ -1038,17 +1042,15 @@ var Module = null; } var nr = modulecfg['native_resolution']; - resolve({ files: game_files, - emulatorType: module, - jsFilename: get_js_url(modulecfg.js_filename), - locateAdditionalJS: locateAdditionalJS, - emulatorStart: metadata.getElementsByTagName("emulator_start") - .item(0) - .textContent, - nativeResolution: { width: nr[0], - height: nr[1] }, - aspectRatio: nr[0] / nr[1] - }); + + resolve(cfgr.apply(null, extend([cfgr.emulatorJS(get_js_url(modulecfg.js_filename)), + cfgr.locateAdditionalEmulatorJS(locateAdditionalJS), + cfgr.nativeResolution(nr[0], nr[1]), + cfgr.aspectRatio(nr[0] / nr[1]), + cfgr.startExe(metadata.getElementsByTagName("emulator_start") + .item(0) + .textContent)], + game_files))); }, function () { splash.loading_text = 'Failed to download game data!'; @@ -1093,6 +1095,60 @@ var Module = null; return emulator; } + function DosBoxLoader() { + return Array.prototype.reduce.call(arguments, extend); + } + + DosBoxLoader.canvas = function (id) { + var elem = id instanceof Element ? id : document.getElementById(id); + return { canvas: elem }; + }; + + DosBoxLoader.emulatorJS = function (url) { + return { emulatorJS: url }; + }; + + DosBoxLoader.locateAdditionalEmulatorJS = function (func) { + return { locateAdditionalJS: func }; + }; + + DosBoxLoader.startExe = function (path) { + return { emulatorStart: path }; + }; + + DosBoxLoader.nativeResolution = function (width, height) { + if (typeof width !== 'number' || typeof height !== 'number') + throw new Error("Width and height must be numbers"); + return { width: Math.floor(width), height: Math.floor(height) }; + }; + + DosBoxLoader.aspectRatio = function (ratio) { + if (typeof ratio !== 'number') + throw new Error("Aspect ratio must be a number"); + return { aspectRatio: ratio }; + }; + + DosBoxLoader.mountZip = function (drive, file) { + return { files: [{ drive: drive, + mountpoint: "/" + drive, + file: file + }] }; + }; + + DosBoxLoader.mountFile = function (filename, file) { + return { files: [{ mountpoint: filename, + file: file + }] }; + }; + + DosBoxLoader.fetchFile = function (title, url) { + return { title: title, url: url }; + }; + + DosBoxLoader.localFile = function (title, data) { + return { title: title, data: data }; + }; + function Emulator(canvas, callback, loadFiles) { var js_url; var requests = []; @@ -1173,6 +1229,43 @@ var Module = null; var loading = loadFiles(fetch_file, splash); loading.then(function (_game_data) { game_data = _game_data; + game_data.fs = new BrowserFS.FileSystem.MountableFileSystem(); + game_data.mounts = []; + 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); + } + + function mountat(drive) { + return function (data) { + drive = drive.toLowerCase(); + var mountpoint = '/'+ drive; + game_data.mounts.push({ drive: drive, mountpoint: mountpoint }); + game_data.fs.mount(mountpoint, BFSOpenZip(new Buffer(data))); + }; + } + + function saveat(filename) { + return function (data) { + game_data.fs.writeFileSync(filename, new Buffer(data)); + }; + } + + return Promise.all(game_data.files.map(function (f) { + if (f && f.file) + if (f.drive) { + return fetch(f.file).then(mountat(f.drive)); + } else if (f.filename) { + return fetch(f.file).then(saveat(f.filename)); + } + return null; + })); + }) + .then(function (game_files) { return new Promise(function (resolve, reject) { splash.loading_text = 'Press any key to continue...'; splash.spinning = false; @@ -1181,6 +1274,10 @@ var Module = null; window.addEventListener('keypress', k = keyevent(resolve)); canvas.addEventListener('click', c = resolve); }); + }, + function () { + splash.loading_text = 'Failed to download game data!'; + splash.failed_loading = true; }) .then(function () { splash.spinning = true; @@ -1200,11 +1297,12 @@ var Module = null; // so let's map the prefixed versions to the correct function. canvas.requestPointerLock = getpointerlockenabler(); - Module = init_module(game_data.emulatorStart, game_data.files, game_data.locateAdditionalJS); + moveConfigToRoot(game_data.fs); + Module = init_module(game_data.emulatorStart, game_data.mounts, game_data.fs, game_data.locateAdditionalJS); - if (game_data.jsFilename) { + if (game_data.emulatorJS) { splash.loading_text = 'Launching DosBox'; - attach_script(game_data.jsFilename); + attach_script(game_data.emulatorJS); } else { splash.loading_text = 'Non-system disk or disk error'; } @@ -1269,13 +1367,13 @@ var Module = null; }); }; - var build_dosbox_arguments = function (emulator_start, game_files) { + var build_dosbox_arguments = function (emulator_start, mounts) { splash.loading_text = 'Building arguments'; var args = []; - var len = game_files.length; + var len = mounts.length; for (var i = 0; i < len; i++) { - args.push('-c', 'mount '+ game_files[i].drive +' '+ game_files[i].mountpoint); + args.push('-c', 'mount '+ mounts[i].drive +' /emulator'+ mounts[i].mountpoint); } var path = emulator_start.split(/\\|\//); // I have LTS already @@ -1288,8 +1386,8 @@ var Module = null; return args; }; - var init_module = function(emulator_start, game_files, locateAdditionalJS) { - return { arguments: build_dosbox_arguments(emulator_start, game_files), + var init_module = function(emulator_start, mounts, fs, locateAdditionalJS) { + return { arguments: build_dosbox_arguments(emulator_start, mounts), screenIsReadOnly: true, print: function (text) { console.log(text); }, canvas: canvas, @@ -1297,12 +1395,12 @@ var Module = null; locateFile: locateAdditionalJS, preInit: function () { splash.loading_text = 'Loading game file(s) into file system'; - var len = game_files.length; - for (var i = 0; i < len; i++) { - Emulator.BFSMountZip(game_files[i].mountpoint, - new BrowserFS.BFSRequire('buffer').Buffer(game_files[i].data)); - } - Emulator.moveConfigToRoot(); + // Re-initialize BFS to just use the writable in-memory storage. + BrowserFS.initialize(fs); + var BFS = new BrowserFS.EmscriptenFS(); + // Mount the file system into Emscripten. + FS.mkdir('/emulator'); + FS.mount(BFS, {root: '/'}, '/emulator'); splash.finished_loading = true; if (callback) { window.setTimeout(function() { callback(this); }, 0); @@ -1323,8 +1421,10 @@ var Module = null; }; var resizeCanvas = function (canvas, scale, resolution, aspectRatio) { - canvas.style.width = resolution.css_width * scale +'px'; - canvas.style.height = resolution.css_height * scale +'px'; + if (scale && resolution) { + canvas.style.width = resolution.css_width * scale +'px'; + canvas.style.height = resolution.css_height * scale +'px'; + } }; var drawsplash = function () { @@ -1441,27 +1541,41 @@ var Module = null; } }; - Emulator.BFSMountZip = function BFSMount(path, loadedData) { + function BFSOpenZip(loadedData) { var zipfs = new BrowserFS.FileSystem.ZipFS(loadedData), mfs = new BrowserFS.FileSystem.MountableFileSystem(), memfs = new BrowserFS.FileSystem.InMemory(); mfs.mount('/zip', zipfs); mfs.mount('/mem', memfs); - BrowserFS.initialize(mfs); // Copy the read-only zip file contents to a writable in-memory storage. - this.recursiveCopy('/zip', '/mem'); - // Re-initialize BFS to just use the writable in-memory storage. - BrowserFS.initialize(memfs); - // Mount the file system into Emscripten. - var BFS = new BrowserFS.EmscriptenFS(); - FS.mkdir(path); - FS.mount(BFS, {root: '/'}, path); + recursiveCopy(mfs, '/zip', '/mem'); + return memfs; }; + // 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; } + }; + // Helper function: Recursively copies contents from one folder to another. - Emulator.recursiveCopy = function recursiveCopy(oldDir, newDir) { - var path = BrowserFS.BFSRequire('path'), - fs = BrowserFS.BFSRequire('fs'); + function recursiveCopy(fs, oldDir, newDir) { + var path = BrowserFS.BFSRequire('path'); copyDirectory(oldDir, newDir); function copyDirectory(oldDir, newDir) { if (!fs.existsSync(newDir)) { @@ -1478,44 +1592,73 @@ var Module = null; }); } function copyFile(oldFile, newFile) { - fs.writeFileSync(newFile, fs.readFileSync(oldFile)); + fs.writeFileSync(newFile, + fs.readFileSync(oldFile, null, flag_r), + null, flag_w, 0x1a4); } }; /** * Searches for dosbox.conf, and moves it to '/dosbox.conf' so dosbox uses it. */ - Emulator.moveConfigToRoot = function moveConfigToRoot() { - if (typeof FS !== 'undefined') { - var dosboxConfPath = null; - // Recursively search for dosbox.conf. - function searchDirectory(dirPath) { - FS.readdir(dirPath).forEach(function(item) { - // 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.stat(itemPath); - if (FS.isDir(itemStat.mode)) { - searchDirectory(itemPath); - } else if (item === 'dosbox.conf') { - dosboxConfPath = itemPath; - } - }); - } - searchDirectory('/'); + function moveConfigToRoot(fs) { + var dosboxConfPath = null; + // Recursively search for dosbox.conf. + function searchDirectory(dirPath) { + fs.readdirSync(dirPath).forEach(function(item) { + // 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; + } + }); + } - if (dosboxConfPath !== null) { - FS.writeFile('/dosbox.conf', FS.readFile(dosboxConfPath), { encoding: 'binary' }); - } + 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(a[k], b[k]); + }); + return a; + } + return b; + } + window.IALoader = IALoader; + window.DosBoxLoader = DosBoxLoader; window.Emulator = Emulator; })(typeof Promise === 'undefined' ? ES6Promise.Promise : Promise); From 991d5465e87a4860b7d0fb247250eba3593e27bf Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Mon, 9 Feb 2015 21:23:08 -0800 Subject: [PATCH 24/60] fix the positioning of the progress indicators This covers the case where we measured the wrong height for the splash image originally, when there's a slight delay in loading it. --- emdosbox-loader.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/emdosbox-loader.js b/emdosbox-loader.js index 2061b0a..adbc170 100644 --- a/emdosbox-loader.js +++ b/emdosbox-loader.js @@ -1460,6 +1460,9 @@ var Module = null; context.restore(); + var table = document.getElementById("dosbox-progress-indicator"); + table.style.top = (canvas.offsetTop + (canvas.height / 2 + splashimg.height / 2) + 16 - (64/2)) +'px'; + if (splash.finished_loading) { document.getElementById("dosbox-progress-indicator").style.display = 'none'; } From a3a5ce3d0c167e0eefbdd59f34f7b5b922587901 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Thu, 26 Feb 2015 11:38:23 -0800 Subject: [PATCH 25/60] merge the jsmess and emdosbox loaders The same code now handles both, and the config api is specialized for them as well. --- jsmess-launcher.js | 168 --------- jsmess-loader.js | 394 --------------------- emdosbox-launcher.js => launcher.js | 16 +- emdosbox-loader.js => loader.js | 514 +++++++++++++++++++--------- 4 files changed, 369 insertions(+), 723 deletions(-) delete mode 100644 jsmess-launcher.js delete mode 100644 jsmess-loader.js rename emdosbox-launcher.js => launcher.js (87%) rename emdosbox-loader.js => loader.js (79%) diff --git a/jsmess-launcher.js b/jsmess-launcher.js deleted file mode 100644 index cf44985..0000000 --- a/jsmess-launcher.js +++ /dev/null @@ -1,168 +0,0 @@ -var ar = new Array(33,34,35,36,37,38,39,40); - -function getfullscreenenabler() { - return canvas.webkitRequestFullScreen || canvas.mozRequestFullScreen || canvas.requestFullScreen; -} - -function isfullscreensupported() { - return !!(getfullscreenenabler()); -} - -function gofullscreen() { - Module.requestFullScreen(1,0); -} - -function keypress(e) { - if (typeof(loader_game)=='object' && !loader_game.started) - return true; // Don't ignore certain keys yet (until game started by "click to play") - - var key = e.which; - if($.inArray(key,ar) > -1) { - e.preventDefault(); //Don't let arrow, pg up/down, home, end affect page position - return false; - } - return true; -} - -window.onkeydown = keypress; - -(function() { - function get(name) { - if(typeof(loader_game)=='object') - return loader_game[name]; //alternate case where dont have CGI args to parse... - if(name=(new RegExp('[?&]'+encodeURIComponent(name)+'=([^&]*)')).exec(location.search)) { - return decodeURIComponent(name[1]); - } - } - - var games; - var mess; - var module; - - function getmodule() { - module = get('module'); - module = module ? module : 'test'; - } - - function init() { - getmodule(); - ready(); - } - - function ready() { - var fullscreenbutton = document.getElementById('gofullscreen') - if (isfullscreensupported()) { - fullscreenbutton.addEventListener('click', gofullscreen); - if ('onfullscreenchange' in document) { - document.addEventListener('fullscreenchange', JSMESS.fullScreenChangeHandler); - } else if ('onmozfullscreenchange' in document) { - document.addEventListener('mozfullscreenchange', JSMESS.fullScreenChangeHandler); - } else if ('onwebkitfullscreenchange' in document) { - document.addEventListener('webkitfullscreenchange', JSMESS.fullScreenChangeHandler); - } - } else { - fullscreenbutton.disabled = true; - } - var canvas = document.getElementById('canvas'); - mess = new JSMESS(canvas) - .setscale(get('scale') ? parseFloat(get('scale')) : 1) - .setmodule(module) - setgame(loader_game); - if (get('autostart')) { - mess.start(); - } - // Gamepad text - if (detectgamepadsupport()) { - var gamepadDiv = document.getElementById('gamepadtext'); - gamepadDiv.innerHTML = "No gamepads detected. Press a button on a gamepad to use it."; - listenforgamepads(function(gamepads, newgamepad) { - var s = (gamepads.length === 1 ? '' : 's'); - gamepadDiv.innerHTML = gamepads.length + ' gamepad'+s+' detected. If the game does not ' + - 'respond to your gamepad'+s+', refresh the browser and try again.'; - if (mess.hasStarted) { - gamepadDiv.innerHTML += "
Restart MESS to use new gamepads."; - } - }); - } - } - - function setgame(game) { - game = (game == 'NONE') ? undefined : game; - // 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 - mess.setgame(game ? '//cors.archive.org/cors/'+ game : undefined); - } - - function switchgame(e) { - setgame(e.target.value); - } - - // Firefox will not give us Joystick data unless we register this NOP - // callback. - // https://bugzilla.mozilla.org/show_bug.cgi?id=936104 - addEventListener("gamepadconnected", function() {}); - var getgamepads = navigator.getGamepads || navigator.webkitGamepads || - navigator.mozGamepads || navigator.gamepads || navigator.webkitGetGamepads; - /** - * Does the current browser support the Gamepad API? - * Returns a boolean. - */ - function detectgamepadsupport() { - return typeof getgamepads === 'function'; - } - // The timer that listens for gamepads, in case we ever want to stop it. - var gamepadlistener; - /** - * Listens for new gamepads, and triggers the callback when it detects a - * change. - * The callback is passed an array of active gamepads. - */ - function listenforgamepads(cb, freq) { - // NOP if the browser doesn't support gamepads. - if (!detectgamepadsupport()) return; - // Map from gamepad id to gamepad information. - var prevgamepads = {}; - // DEFAULT: Check gamepads every second. - if (typeof freq === 'undefined') freq = 1000; - gamepadlistener = setInterval(function() { - // Browsers get cranky when you don't apply this on the navigator object. - var gamepads = getgamepads.apply(navigator); - var currentgamepads = {}; - var i; - var hasChanged = false; - for (i = 0; i < gamepads.length; i++) { - var gamepad = gamepads[i]; - if (gamepad != null) { - currentgamepads[gamepad.id] = gamepad; - if (!prevgamepads.hasOwnProperty(gamepad.id)) { - // Gamepad has been added. - hasChanged = true; - } - } - } - - // Has a gamepad been removed? - if (!hasChanged) { - for (var gamepadid in prevgamepads) { - if (!currentgamepads.hasOwnProperty(gamepadid)) { - hasChanged = true; - } - } - } - - prevgamepads = currentgamepads; - - if (hasChanged) { - // Actual gamepads, filtered from gamepads. Chrome puts empty items into - // its gamepadlist. - var actualgamepads = []; - for (i = 0; i < gamepads.length; i++) { - if (gamepads[i] != null) actualgamepads.push(gamepads[i]); - } - cb(actualgamepads); - } - }, freq); - } - - window.addEventListener('load', init); -})(); \ No newline at end of file diff --git a/jsmess-loader.js b/jsmess-loader.js deleted file mode 100644 index 93211e6..0000000 --- a/jsmess-loader.js +++ /dev/null @@ -1,394 +0,0 @@ -var Module = null; - -function JSMESS(canvas, module, game, precallback, callback, scale) { - var js_data; - var moduledata; - var requests = []; - var drawloadingtimer; - var file_countdown; - var spinnerrot = 0; - var splashimg = new Image(); - var spinnerimg = new Image(); - var has_started = false; - var loading = false; - var LOADING_TEXT; - - 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 can_start = function () { - return !!canvas && !!module && !!game && !!scale && !has_started - }; - - this.setscale = function(_scale) { - scale = _scale; - try_start(); - return this; - } - - this.setprecallback = function(_precallback) { - precallback = _precallback; - return this; - } - - this.setcallback = function(_callback) { - callback = _callback; - return this; - } - - this.setmodule = function(_module) { - module = _module; - try_start(); - return this; - } - - this.setgame = function(_game) { - game = _game; - try_start(); - return this; - } - - var draw_loading_status = function() { - var context = canvas.getContext('2d'); - context.clearRect(0, 0, canvas.width, canvas.height); - context.drawImage(splashimg, canvas.width / 2 - (splashimg.width / 2), canvas.height / 3 - (splashimg.height / 2)); - var spinnerpos = (canvas.height / 2 + splashimg.height / 2) + 16; - context.save(); - context.translate((canvas.width / 2), spinnerpos); - context.rotate(spinnerrot); - context.drawImage(spinnerimg, -(64/2), -(64/2), 64, 64); - context.restore(); - context.save(); - context.font = '18px sans-serif'; - context.fillStyle = 'Black'; - context.textAlign = 'center'; - context.fillText(LOADING_TEXT, canvas.width / 2, (canvas.height / 2) + (splashimg.height / 4)); - context.restore(); - spinnerrot += .25; - }; - - var progress_fetch_file = function(e) { - if (e.lengthComputable) { - e.target.progress = e.loaded / e.total; - e.target.loaded = e.loaded; - e.target.total = e.total; - e.target.lengthComputable = e.lengthComputable; - } - }; - - var fetch_file = function(title, url, cb, rt, raw, unmanaged) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', url, true); - xhr.responseType = rt ? rt : 'arraybuffer'; - xhr.onload = function(e) { - if (xhr.status != 200) { - return; - } - if (!unmanaged) { - xhr.progress = 1.0; - } - var ints = raw ? xhr.response : new Int8Array(xhr.response); - cb(ints); - }; - if (!unmanaged) { - xhr.onprogress = progress_fetch_file; - xhr.title = title; - xhr.progress = 0; - xhr.total = 0; - xhr.loaded = 0; - xhr.lengthComputable = false; - requests.push(xhr); - } - xhr.send(); - }; - - var update_countdown = function() { - file_countdown -= 1 - if (file_countdown <= 0) { - loading = false; - var headID = document.getElementsByTagName('head')[0]; - var newScript = document.createElement('script'); - newScript.type = 'text/javascript'; - newScript.text = js_data; - headID.appendChild(newScript); - - // see archive.js for the mute/unmute button/JS - if (!($.cookie && $.cookie('unmute'))){ - setTimeout(function(){ - // someone moved it from 1st to 2nd! - if (JSMESS && typeof(JSMESS.sdl_pauseaudio)!='undefined') - JSMESS.sdl_pauseaudio(1); - else if (_SDL_PauseAudio) - _SDL_PauseAudio(1); - - }, 3000); - } - } - }; - - var build_mess_arguments = function (config) { - LOADING_TEXT = 'Building arguments'; - var nr = config['native_resolution']; - // see archive.js for the mute/unmute button/JS - var muted = (!(typeof($.cookie)!='undefined' && $.cookie('unmute'))); - - var args = [ - config['driver'], - '-verbose', - '-rompath','.', - '-window', - '-resolution', nr[0]+'x' + nr[1], - '-nokeepaspect' - ]; - - if (config.autoboot) { - args.push('-autoboot_command'); - } - - if (muted){ - args.push('-sound', 'none'); - } else if (SAMPLE_RATE) { - args.push('-samplerate', SAMPLE_RATE); - } - - if (game) { - args.push('-' + config['peripherals'][0], game.replace(/\//g,'_')) - } - - if (config['extra_args']) { - args = args.concat(config['extra_args']) - } - - return args - }; - - var build_mame_arguments = function (config) { - LOADING_TEXT = 'Building arguments'; - var nr = config['native_resolution']; - // see archive.js for the mute/unmute button/JS - var muted = (!(typeof($.cookie)!='undefined' && $.cookie('unmute'))); - - var args = [ - config['driver'], - '-verbose', - '-rompath','.', - '-window', - '-resolution', nr[0]+'x' + nr[1], - '-nokeepaspect' - ]; - - if (muted){ - args.push('-sound', 'none'); - } else if (SAMPLE_RATE) { - args.push('-samplerate', SAMPLE_RATE); - } - - if (config['extra_args']) { - args = args.concat(config['extra_args']) - } - - return args - }; - - get_game_name = function (game_path) { - return game_path.split('/').pop(); - }; - - var init_module = function() { - LOADING_TEXT = 'Parsing config'; - var modulecfg = JSON.parse(moduledata); - - var game_file = null; - var keymap = null; - var bios_filenames = modulecfg['bios_filenames']; - var bios_files = {}; - - var nr = modulecfg['native_resolution']; - - JSMESS.width = nr[0] * scale; - JSMESS.height = nr[1] * scale; - - var use_mame = parseInt(modulecfg['arcade'], 10); - var arguments; - - if (use_mame) { - arguments = build_mame_arguments(modulecfg); - } else { - arguments = build_mess_arguments(modulecfg); - } - - Module = { - arguments: arguments, - screenIsReadOnly: true, - print: (function() { - return function(text) { - console.log(text); - }; - })(), - canvas: canvas, - noInitialRun: false, - preInit: function() { - LOADING_TEXT = 'Loading binary files into file system'; - // Load the downloaded binary files into the filesystem. - for (var bios_fname in bios_files) { - if (bios_files.hasOwnProperty(bios_fname)) { - Module['FS_createDataFile']('/', bios_fname, bios_files[bios_fname], true, true); - } - } - if (game && !use_mame) { - LOADING_TEXT = 'Loading game file into file system'; - Module['FS_createDataFile']('/', game.replace(/\//g,'_'), game_file, true, true); - } - Module['FS_createFolder']('/', 'cfg', true, true); - Module['FS_createDataFile']('/cfg', modulecfg['driver'] + '.cfg', keymap, true, true); - window.clearInterval(drawloadingtimer); - if (callback) { - modulecfg.canvas = canvas; - window.setTimeout(function() {callback(modulecfg)}, 0); - } - } - }; - - bios_filenames = bios_filenames.filter(String); - file_countdown = bios_filenames.length + (game ? 1 : 0) + 2 - - // Fetch the BIOS and the game we want to run. - LOADING_TEXT = 'Fetching BIOS and Game'; - if (!use_mame) { - for (var i=0; i < bios_filenames.length; i++) { - var fname = bios_filenames[i]; - // 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 - fetch_file('Bios', '//cors.archive.org/cors/jsmess_bios_v2/' + fname, function(data) { bios_files[fname] = data; update_countdown(); }); - } - } else { - fetch_file('Bios', game, function (data) { bios_files[get_game_name(game)] = data; update_countdown(); }); - } - - if (game && !use_mame) { - fetch_file('Game', game, function(data) { game_file = data; update_countdown(); }); - } - - // 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 - fetch_file('Keymap', '//cors.archive.org/cors/jsmess_config_v2/' + modulecfg['driver'] + '.cfg', function(data) { keymap = data; update_countdown(); }, 'text', true, true); - fetch_file('Javascript', '//cors.archive.org/cors/jsmess_engine_v2/' + modulecfg['js_filename'], function(data) { js_data = data; update_countdown(); }, 'text', true); - - }; - - var keyevent = function(e) { - if (typeof(loader_game)=='object') return; // game will start with click-to-play instead of [SPACE] char - if (e.which == 32) { - e.preventDefault(); - start(); - } - } - - var start = function() { - window.removeEventListener('keypress', keyevent); - canvas.removeEventListener('click', start); - loading = true; - drawloadingtimer = window.setInterval(draw_loading_status, 1000/60); - if (precallback) { - window.setTimeout(function() {precallback()}, 0); - } - init_module(); - return this; - } - this.start = start; - window.JSMESSstart = start;//global hook to method (so can be invoked with a "click to play" image being clicked) - - var drawsplash = function() { - var context = canvas.getContext('2d'); - splashimg.onload = function(){ - context.clearRect(0, 0, canvas.width, canvas.height); - context.save(); - context.drawImage(splashimg, canvas.width / 2 - (splashimg.width / 2), canvas.height / 3 - (splashimg.height / 2)); - context.font = '18px sans-serif'; - context.fillStyle = 'Black'; - context.textAlign = 'center'; - context.fillText('Click here to start', canvas.width / 2, (canvas.height / 2) + (splashimg.height / 2)); - context.textAlign = 'start'; - context.restore(); - }; - spinnerimg.onload = function() { - var use_mame = parseInt(JSON.parse(moduledata).arcade, 10); - var src; - if (use_mame) { - src = '/images/mame.png'; - } else { - src = '/images/mess.png'; - } - splashimg.src = src; - } - spinnerimg.src = '/images/spinner.png'; - } - - var configLoaded = function (data) { - moduledata = data; - window.addEventListener('keypress', keyevent); - canvas.addEventListener('click', start); - drawsplash(); - }; - - function try_start () { - if (!can_start()) { - return; - } - has_started = true; - // NOTE: deliberately use cors.archive.org since this will 302 rewrite to iaXXXXX.us.archive.org/XX/items/jsmess_engine_v2/...json - // and need to keep that "artificial" extra domain-ish name to avoid CORS issues with IE/Safari - fetch_file('ModuleInfo', '//cors.archive.org/cors/jsmess_engine_v2/' + module + '.json', configLoaded, 'text', true, true); - } - - try_start(); -} - -JSMESS._readySet = false; - -JSMESS._readyList = []; - -JSMESS._runReadies = function() { - if (JSMESS._readyList) { - for (var r=0; r < JSMESS._readyList.length; r++) { - JSMESS._readyList[r].call(window, []); - }; - JSMESS._readyList = []; - }; -}; - -JSMESS._readyCheck = function() { - if (JSMESS.running) { - JSMESS._runReadies(); - } else { - JSMESS._readySet = setTimeout(JSMESS._readyCheck, 10); - }; -}; - -JSMESS.ready = function(r) { - if (JSMESS.running) { - r.call(window, []); - } else { - JSMESS._readyList.push(function() { canvas.style.width = JSMESS.width + 'px'; canvas.style.height = JSMESS.height + 'px'; } ); - if (!(JSMESS._readySet)) { - JSMESS._readyCheck(); - } - }; -} - -JSMESS.setScale = function() { - Module.canvas.style.width = JSMESS.width + 'px'; - Module.canvas.style.height = JSMESS.height + 'px'; -}; - -JSMESS.fullScreenChangeHandler = function() { - if (!(document.mozFullScreenElement || document.fullScreenElement)) { - setTimeout(JSMESS.setScale, 0); - } -} diff --git a/emdosbox-launcher.js b/launcher.js similarity index 87% rename from emdosbox-launcher.js rename to launcher.js index ad7542e..fb3e153 100644 --- a/emdosbox-launcher.js +++ b/launcher.js @@ -13,15 +13,19 @@ function ready() { var game = loader_game === 'NONE' ? null : loader_game, scale = get('scale') ? parseFloat(get('scale')) : 1, - canvas = document.getElementById('canvas'); + canvas = document.getElementById('canvas'), + module = get('module'); - emulator = new IALoader(canvas, game, null, scale).start(); + emulator = new IALoader(canvas, game, null, scale, + (module.indexOf('dosbox') == 0 ? '/images/dosbox.png' : '/images/mame.png')).start(); var fullscreenbutton = document.getElementById('gofullscreen'); - if (emulator.isfullscreensupported()) { - fullscreenbutton.addEventListener('click', function () { emulator.requestFullScreen(); }); - } else { - fullscreenbutton.disabled = true; + if (fullscreenbutton) { + if (emulator.isfullscreensupported()) { + fullscreenbutton.addEventListener('click', function () { emulator.requestFullScreen(); }); + } else { + fullscreenbutton.disabled = true; + } } // Gamepad text diff --git a/emdosbox-loader.js b/loader.js similarity index 79% rename from emdosbox-loader.js rename to loader.js index adbc170..5d2a10e 100644 --- a/emdosbox-loader.js +++ b/loader.js @@ -962,9 +962,19 @@ var Module = null; (function (Promise) { - function IALoader(canvas, game, callback, scale) { - var metadata, module, modulecfg, + function IALoader(canvas, game, callback, scale, splashimg) { + 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, module, modulecfg, config_args, emulator = new Emulator(canvas).setScale(scale) + .setSplashImage(splashimg) .setLoad(loadFiles) .setcallback(callback); var cfgr; @@ -980,10 +990,6 @@ var Module = null; module = metadata.getElementsByTagName("emulator") .item(0) .textContent; - if (module.indexOf("dosbox") === 0) - cfgr = DosBoxLoader; - else - throw new Error("Unknown module type "+ module +"; cannot configure the emulator."); return fetch_file('Emulator Metadata', get_emulator_config_url(module), 'text', true, true); @@ -991,75 +997,157 @@ var Module = null; function () { splash.loading_text = 'Failed to download metadata!'; splash.failed_loading = true; - reject(); + reject(1); }) .then(function (data) { modulecfg = JSON.parse(data); + var mame = 'arcade' in modulecfg && parseInt(modulecfg['arcade'], 10); + var get_files; - // first get the urls - var urls = []; - var len = metadata.documentElement.childNodes.length, i; - for (i = 0; i < len; i++) { - var node = metadata.documentElement.childNodes[i]; - var m = node.nodeName.match(/^dosbox_drive_[a-zA-Z]$/); - if (m) { - urls.push(node); + if (module && module.indexOf("dosbox") === 0) { + cfgr = DosBoxLoader; + get_files = get_dosbox_files; + } + else if (module) { + if (mame) { + cfgr = JSMAMELoader; + get_files = get_mame_files; + } else { + cfgr = JSMESSLoader; + get_files = get_mess_files; + } + } + else { + throw new Error("Unknown module type "+ module +"; cannot configure the emulator."); + } + + var nr = modulecfg['native_resolution']; + config_args = [cfgr.emulatorJS(get_js_url(modulecfg.js_filename)), + cfgr.locateAdditionalEmulatorJS(locateAdditionalJS), + cfgr.nativeResolution(nr[0], nr[1]), + cfgr.aspectRatio(nr[0] / nr[1]), + cfgr.sampleRate(SAMPLE_RATE), + cfgr.muted(!($.cookie && $.cookie('unmute')))]; + + if (module && module.indexOf("dosbox") === 0) { + config_args.push(cfgr.startExe(metadata.getElementsByTagName("emulator_start") + .item(0) + .textContent)); + } else if (module) { + if (mame) { + config_args.push(cfgr.driver(modulecfg.driver), + cfgr.extraArgs(modulecfg.extra_args)); + if (modulecfg.peripherals && modulecfg.peripherals[0]) { + config_args.push(cfgr.peripheral(modulecfg.peripherals[0], game)); + } + } else { + config_args.push(cfgr.driver(modulecfg.driver), + cfgr.extraArgs(modulecfg.extra_args)); } } - // and a count, then fetch them in - var files = [], - len = urls.length; - for (i = 0; i < len; i++) { - var node = urls[i], - drive = node.nodeName.split('_')[2], - title = 'Game File ('+ (i+1) +' of '+ (game ? len+1 : len) +')', - url = get_zip_url(node.textContent); - files.push(cfgr.mountZip(drive, cfgr.fetchFile(title, url))); - } - - if (game) { - var drive = 'c', - title = 'Game File ('+ (i+1) +' of '+ (game ? len+1 : len) +')', - url = get_zip_url(game); - files.push(cfgr.mountZip(drive, cfgr.fetchFile(title, url))); - } - splash.loading_text = 'Downloading game data...'; - return Promise.all(files); + return Promise.all(get_files(cfgr, metadata, modulecfg)); }, function () { splash.loading_text = 'Failed to download metadata!'; splash.failed_loading = true; - reject(); + reject(2); }) .then(function (game_files) { - function locateAdditionalJS(filename) { - if ("file_locations" in modulecfg && filename in modulecfg.file_locations) { - return get_js_url(modulecfg.file_locations[filename]); - } - throw new Error("Don't know how to find file: "+ filename); - } - - var nr = modulecfg['native_resolution']; - - resolve(cfgr.apply(null, extend([cfgr.emulatorJS(get_js_url(modulecfg.js_filename)), - cfgr.locateAdditionalEmulatorJS(locateAdditionalJS), - cfgr.nativeResolution(nr[0], nr[1]), - cfgr.aspectRatio(nr[0] / nr[1]), - cfgr.startExe(metadata.getElementsByTagName("emulator_start") - .item(0) - .textContent)], - game_files))); + resolve(cfgr.apply(null, extend(config_args, game_files))); }, function () { splash.loading_text = 'Failed to download game data!'; splash.failed_loading = true; - reject(); + reject(3); }); }); } + function locateAdditionalJS(filename) { + if ("file_locations" in modulecfg && filename in modulecfg.file_locations) { + return get_js_url(modulecfg.file_locations[filename]); + } + throw new Error("Don't know how to find file: "+ filename); + } + + function get_dosbox_files(cfgr, emulator, modulecfg) { + // first get the urls + var urls = [], files = []; + var len = metadata.documentElement.childNodes.length, i; + for (i = 0; i < len; i++) { + var node = metadata.documentElement.childNodes[i]; + var m = node.nodeName.match(/^dosbox_drive_[a-zA-Z]$/); + if (m) { + urls.push(node); + } + } + + // and a count, then fetch them in + var len = urls.length; + for (i = 0; i < len; i++) { + var node = urls[i], + drive = node.nodeName.split('_')[2], + title = 'Game File ('+ (i+1) +' of '+ (game ? len+1 : len) +')', + url = get_zip_url(node.textContent); + files.push(cfgr.mountZip(drive, cfgr.fetchFile(title, url))); + } + + if (game) { + var drive = 'c', + title = 'Game File ('+ (i+1) +' of '+ (game ? len+1 : len) +')', + url = get_zip_url(game); + files.push(cfgr.mountZip(drive, cfgr.fetchFile(title, url))); + } + + return files; + } + + function get_mess_files(cfgr, metadata, modulecfg) { + 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_js_url(fname)))); + } + }); + files.push(cfgr.mountFile('/'+ get_game_name(game), + cfgr.fetchFile("Game File", + get_zip_url(game)))); + files.push(cfgr.mountFile('/'+ modulecfg['driver'] + '.cfg', + cfgr.fetchOptionalFile("CFG File", + get_zip_url(get_item_name(game) +'/'+ modulecfg['driver'] + '.cfg')))); + return files; + } + + function get_mame_files(cfgr, metadata, modulecfg) { + 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_js_url(fname)))); + } + }); + files.push(cfgr.mountFile('/'+ get_game_name(game), + cfgr.fetchFile("Game File", + get_zip_url(game)))); + files.push(cfgr.mountFile('/'+ modulecfg['driver'] + '.cfg', + cfgr.fetchOptionalFile("CFG File", + get_zip_url(get_item_name(game) +'/'+ modulecfg['driver'] + '.cfg')))); + 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(); }; @@ -1095,66 +1183,190 @@ var Module = null; return emulator; } - function DosBoxLoader() { + function BaseLoader() { return Array.prototype.reduce.call(arguments, extend); } - DosBoxLoader.canvas = function (id) { + BaseLoader.canvas = function (id) { var elem = id instanceof Element ? id : document.getElementById(id); return { canvas: elem }; }; - DosBoxLoader.emulatorJS = function (url) { + BaseLoader.emulatorJS = function (url) { return { emulatorJS: url }; }; - DosBoxLoader.locateAdditionalEmulatorJS = function (func) { + BaseLoader.locateAdditionalEmulatorJS = function (func) { return { locateAdditionalJS: func }; }; - DosBoxLoader.startExe = function (path) { - return { emulatorStart: path }; - }; - - DosBoxLoader.nativeResolution = function (width, height) { + BaseLoader.nativeResolution = function (width, height) { if (typeof width !== 'number' || typeof height !== 'number') throw new Error("Width and height must be numbers"); return { width: Math.floor(width), height: Math.floor(height) }; }; - DosBoxLoader.aspectRatio = function (ratio) { + BaseLoader.aspectRatio = function (ratio) { if (typeof ratio !== 'number') throw new Error("Aspect ratio must be a number"); return { aspectRatio: ratio }; }; - DosBoxLoader.mountZip = function (drive, file) { + 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 }] }; }; - DosBoxLoader.mountFile = function (filename, file) { + BaseLoader.mountFile = function (filename, file) { return { files: [{ mountpoint: filename, file: file }] }; }; - DosBoxLoader.fetchFile = function (title, url) { + BaseLoader.fetchFile = function (title, url) { return { title: title, url: url }; }; - DosBoxLoader.localFile = function (title, data) { + BaseLoader.fetchOptionalFile = function (title, url) { + return { title: title, url: url, optional: true }; + }; + + BaseLoader.localFile = function (title, data) { return { title: title, data: data }; }; + function DosBoxLoader() { + var config = Array.prototype.reduce.call(arguments, extend); + config.emulator_arguments = build_dosbox_arguments(config.emulatorStart, config.files); + return config; + } + DosBoxLoader.__proto__ = BaseLoader; + + DosBoxLoader.startExe = function (path) { + return { emulatorStart: path }; + }; + + function JSMESSLoader() { + var config = Array.prototype.reduce.call(arguments, extend); + config.emulator_arguments = build_mess_arguments(config.muted, config.mess_driver, + [config.width, config.height], config.sample_rate, + config.peripheral, config.extra_mess_args); + return config; + } + JSMESSLoader.__proto__ = BaseLoader; + + JSMESSLoader.driver = function (driver) { + return { mess_driver: driver }; + }; + + JSMESSLoader.peripheral = function (peripheral, game) { + return { peripheral: [peripheral, game] }; + }; + + JSMESSLoader.extraArgs = function (args) { + return { extra_mess_args: args }; + }; + + function JSMAMELoader() { + var config = Array.prototype.reduce.call(arguments, extend); + config.emulator_arguments = build_mame_arguments(config.muted, config.mess_driver, + [config.width, config.height], config.sample_rate, + config.extra_mess_args); + return config; + } + JSMAMELoader.__proto__ = BaseLoader; + + JSMAMELoader.driver = function (driver) { + return { mess_driver: driver }; + }; + + JSMAMELoader.extraArgs = function (args) { + return { extra_mess_args: args }; + }; + + var build_mess_arguments = function (muted, game, driver, native_resolution, sample_rate, peripheral, extra_args) { + var args = [driver, + '-verbose', + '-rompath', 'emulator', + '-window', + '-resolution', native_resolution.join('x'), + '-nokeepaspect']; + + if (muted) { + args.push('-sound', 'none'); + } else if (sample_rate) { + args.push('-samplerate', sample_rate); + } + + if (game) { + args.push('-' + peripheral[0], peripheral[1].replace(/\//g,'_')); + } + + if (extra_args) { + args = args.concat(extra_args); + } + + return args; + }; + + var build_mame_arguments = function (muted, driver, native_resolution, sample_rate, extra_args) { + var args = [driver, + '-verbose', + '-rompath', 'emulator', + '-window', + '-resolution', native_resolution.join('x'), + '-nokeepaspect']; + + if (muted) { + args.push('-sound', 'none'); + } else if (sample_rate) { + args.push('-samplerate', sample_rate); + } + + if (extra_args) { + args = args.concat(extra_args); + } + + return args; + }; + + var build_dosbox_arguments = function (emulator_start, files) { + var args = []; + + var len = files.length; + for (var i = 0; i < len; i++) { + if ('mountpoint' in files[i]) { + args.push('-c', 'mount '+ files[i].drive +' /emulator'+ files[i].mountpoint); + } + } + + 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; + }; + function Emulator(canvas, callback, loadFiles) { var js_url; var requests = []; var drawloadingtimer; var splashimg = new Image(); var spinnerimg = new Image(); + spinnerimg.src = '/images/spinner.png'; // 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; @@ -1165,15 +1377,6 @@ var Module = null; colors: { foreground: 'black', background: 'white' } }; - 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 SDL_PauseAudio; this.mute = function (state) { try { @@ -1187,12 +1390,27 @@ var Module = null; }; 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/jsmess will set it to, but it avoids + // the case where the size was left at the default 300x150 + if (!canvas.hasAttribute("width")) { + canvas.width = canvas.style.width; + canvas.height = canvas.style.height; + } this.setScale = function(_scale) { scale = _scale; return this; }; + this.setSplashImage = function(_splashimg) { + if (_splashimg) { + splashimg.src = _splashimg; + } + return this; + }; + this.setCSSResolution = function(_resolution) { css_resolution = _resolution; return this; @@ -1230,28 +1448,30 @@ var Module = null; loading.then(function (_game_data) { game_data = _game_data; game_data.fs = new BrowserFS.FileSystem.MountableFileSystem(); - game_data.mounts = []; 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); + return fetch_file(file.title, file.url, null, null, file.optional); } function mountat(drive) { return function (data) { - drive = drive.toLowerCase(); - var mountpoint = '/'+ drive; - game_data.mounts.push({ drive: drive, mountpoint: mountpoint }); - game_data.fs.mount(mountpoint, BFSOpenZip(new Buffer(data))); + if (data !== null) { + drive = drive.toLowerCase(); + var mountpoint = '/'+ drive; + game_data.fs.mount(mountpoint, BFSOpenZip(new Buffer(data))); + } }; } function saveat(filename) { return function (data) { - game_data.fs.writeFileSync(filename, new Buffer(data)); + if (data !== null) { + game_data.fs.writeFileSync(filename, new Buffer(data), null, flag_w, 0x1a4); + } }; } @@ -1259,8 +1479,8 @@ var Module = null; if (f && f.file) if (f.drive) { return fetch(f.file).then(mountat(f.drive)); - } else if (f.filename) { - return fetch(f.file).then(saveat(f.filename)); + } else if (f.mountpoint) { + return fetch(f.file).then(saveat(f.mountpoint)); } return null; })); @@ -1298,10 +1518,10 @@ var Module = null; canvas.requestPointerLock = getpointerlockenabler(); moveConfigToRoot(game_data.fs); - Module = init_module(game_data.emulatorStart, game_data.mounts, game_data.fs, game_data.locateAdditionalJS); + Module = init_module(game_data.emulator_arguments, game_data.fs, game_data.locateAdditionalJS); if (game_data.emulatorJS) { - splash.loading_text = 'Launching DosBox'; + splash.loading_text = 'Launching Emulator'; attach_script(game_data.emulatorJS); } else { splash.loading_text = 'Non-system disk or disk error'; @@ -1315,11 +1535,30 @@ var Module = null; }; this.start = start; - var progress_fetch_file = function (e) { - + var init_module = function(args, fs, locateAdditionalJS) { + return { arguments: args, + screenIsReadOnly: true, + print: function (text) { console.log(text); }, + canvas: canvas, + noInitialRun: false, + locateFile: locateAdditionalJS, + preInit: function () { + splash.loading_text = 'Loading game file(s) into file system'; + // Re-initialize BFS to just use the writable in-memory storage. + BrowserFS.initialize(fs); + var BFS = new BrowserFS.EmscriptenFS(); + // Mount the file system into Emscripten. + FS.mkdir('/emulator'); + FS.mount(BFS, {root: '/'}, '/emulator'); + splash.finished_loading = true; + if (callback) { + window.setTimeout(function() { callback(this); }, 0); + } + } + }; }; - var fetch_file = function(title, url, rt, raw, unmanaged) { + var fetch_file = function(title, url, rt, raw, optional) { var table = document.getElementById("dosbox-progress-indicator"); var row, cell; if (!table) { @@ -1342,73 +1581,24 @@ var Module = null; xhr.responseType = rt ? rt : 'arraybuffer'; xhr.onload = function(e) { if (xhr.status === 200) { - if (!unmanaged) { - xhr.progress = 1.0; - } cell.textContent = '✔'; resolve(raw ? xhr.response : new Int8Array(xhr.response)); } }; xhr.onerror = function (e) { - cell.textContent = '✘'; - reject(); + if (optional) { + cell.textContent = '?'; + resolve(null); + } else { + cell.textContent = '✘'; + reject(); + } }; - if (!unmanaged) { - xhr.onprogress = progress_fetch_file; - xhr.title = title; - xhr.progress = 0; - xhr.total = 0; - xhr.loaded = 0; - xhr.lengthComputable = false; - requests.push(xhr); - } xhr.send(); }); }; - var build_dosbox_arguments = function (emulator_start, mounts) { - splash.loading_text = 'Building arguments'; - var args = []; - - var len = mounts.length; - for (var i = 0; i < len; i++) { - args.push('-c', 'mount '+ mounts[i].drive +' /emulator'+ mounts[i].mountpoint); - } - - 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; - }; - - var init_module = function(emulator_start, mounts, fs, locateAdditionalJS) { - return { arguments: build_dosbox_arguments(emulator_start, mounts), - screenIsReadOnly: true, - print: function (text) { console.log(text); }, - canvas: canvas, - noInitialRun: false, - locateFile: locateAdditionalJS, - preInit: function () { - splash.loading_text = 'Loading game file(s) into file system'; - // Re-initialize BFS to just use the writable in-memory storage. - BrowserFS.initialize(fs); - var BFS = new BrowserFS.EmscriptenFS(); - // Mount the file system into Emscripten. - FS.mkdir('/emulator'); - FS.mount(BFS, {root: '/'}, '/emulator'); - splash.finished_loading = true; - if (callback) { - window.setTimeout(function() { callback(this); }, 0); - } - } - }; - }; - function keyevent(resolve) { return function (e) { if (typeof loader_game === 'object') @@ -1430,12 +1620,18 @@ var Module = null; var drawsplash = function () { canvas.setAttribute('moz-opaque', ''); var context = canvas.getContext('2d'); - splashimg.onload = function () { - draw_loading_status(0); - animLoop(draw_loading_status); - }; - splashimg.src = '/images/dosbox.png'; - spinnerimg.src = '/images/spinner.png'; + if (splashimg.src && splashimg.complete) { + draw_loading_status(0); + animLoop(draw_loading_status); + } else { + splashimg.onload = function () { + draw_loading_status(0); + animLoop(draw_loading_status); + }; + if (!splashimg.src) { + splashimg.src = '/images/dosbox.png'; + } + } }; var draw_loading_status = function (deltaT) { @@ -1461,10 +1657,12 @@ var Module = null; context.restore(); var table = document.getElementById("dosbox-progress-indicator"); - table.style.top = (canvas.offsetTop + (canvas.height / 2 + splashimg.height / 2) + 16 - (64/2)) +'px'; + if (table) { + table.style.top = (canvas.offsetTop + (canvas.height / 2 + splashimg.height / 2) + 16 - (64/2)) +'px'; + } - if (splash.finished_loading) { - document.getElementById("dosbox-progress-indicator").style.display = 'none'; + if (splash.finished_loading && table) { + table.style.display = 'none'; } if (splash.finished_loading || splash.failed_loading) { return false; @@ -1662,6 +1860,8 @@ var Module = null; window.IALoader = IALoader; window.DosBoxLoader = DosBoxLoader; + window.JSMESSLoader = JSMESSLoader; + window.JSMAMELoader = JSMAMELoader; window.Emulator = Emulator; })(typeof Promise === 'undefined' ? ES6Promise.Promise : Promise); @@ -1703,3 +1903,7 @@ var Module = null; // // return false; will stop the loop // }, // animWrapper); + +// legacy +var JSMESS = JSMESS || {}; +JSMESS.ready = function (f) { f(); }; From 46b3e6a0207187be8c357214c230fcf07d068d96 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Thu, 26 Feb 2015 11:42:54 -0800 Subject: [PATCH 26/60] remove es6-promise code from loader.js, now that IE loads it from the proper file --- loader.js | 961 ------------------------------------------------------ 1 file changed, 961 deletions(-) diff --git a/loader.js b/loader.js index 5d2a10e..752eb08 100644 --- a/loader.js +++ b/loader.js @@ -1,964 +1,3 @@ -/*! - * @overview es6-promise - a tiny implementation of Promises/A+. - * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) - * @license Licensed under MIT license - * See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE - * @version 2.0.0 - */ - -(function() { - "use strict"; - - function $$utils$$objectOrFunction(x) { - return typeof x === 'function' || (typeof x === 'object' && x !== null); - } - - function $$utils$$isFunction(x) { - return typeof x === 'function'; - } - - function $$utils$$isMaybeThenable(x) { - return typeof x === 'object' && x !== null; - } - - var $$utils$$_isArray; - - if (!Array.isArray) { - $$utils$$_isArray = function (x) { - return Object.prototype.toString.call(x) === '[object Array]'; - }; - } else { - $$utils$$_isArray = Array.isArray; - } - - var $$utils$$isArray = $$utils$$_isArray; - var $$utils$$now = Date.now || function() { return new Date().getTime(); }; - function $$utils$$F() { } - - var $$utils$$o_create = (Object.create || function (o) { - if (arguments.length > 1) { - throw new Error('Second argument not supported'); - } - if (typeof o !== 'object') { - throw new TypeError('Argument must be an object'); - } - $$utils$$F.prototype = o; - return new $$utils$$F(); - }); - - var $$asap$$len = 0; - - var $$asap$$default = function asap(callback, arg) { - $$asap$$queue[$$asap$$len] = callback; - $$asap$$queue[$$asap$$len + 1] = arg; - $$asap$$len += 2; - if ($$asap$$len === 2) { - // If len is 1, that means that we need to schedule an async flush. - // If additional callbacks are queued before the queue is flushed, they - // will be processed by this flush that we are scheduling. - $$asap$$scheduleFlush(); - } - }; - - var $$asap$$browserGlobal = (typeof window !== 'undefined') ? window : {}; - var $$asap$$BrowserMutationObserver = $$asap$$browserGlobal.MutationObserver || $$asap$$browserGlobal.WebKitMutationObserver; - - // test for web worker but not in IE10 - var $$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' && - typeof importScripts !== 'undefined' && - typeof MessageChannel !== 'undefined'; - - // node - function $$asap$$useNextTick() { - return function() { - process.nextTick($$asap$$flush); - }; - } - - function $$asap$$useMutationObserver() { - var iterations = 0; - var observer = new $$asap$$BrowserMutationObserver($$asap$$flush); - var node = document.createTextNode(''); - observer.observe(node, { characterData: true }); - - return function() { - node.data = (iterations = ++iterations % 2); - }; - } - - // web worker - function $$asap$$useMessageChannel() { - var channel = new MessageChannel(); - channel.port1.onmessage = $$asap$$flush; - return function () { - channel.port2.postMessage(0); - }; - } - - function $$asap$$useSetTimeout() { - return function() { - setTimeout($$asap$$flush, 1); - }; - } - - var $$asap$$queue = new Array(1000); - - function $$asap$$flush() { - for (var i = 0; i < $$asap$$len; i+=2) { - var callback = $$asap$$queue[i]; - var arg = $$asap$$queue[i+1]; - - callback(arg); - - $$asap$$queue[i] = undefined; - $$asap$$queue[i+1] = undefined; - } - - $$asap$$len = 0; - } - - var $$asap$$scheduleFlush; - - // Decide what async method to use to triggering processing of queued callbacks: - if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') { - $$asap$$scheduleFlush = $$asap$$useNextTick(); - } else if ($$asap$$BrowserMutationObserver) { - $$asap$$scheduleFlush = $$asap$$useMutationObserver(); - } else if ($$asap$$isWorker) { - $$asap$$scheduleFlush = $$asap$$useMessageChannel(); - } else { - $$asap$$scheduleFlush = $$asap$$useSetTimeout(); - } - - function $$$internal$$noop() {} - var $$$internal$$PENDING = void 0; - var $$$internal$$FULFILLED = 1; - var $$$internal$$REJECTED = 2; - var $$$internal$$GET_THEN_ERROR = new $$$internal$$ErrorObject(); - - function $$$internal$$selfFullfillment() { - return new TypeError("You cannot resolve a promise with itself"); - } - - function $$$internal$$cannotReturnOwn() { - return new TypeError('A promises callback cannot return that same promise.') - } - - function $$$internal$$getThen(promise) { - try { - return promise.then; - } catch(error) { - $$$internal$$GET_THEN_ERROR.error = error; - return $$$internal$$GET_THEN_ERROR; - } - } - - function $$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) { - try { - then.call(value, fulfillmentHandler, rejectionHandler); - } catch(e) { - return e; - } - } - - function $$$internal$$handleForeignThenable(promise, thenable, then) { - $$asap$$default(function(promise) { - var sealed = false; - var error = $$$internal$$tryThen(then, thenable, function(value) { - if (sealed) { return; } - sealed = true; - if (thenable !== value) { - $$$internal$$resolve(promise, value); - } else { - $$$internal$$fulfill(promise, value); - } - }, function(reason) { - if (sealed) { return; } - sealed = true; - - $$$internal$$reject(promise, reason); - }, 'Settle: ' + (promise._label || ' unknown promise')); - - if (!sealed && error) { - sealed = true; - $$$internal$$reject(promise, error); - } - }, promise); - } - - function $$$internal$$handleOwnThenable(promise, thenable) { - if (thenable._state === $$$internal$$FULFILLED) { - $$$internal$$fulfill(promise, thenable._result); - } else if (promise._state === $$$internal$$REJECTED) { - $$$internal$$reject(promise, thenable._result); - } else { - $$$internal$$subscribe(thenable, undefined, function(value) { - $$$internal$$resolve(promise, value); - }, function(reason) { - $$$internal$$reject(promise, reason); - }); - } - } - - function $$$internal$$handleMaybeThenable(promise, maybeThenable) { - if (maybeThenable.constructor === promise.constructor) { - $$$internal$$handleOwnThenable(promise, maybeThenable); - } else { - var then = $$$internal$$getThen(maybeThenable); - - if (then === $$$internal$$GET_THEN_ERROR) { - $$$internal$$reject(promise, $$$internal$$GET_THEN_ERROR.error); - } else if (then === undefined) { - $$$internal$$fulfill(promise, maybeThenable); - } else if ($$utils$$isFunction(then)) { - $$$internal$$handleForeignThenable(promise, maybeThenable, then); - } else { - $$$internal$$fulfill(promise, maybeThenable); - } - } - } - - function $$$internal$$resolve(promise, value) { - if (promise === value) { - $$$internal$$reject(promise, $$$internal$$selfFullfillment()); - } else if ($$utils$$objectOrFunction(value)) { - $$$internal$$handleMaybeThenable(promise, value); - } else { - $$$internal$$fulfill(promise, value); - } - } - - function $$$internal$$publishRejection(promise) { - if (promise._onerror) { - promise._onerror(promise._result); - } - - $$$internal$$publish(promise); - } - - function $$$internal$$fulfill(promise, value) { - if (promise._state !== $$$internal$$PENDING) { return; } - - promise._result = value; - promise._state = $$$internal$$FULFILLED; - - if (promise._subscribers.length === 0) { - } else { - $$asap$$default($$$internal$$publish, promise); - } - } - - function $$$internal$$reject(promise, reason) { - if (promise._state !== $$$internal$$PENDING) { return; } - promise._state = $$$internal$$REJECTED; - promise._result = reason; - - $$asap$$default($$$internal$$publishRejection, promise); - } - - function $$$internal$$subscribe(parent, child, onFulfillment, onRejection) { - var subscribers = parent._subscribers; - var length = subscribers.length; - - parent._onerror = null; - - subscribers[length] = child; - subscribers[length + $$$internal$$FULFILLED] = onFulfillment; - subscribers[length + $$$internal$$REJECTED] = onRejection; - - if (length === 0 && parent._state) { - $$asap$$default($$$internal$$publish, parent); - } - } - - function $$$internal$$publish(promise) { - var subscribers = promise._subscribers; - var settled = promise._state; - - if (subscribers.length === 0) { return; } - - var child, callback, detail = promise._result; - - for (var i = 0; i < subscribers.length; i += 3) { - child = subscribers[i]; - callback = subscribers[i + settled]; - - if (child) { - $$$internal$$invokeCallback(settled, child, callback, detail); - } else { - callback(detail); - } - } - - promise._subscribers.length = 0; - } - - function $$$internal$$ErrorObject() { - this.error = null; - } - - var $$$internal$$TRY_CATCH_ERROR = new $$$internal$$ErrorObject(); - - function $$$internal$$tryCatch(callback, detail) { - try { - return callback(detail); - } catch(e) { - $$$internal$$TRY_CATCH_ERROR.error = e; - return $$$internal$$TRY_CATCH_ERROR; - } - } - - function $$$internal$$invokeCallback(settled, promise, callback, detail) { - var hasCallback = $$utils$$isFunction(callback), - value, error, succeeded, failed; - - if (hasCallback) { - value = $$$internal$$tryCatch(callback, detail); - - if (value === $$$internal$$TRY_CATCH_ERROR) { - failed = true; - error = value.error; - value = null; - } else { - succeeded = true; - } - - if (promise === value) { - $$$internal$$reject(promise, $$$internal$$cannotReturnOwn()); - return; - } - - } else { - value = detail; - succeeded = true; - } - - if (promise._state !== $$$internal$$PENDING) { - // noop - } else if (hasCallback && succeeded) { - $$$internal$$resolve(promise, value); - } else if (failed) { - $$$internal$$reject(promise, error); - } else if (settled === $$$internal$$FULFILLED) { - $$$internal$$fulfill(promise, value); - } else if (settled === $$$internal$$REJECTED) { - $$$internal$$reject(promise, value); - } - } - - function $$$internal$$initializePromise(promise, resolver) { - try { - resolver(function resolvePromise(value){ - $$$internal$$resolve(promise, value); - }, function rejectPromise(reason) { - $$$internal$$reject(promise, reason); - }); - } catch(e) { - $$$internal$$reject(promise, e); - } - } - - function $$$enumerator$$makeSettledResult(state, position, value) { - if (state === $$$internal$$FULFILLED) { - return { - state: 'fulfilled', - value: value - }; - } else { - return { - state: 'rejected', - reason: value - }; - } - } - - function $$$enumerator$$Enumerator(Constructor, input, abortOnReject, label) { - this._instanceConstructor = Constructor; - this.promise = new Constructor($$$internal$$noop, label); - this._abortOnReject = abortOnReject; - - if (this._validateInput(input)) { - this._input = input; - this.length = input.length; - this._remaining = input.length; - - this._init(); - - if (this.length === 0) { - $$$internal$$fulfill(this.promise, this._result); - } else { - this.length = this.length || 0; - this._enumerate(); - if (this._remaining === 0) { - $$$internal$$fulfill(this.promise, this._result); - } - } - } else { - $$$internal$$reject(this.promise, this._validationError()); - } - } - - $$$enumerator$$Enumerator.prototype._validateInput = function(input) { - return $$utils$$isArray(input); - }; - - $$$enumerator$$Enumerator.prototype._validationError = function() { - return new Error('Array Methods must be provided an Array'); - }; - - $$$enumerator$$Enumerator.prototype._init = function() { - this._result = new Array(this.length); - }; - - var $$$enumerator$$default = $$$enumerator$$Enumerator; - - $$$enumerator$$Enumerator.prototype._enumerate = function() { - var length = this.length; - var promise = this.promise; - var input = this._input; - - for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) { - this._eachEntry(input[i], i); - } - }; - - $$$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) { - var c = this._instanceConstructor; - if ($$utils$$isMaybeThenable(entry)) { - if (entry.constructor === c && entry._state !== $$$internal$$PENDING) { - entry._onerror = null; - this._settledAt(entry._state, i, entry._result); - } else { - this._willSettleAt(c.resolve(entry), i); - } - } else { - this._remaining--; - this._result[i] = this._makeResult($$$internal$$FULFILLED, i, entry); - } - }; - - $$$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) { - var promise = this.promise; - - if (promise._state === $$$internal$$PENDING) { - this._remaining--; - - if (this._abortOnReject && state === $$$internal$$REJECTED) { - $$$internal$$reject(promise, value); - } else { - this._result[i] = this._makeResult(state, i, value); - } - } - - if (this._remaining === 0) { - $$$internal$$fulfill(promise, this._result); - } - }; - - $$$enumerator$$Enumerator.prototype._makeResult = function(state, i, value) { - return value; - }; - - $$$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) { - var enumerator = this; - - $$$internal$$subscribe(promise, undefined, function(value) { - enumerator._settledAt($$$internal$$FULFILLED, i, value); - }, function(reason) { - enumerator._settledAt($$$internal$$REJECTED, i, reason); - }); - }; - - var $$promise$all$$default = function all(entries, label) { - return new $$$enumerator$$default(this, entries, true /* abort on reject */, label).promise; - }; - - var $$promise$race$$default = function race(entries, label) { - /*jshint validthis:true */ - var Constructor = this; - - var promise = new Constructor($$$internal$$noop, label); - - if (!$$utils$$isArray(entries)) { - $$$internal$$reject(promise, new TypeError('You must pass an array to race.')); - return promise; - } - - var length = entries.length; - - function onFulfillment(value) { - $$$internal$$resolve(promise, value); - } - - function onRejection(reason) { - $$$internal$$reject(promise, reason); - } - - for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) { - $$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection); - } - - return promise; - }; - - var $$promise$resolve$$default = function resolve(object, label) { - /*jshint validthis:true */ - var Constructor = this; - - if (object && typeof object === 'object' && object.constructor === Constructor) { - return object; - } - - var promise = new Constructor($$$internal$$noop, label); - $$$internal$$resolve(promise, object); - return promise; - }; - - var $$promise$reject$$default = function reject(reason, label) { - /*jshint validthis:true */ - var Constructor = this; - var promise = new Constructor($$$internal$$noop, label); - $$$internal$$reject(promise, reason); - return promise; - }; - - var $$es6$promise$promise$$counter = 0; - - function $$es6$promise$promise$$needsResolver() { - throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); - } - - function $$es6$promise$promise$$needsNew() { - throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); - } - - var $$es6$promise$promise$$default = $$es6$promise$promise$$Promise; - - /** - Promise objects represent the eventual result of an asynchronous operation. The - primary way of interacting with a promise is through its `then` method, which - registers callbacks to receive either a promise’s eventual value or the reason - why the promise cannot be fulfilled. - - Terminology - ----------- - - - `promise` is an object or function with a `then` method whose behavior conforms to this specification. - - `thenable` is an object or function that defines a `then` method. - - `value` is any legal JavaScript value (including undefined, a thenable, or a promise). - - `exception` is a value that is thrown using the throw statement. - - `reason` is a value that indicates why a promise was rejected. - - `settled` the final resting state of a promise, fulfilled or rejected. - - A promise can be in one of three states: pending, fulfilled, or rejected. - - Promises that are fulfilled have a fulfillment value and are in the fulfilled - state. Promises that are rejected have a rejection reason and are in the - rejected state. A fulfillment value is never a thenable. - - Promises can also be said to *resolve* a value. If this value is also a - promise, then the original promise's settled state will match the value's - settled state. So a promise that *resolves* a promise that rejects will - itself reject, and a promise that *resolves* a promise that fulfills will - itself fulfill. - - - Basic Usage: - ------------ - - ```js - var promise = new Promise(function(resolve, reject) { - // on success - resolve(value); - - // on failure - reject(reason); - }); - - promise.then(function(value) { - // on fulfillment - }, function(reason) { - // on rejection - }); - ``` - - Advanced Usage: - --------------- - - Promises shine when abstracting away asynchronous interactions such as - `XMLHttpRequest`s. - - ```js - function getJSON(url) { - return new Promise(function(resolve, reject){ - var xhr = new XMLHttpRequest(); - - xhr.open('GET', url); - xhr.onreadystatechange = handler; - xhr.responseType = 'json'; - xhr.setRequestHeader('Accept', 'application/json'); - xhr.send(); - - function handler() { - if (this.readyState === this.DONE) { - if (this.status === 200) { - resolve(this.response); - } else { - reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']')); - } - } - }; - }); - } - - getJSON('/posts.json').then(function(json) { - // on fulfillment - }, function(reason) { - // on rejection - }); - ``` - - Unlike callbacks, promises are great composable primitives. - - ```js - Promise.all([ - getJSON('/posts'), - getJSON('/comments') - ]).then(function(values){ - values[0] // => postsJSON - values[1] // => commentsJSON - - return values; - }); - ``` - - @class Promise - @param {function} resolver - Useful for tooling. - @constructor - */ - function $$es6$promise$promise$$Promise(resolver) { - this._id = $$es6$promise$promise$$counter++; - this._state = undefined; - this._result = undefined; - this._subscribers = []; - - if ($$$internal$$noop !== resolver) { - if (!$$utils$$isFunction(resolver)) { - $$es6$promise$promise$$needsResolver(); - } - - if (!(this instanceof $$es6$promise$promise$$Promise)) { - $$es6$promise$promise$$needsNew(); - } - - $$$internal$$initializePromise(this, resolver); - } - } - - $$es6$promise$promise$$Promise.all = $$promise$all$$default; - $$es6$promise$promise$$Promise.race = $$promise$race$$default; - $$es6$promise$promise$$Promise.resolve = $$promise$resolve$$default; - $$es6$promise$promise$$Promise.reject = $$promise$reject$$default; - - $$es6$promise$promise$$Promise.prototype = { - constructor: $$es6$promise$promise$$Promise, - - /** - The primary way of interacting with a promise is through its `then` method, - which registers callbacks to receive either a promise's eventual value or the - reason why the promise cannot be fulfilled. - - ```js - findUser().then(function(user){ - // user is available - }, function(reason){ - // user is unavailable, and you are given the reason why - }); - ``` - - Chaining - -------- - - The return value of `then` is itself a promise. This second, 'downstream' - promise is resolved with the return value of the first promise's fulfillment - or rejection handler, or rejected if the handler throws an exception. - - ```js - findUser().then(function (user) { - return user.name; - }, function (reason) { - return 'default name'; - }).then(function (userName) { - // If `findUser` fulfilled, `userName` will be the user's name, otherwise it - // will be `'default name'` - }); - - findUser().then(function (user) { - throw new Error('Found user, but still unhappy'); - }, function (reason) { - throw new Error('`findUser` rejected and we're unhappy'); - }).then(function (value) { - // never reached - }, function (reason) { - // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'. - // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'. - }); - ``` - If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream. - - ```js - findUser().then(function (user) { - throw new PedagogicalException('Upstream error'); - }).then(function (value) { - // never reached - }).then(function (value) { - // never reached - }, function (reason) { - // The `PedgagocialException` is propagated all the way down to here - }); - ``` - - Assimilation - ------------ - - Sometimes the value you want to propagate to a downstream promise can only be - retrieved asynchronously. This can be achieved by returning a promise in the - fulfillment or rejection handler. The downstream promise will then be pending - until the returned promise is settled. This is called *assimilation*. - - ```js - findUser().then(function (user) { - return findCommentsByAuthor(user); - }).then(function (comments) { - // The user's comments are now available - }); - ``` - - If the assimliated promise rejects, then the downstream promise will also reject. - - ```js - findUser().then(function (user) { - return findCommentsByAuthor(user); - }).then(function (comments) { - // If `findCommentsByAuthor` fulfills, we'll have the value here - }, function (reason) { - // If `findCommentsByAuthor` rejects, we'll have the reason here - }); - ``` - - Simple Example - -------------- - - Synchronous Example - - ```javascript - var result; - - try { - result = findResult(); - // success - } catch(reason) { - // failure - } - ``` - - Errback Example - - ```js - findResult(function(result, err){ - if (err) { - // failure - } else { - // success - } - }); - ``` - - Promise Example; - - ```javascript - findResult().then(function(result){ - // success - }, function(reason){ - // failure - }); - ``` - - Advanced Example - -------------- - - Synchronous Example - - ```javascript - var author, books; - - try { - author = findAuthor(); - books = findBooksByAuthor(author); - // success - } catch(reason) { - // failure - } - ``` - - Errback Example - - ```js - - function foundBooks(books) { - - } - - function failure(reason) { - - } - - findAuthor(function(author, err){ - if (err) { - failure(err); - // failure - } else { - try { - findBoooksByAuthor(author, function(books, err) { - if (err) { - failure(err); - } else { - try { - foundBooks(books); - } catch(reason) { - failure(reason); - } - } - }); - } catch(error) { - failure(err); - } - // success - } - }); - ``` - - Promise Example; - - ```javascript - findAuthor(). - then(findBooksByAuthor). - then(function(books){ - // found books - }).catch(function(reason){ - // something went wrong - }); - ``` - - @method then - @param {Function} onFulfilled - @param {Function} onRejected - Useful for tooling. - @return {Promise} - */ - then: function(onFulfillment, onRejection) { - var parent = this; - var state = parent._state; - - if (state === $$$internal$$FULFILLED && !onFulfillment || state === $$$internal$$REJECTED && !onRejection) { - return this; - } - - var child = new this.constructor($$$internal$$noop); - var result = parent._result; - - if (state) { - var callback = arguments[state - 1]; - $$asap$$default(function(){ - $$$internal$$invokeCallback(state, child, callback, result); - }); - } else { - $$$internal$$subscribe(parent, child, onFulfillment, onRejection); - } - - return child; - }, - - /** - `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same - as the catch block of a try/catch statement. - - ```js - function findAuthor(){ - throw new Error('couldn't find that author'); - } - - // synchronous - try { - findAuthor(); - } catch(reason) { - // something went wrong - } - - // async with promises - findAuthor().catch(function(reason){ - // something went wrong - }); - ``` - - @method catch - @param {Function} onRejection - Useful for tooling. - @return {Promise} - */ - 'catch': function(onRejection) { - return this.then(null, onRejection); - } - }; - - var $$es6$promise$polyfill$$default = function polyfill() { - var local; - - if (typeof global !== 'undefined') { - local = global; - } else if (typeof window !== 'undefined' && window.document) { - local = window; - } else { - local = self; - } - - var es6PromiseSupport = - "Promise" in local && - // Some of these methods are missing from - // Firefox/Chrome experimental implementations - "resolve" in local.Promise && - "reject" in local.Promise && - "all" in local.Promise && - "race" in local.Promise && - // Older version of the spec had a resolver object - // as the arg rather than a function - (function() { - var resolve; - new local.Promise(function(r) { resolve = r; }); - return $$utils$$isFunction(resolve); - }()); - - if (!es6PromiseSupport) { - local.Promise = $$es6$promise$promise$$default; - } - }; - - var es6$promise$umd$$ES6Promise = { - 'Promise': $$es6$promise$promise$$default, - 'polyfill': $$es6$promise$polyfill$$default - }; - - /* global define:true module:true window: true */ - if (typeof define === 'function' && define['amd']) { - define(function() { return es6$promise$umd$$ES6Promise; }); - } else if (typeof module !== 'undefined' && module['exports']) { - module['exports'] = es6$promise$umd$$ES6Promise; - } else if (typeof this !== 'undefined') { - this['ES6Promise'] = es6$promise$umd$$ES6Promise; - } -}).call(this); - var Module = null; (function (Promise) { From 78fbc4a7f6c692ab2aae72f332f9584e234ec171 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Thu, 26 Feb 2015 12:04:09 -0800 Subject: [PATCH 27/60] fix arguments for build_mess_arguments --- loader.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/loader.js b/loader.js index 752eb08..2ff522f 100644 --- a/loader.js +++ b/loader.js @@ -333,7 +333,7 @@ var Module = null; return { extra_mess_args: args }; }; - var build_mess_arguments = function (muted, game, driver, native_resolution, sample_rate, peripheral, extra_args) { + var build_mess_arguments = function (muted, driver, native_resolution, sample_rate, peripheral, extra_args) { var args = [driver, '-verbose', '-rompath', 'emulator', @@ -347,7 +347,7 @@ var Module = null; args.push('-samplerate', sample_rate); } - if (game) { + if (peripheral && peripheral[0]) { args.push('-' + peripheral[0], peripheral[1].replace(/\//g,'_')); } From 2e0578cdef2cce90ad6f0104365f51fdd1ac7bf1 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Thu, 26 Feb 2015 12:12:55 -0800 Subject: [PATCH 28/60] fix url for bios files --- loader.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/loader.js b/loader.js index 2ff522f..935399c 100644 --- a/loader.js +++ b/loader.js @@ -151,7 +151,7 @@ var Module = null; var title = "Bios File ("+ (i+1) +" of "+ bios_files.length +")"; files.push(cfgr.mountFile('/'+ fname, cfgr.fetchFile(title, - get_js_url(fname)))); + get_bios_url(fname)))); } }); files.push(cfgr.mountFile('/'+ get_game_name(game), @@ -171,7 +171,7 @@ var Module = null; var title = "Bios File ("+ (i+1) +" of "+ bios_files.length +")"; files.push(cfgr.mountFile('/'+ fname, cfgr.fetchFile(title, - get_js_url(fname)))); + get_bios_url(fname)))); } }); files.push(cfgr.mountFile('/'+ get_game_name(game), @@ -210,6 +210,10 @@ var Module = null; return "//cors.archive.org/cors/jsmess_engine_v2/"+ js_filename; }; + var get_bios_url = function (bios_filename) { + return "//cors.archive.org/cors/jsmess_bios_v2/"+ bios_filename; + }; + function mountat (drive) { return function (data) { return { drive: drive, From 1b8a17a31a5eae278956b2d410ce1ded524efad5 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Thu, 26 Feb 2015 12:27:21 -0800 Subject: [PATCH 29/60] fix peripheral handling for mess/mame --- loader.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/loader.js b/loader.js index 935399c..4072073 100644 --- a/loader.js +++ b/loader.js @@ -73,15 +73,11 @@ var Module = null; .item(0) .textContent)); } else if (module) { - if (mame) { - config_args.push(cfgr.driver(modulecfg.driver), - cfgr.extraArgs(modulecfg.extra_args)); - if (modulecfg.peripherals && modulecfg.peripherals[0]) { - config_args.push(cfgr.peripheral(modulecfg.peripherals[0], game)); - } - } else { - config_args.push(cfgr.driver(modulecfg.driver), - cfgr.extraArgs(modulecfg.extra_args)); + config_args.push(cfgr.driver(modulecfg.driver), + cfgr.extraArgs(modulecfg.extra_args)); + if (modulecfg.peripherals && modulecfg.peripherals[0]) { + config_args.push(cfgr.peripheral(modulecfg.peripherals[0], + get_game_name(game))); } } @@ -352,7 +348,8 @@ var Module = null; } if (peripheral && peripheral[0]) { - args.push('-' + peripheral[0], peripheral[1].replace(/\//g,'_')); + args.push('-' + peripheral[0], + '/emulator/'+ (peripheral[1].replace(/\//g,'_'))); } if (extra_args) { From c6721bf5d21d76dffb0cf4f2e507a87bf9977dfb Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Fri, 27 Feb 2015 16:02:22 -0800 Subject: [PATCH 30/60] first stab at a readme --- README.md | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..7ffa4af --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# Name # + +js-emulators + +# Synopsis # + +The goal of this little project is to make it easy to embed a javascript-based emulator in your own webpage. It downloads the files you specify (with aprogress ui to show what is happening), arranges them to form a filesystem, constructs the necessary arguments for the emulator, handles transitions to and from full-screen mode, and detects and enables game pads. + +To use this project you'll need to provide it with a canvas element, styled as necessary so that it has the correct size on screen (the program will be scaled up automatically to fit, controlling for aspect ratio). You will also likely want to provide a simple UI for entering full-screen mode or muting the audio; these can simply call methods on the emulator when activated. + +# Examples # + +## Arcade game ## + +Loads the emulator for the arcade game 1943, and gives it a compressed copy of the rom (assumes that this is in games/1943.zip). + + var emulator = new Emulator("#canvas", null, + new JSMAMELoader(JSMAMELoader.driver("1943"), + JSMAMELoader.emulatorJS("emulators/mess1943.js.gz"), + JSMAMELoader.mountFile("1943.zip", + JSMAMELoader.fetchFile("Game File", "games/1943.zip")))) + +## Console game for Atari 2600 ## + +Loads the emulator for the Atari 2600 console, and an image of a catridge for Pitfall. Notice how we download the image, storing it in a file, then set up a "cart" peripheral so that the emulator can find it. We also load a configuration file that preconfigures some keybindings needed to use the 2600. + + var emulator = new Emulator("#canvas", null, + new JSMESSLoader(JSMESSLoader.driver("a2600"), + JSMESSLoader.emulatorJS("emulators/messa2600.js.gz"), + JSMESSLoader.mountFile("atari_2600_pitfall_1983_cce_c-813.bin", + JSMESSLoader.fetchFile("Game File", + "games/atari_2600_pitfall_1983_cce_c-813.bin")), + JSMESSLoader.mountFile("foo.cfg", + JSMESSLoader.fetchFile("Config File", + "emulators/a2600.cfg")), + JSMESSLoader.peripheral("cart", "atari_2600_pitfall_1983_cce_c-813.bin"))) + +## DOS game ## + +Here we load the dosbox emulator, and a zip file containing the game ZZT which we mount as the C drive. We also tell DosBox to immediately start running zzt.exe. + + var emulator = new Emulator("#canvas", null, + new DosBoxLoader(DosBoxLoader.emulatorJS("emulators/dosbox.js.gz"), + DosBoxLoader.mountZip("c", DosBoxLoader.fetchFile("Game File", "games/zzt.zip")), + DosBoxLoader.startExe("zzt.exe"))) + +# Configuration API # + +Currently there are two supported emulators, JSMESS and EM-DosBox. JSMESS provides emulation for arcade games, consoles, and early personal computers. As this emulator supports such a wide variety of hardware it has been broken up into several dozen emulators each supporting one machine lest the resulting javascript be intractably large (60+ megabytes). EM-DosBox provides emulation for software that runs on x86 PCs using the DOS operating systems common to the era. + +Each of these is configured by calling a constructor function and providing it with arguments formed by calling static methods on that same constructor. + +## Common ## + +* emulatorJS(url) +* mountZip(drive, file) +* mountFile(filename, file) +* fetchFile(url) +* fetchOptionalFile(url) +* localFile(data) + +## JSMESS ## + +* driver(driverName) +* extraArgs(args) +* peripheral(name, filename) + +## JSMAME ## + +* driver(driverName) +* extraArgs(args) + +## EM-DosBox ## + +* startExe(filename) + +# Known Bugs # + +* splash screen doesn't always fit inside the canvas +* not enough indication of download progress From 8ff85a34d834b5a43bb525ec023b60692e340f2e Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Fri, 27 Feb 2015 16:42:56 -0800 Subject: [PATCH 31/60] improve readme a bit: IA, known issues --- README.md | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 7ffa4af..e0cbda7 100644 --- a/README.md +++ b/README.md @@ -52,29 +52,40 @@ Each of these is configured by calling a constructor function and providing it w ## Common ## -* emulatorJS(url) -* mountZip(drive, file) -* mountFile(filename, file) -* fetchFile(url) -* fetchOptionalFile(url) -* localFile(data) +* `emulatorJS(url)` +* `mountZip(drive, file)` +* `mountFile(filename, file)` +* `fetchFile(url)` +* `fetchOptionalFile(url)` +* `localFile(data)` ## JSMESS ## -* driver(driverName) -* extraArgs(args) -* peripheral(name, filename) +* `driver(driverName)` +* `extraArgs(args)` +* `peripheral(name, filename)` ## JSMAME ## -* driver(driverName) -* extraArgs(args) +* `driver(driverName)` +* `extraArgs(args)` ## EM-DosBox ## * startExe(filename) +# Internet Archive # + +There's also a helper for loading software from [the Internet Archive](https://archive.org/v2), `IALoader`. IALoader looks at the metadata associated with their items and uses that to build the configuration for the emulator. + +## Examples ## + + var emulator = IALoader("#canvas", "atari_2600_pitfall_1983_cce_c-813/atari_2600_pitfall_1983_cce_c-813.bin"); + # Known Bugs # * splash screen doesn't always fit inside the canvas -* not enough indication of download progress +* need to improve the download progress indicators +* browser feature detection for volume/mute/full-screen +* handling of aspect ratios, and their interaction with full-screen mode +* finish API for volume/mute/full-screen requests From 8607c597870da1414ca586e35369988f6cfbdd61 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Fri, 27 Feb 2015 16:54:19 -0800 Subject: [PATCH 32/60] mention run-time api --- README.md | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e0cbda7..d1161f6 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,11 @@ The goal of this little project is to make it easy to embed a javascript-based e To use this project you'll need to provide it with a canvas element, styled as necessary so that it has the correct size on screen (the program will be scaled up automatically to fit, controlling for aspect ratio). You will also likely want to provide a simple UI for entering full-screen mode or muting the audio; these can simply call methods on the emulator when activated. -# Examples # +# Configuration # -## Arcade game ## +## Examples ## + +### Arcade game ### Loads the emulator for the arcade game 1943, and gives it a compressed copy of the rom (assumes that this is in games/1943.zip). @@ -20,7 +22,7 @@ Loads the emulator for the arcade game 1943, and gives it a compressed copy of t JSMAMELoader.mountFile("1943.zip", JSMAMELoader.fetchFile("Game File", "games/1943.zip")))) -## Console game for Atari 2600 ## +### Console game for Atari 2600 ### Loads the emulator for the Atari 2600 console, and an image of a catridge for Pitfall. Notice how we download the image, storing it in a file, then set up a "cart" peripheral so that the emulator can find it. We also load a configuration file that preconfigures some keybindings needed to use the 2600. @@ -35,7 +37,7 @@ Loads the emulator for the Atari 2600 console, and an image of a catridge for Pi "emulators/a2600.cfg")), JSMESSLoader.peripheral("cart", "atari_2600_pitfall_1983_cce_c-813.bin"))) -## DOS game ## +### DOS game ### Here we load the dosbox emulator, and a zip file containing the game ZZT which we mount as the C drive. We also tell DosBox to immediately start running zzt.exe. @@ -44,13 +46,13 @@ Here we load the dosbox emulator, and a zip file containing the game ZZT which w DosBoxLoader.mountZip("c", DosBoxLoader.fetchFile("Game File", "games/zzt.zip")), DosBoxLoader.startExe("zzt.exe"))) -# Configuration API # +## Configuration API ## Currently there are two supported emulators, JSMESS and EM-DosBox. JSMESS provides emulation for arcade games, consoles, and early personal computers. As this emulator supports such a wide variety of hardware it has been broken up into several dozen emulators each supporting one machine lest the resulting javascript be intractably large (60+ megabytes). EM-DosBox provides emulation for software that runs on x86 PCs using the DOS operating systems common to the era. Each of these is configured by calling a constructor function and providing it with arguments formed by calling static methods on that same constructor. -## Common ## +### Common ### * `emulatorJS(url)` * `mountZip(drive, file)` @@ -59,22 +61,22 @@ Each of these is configured by calling a constructor function and providing it w * `fetchOptionalFile(url)` * `localFile(data)` -## JSMESS ## +### JSMESS ### * `driver(driverName)` * `extraArgs(args)` * `peripheral(name, filename)` -## JSMAME ## +### JSMAME ### * `driver(driverName)` * `extraArgs(args)` -## EM-DosBox ## +### EM-DosBox ### -* startExe(filename) +* `startExe(filename)` -# Internet Archive # +## Internet Archive ## There's also a helper for loading software from [the Internet Archive](https://archive.org/v2), `IALoader`. IALoader looks at the metadata associated with their items and uses that to build the configuration for the emulator. @@ -82,6 +84,15 @@ There's also a helper for loading software from [the Internet Archive](https://a var emulator = IALoader("#canvas", "atari_2600_pitfall_1983_cce_c-813/atari_2600_pitfall_1983_cce_c-813.bin"); +# Runtime API # + +Once you have an emulator object, there are several methods you can call. + +* `start()` +* `requestFullScreen()` +* `mute()` +* others… + # Known Bugs # * splash screen doesn't always fit inside the canvas From 95caa220354ddfc3c77ac093d9c7476da27a6588 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 3 Mar 2015 23:51:06 -0800 Subject: [PATCH 33/60] move promises to the correct filename --- es6-promise-2.0.1.js => es6-promise.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename es6-promise-2.0.1.js => es6-promise.js (100%) diff --git a/es6-promise-2.0.1.js b/es6-promise.js similarity index 100% rename from es6-promise-2.0.1.js rename to es6-promise.js From c344a4593ba36c6499822ec82de5f8ed867db8f7 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 3 Mar 2015 23:51:17 -0800 Subject: [PATCH 34/60] add browserfs --- browserfs.js | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 browserfs.js diff --git a/browserfs.js b/browserfs.js new file mode 100644 index 0000000..eacbb24 --- /dev/null +++ b/browserfs.js @@ -0,0 +1,5 @@ +!function(){if(Date.now||(Date.now=function(){return(new Date).getTime()}),Array.isArray||(Array.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)}),Object.keys||(Object.keys=function(){var t=Object.prototype.hasOwnProperty,e=!{toString:null}.propertyIsEnumerable("toString"),n=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],r=n.length;return function(i){if("object"!=typeof i&&("function"!=typeof i||null===i))throw new TypeError("Object.keys called on non-object");var o,a,s=[];for(o in i)t.call(i,o)&&s.push(o);if(e)for(a=0;r>a;a++)t.call(i,n[a])&&s.push(n[a]);return s}}()),"b"!=="ab".substr(-1)&&(String.prototype.substr=function(t){return function(e,n){return 0>e&&(e=this.length+e),t.call(this,e,n)}}(String.prototype.substr)),Array.prototype.forEach||(Array.prototype.forEach=function(t,e){for(var n=0;n0)){var r=e.shift();return r()}};t.addEventListener?t.addEventListener("message",i,!0):t.attachEvent("onmessage",i)}else if(t.MessageChannel){var o=new t.MessageChannel;o.port1.onmessage=function(){return e.length>0?e.shift()():void 0},t.setImmediate=function(t){e.push(t),o.port2.postMessage("")}}else t.setImmediate=function(t){return setTimeout(t,0)}}Array.prototype.indexOf||(Array.prototype.indexOf=function(t,e){if("undefined"==typeof e&&(e=0),!this)throw new TypeError;var n=this.length;if(0===n||r>=n)return-1;var r=e;0>r&&(r=n+r);for(var i=r;n>i;i++)if(this[i]===t)return i;return-1}),Array.prototype.forEach||(Array.prototype.forEach=function(t,e){var n,r;for(n=0,r=this.length;r>n;++n)n in this&&t.call(e,this[n],n,this)}),Array.prototype.map||(Array.prototype.map=function(t,e){var n,r,i;if(null==this)throw new TypeError(" this is null or not defined");var o=Object(this),a=o.length>>>0;if("function"!=typeof t)throw new TypeError(t+" is not a function");for(e&&(n=e),r=new Array(a),i=0;a>i;){var s,u;i in o&&(s=o[i],u=t.call(n,s,i,o),r[i]=u),i++}return r}),"undefined"!=typeof document&&void 0===window.chrome&&document.write("\r\n\r\n");var a,s,u;!function(t){function e(t,e){return b.call(t,e)}function n(t,e){var n,r,i,o,a,s,u,f,c,p,h,l=e&&e.split("/"),d=m.map,y=d&&d["*"]||{};if(t&&"."===t.charAt(0))if(e){for(l=l.slice(0,l.length-1),t=t.split("/"),a=t.length-1,m.nodeIdCompat&&S.test(t[a])&&(t[a]=t[a].replace(S,"")),t=l.concat(t),c=0;c0&&(t.splice(c-1,2),c-=2)}t=t.join("/")}else 0===t.indexOf("./")&&(t=t.substring(2));if((l||y)&&d){for(n=t.split("/"),c=n.length;c>0;c-=1){if(r=n.slice(0,c).join("/"),l)for(p=l.length;p>0;p-=1)if(i=d[l.slice(0,p).join("/")],i&&(i=i[r])){o=i,s=c;break}if(o)break;!u&&y&&y[r]&&(u=y[r],f=c)}!o&&u&&(o=u,s=f),o&&(n.splice(0,s,o),t=n.join("/"))}return t}function r(e,n){return function(){var r=E.call(arguments,0);return"string"!=typeof r[0]&&1===r.length&&r.push(null),l.apply(t,r.concat([e,n]))}}function i(t){return function(e){return n(e,t)}}function o(t){return function(e){g[t]=e}}function f(n){if(e(w,n)){var r=w[n];delete w[n],v[n]=!0,h.apply(t,r)}if(!e(g,n)&&!e(v,n))throw new Error("No "+n);return g[n]}function c(t){var e,n=t?t.indexOf("!"):-1;return n>-1&&(e=t.substring(0,n),t=t.substring(n+1,t.length)),[e,t]}function p(t){return function(){return m&&m.config&&m.config[t]||{}}}var h,l,d,y,g={},w={},m={},v={},b=Object.prototype.hasOwnProperty,E=[].slice,S=/\.js$/;d=function(t,e){var r,o=c(t),a=o[0];return t=o[1],a&&(a=n(a,e),r=f(a)),a?t=r&&r.normalize?r.normalize(t,i(e)):n(t,e):(t=n(t,e),o=c(t),a=o[0],t=o[1],a&&(r=f(a))),{f:a?a+"!"+t:t,n:t,pr:a,p:r}},y={require:function(t){return r(t)},exports:function(t){var e=g[t];return"undefined"!=typeof e?e:g[t]={}},module:function(t){return{id:t,uri:"",exports:g[t],config:p(t)}}},h=function(n,i,a,s){var u,c,p,h,l,m,b=[],E=typeof a;if(s=s||n,"undefined"===E||"function"===E){for(i=!i.length&&a.length?["require","exports","module"]:i,l=0;l>>24)},t.prototype.writeInt16LE=function(t,e){this.writeUInt8(t,255&e),this.writeUInt8(t+1,255&e>>>8|(2147483648&e)>>>24)},t.prototype.writeInt16BE=function(t,e){this.writeUInt8(t+1,255&e),this.writeUInt8(t,255&e>>>8|(2147483648&e)>>>24)},t.prototype.writeInt32LE=function(t,e){this.writeUInt8(t,255&e),this.writeUInt8(t+1,255&e>>>8),this.writeUInt8(t+2,255&e>>>16),this.writeUInt8(t+3,255&e>>>24)},t.prototype.writeInt32BE=function(t,e){this.writeUInt8(t+3,255&e),this.writeUInt8(t+2,255&e>>>8),this.writeUInt8(t+1,255&e>>>16),this.writeUInt8(t,255&e>>>24)},t.prototype.writeUInt8=function(){throw new n.ApiError(14,"BufferCore implementations should implement writeUInt8.")},t.prototype.writeUInt16LE=function(t,e){this.writeUInt8(t,255&e),this.writeUInt8(t+1,255&e>>8)},t.prototype.writeUInt16BE=function(t,e){this.writeUInt8(t+1,255&e),this.writeUInt8(t,255&e>>8)},t.prototype.writeUInt32LE=function(t,e){this.writeInt32LE(t,0|e)},t.prototype.writeUInt32BE=function(t,e){this.writeInt32BE(t,0|e)},t.prototype.writeFloatLE=function(t,e){this.writeInt32LE(t,this.float2intbits(e))},t.prototype.writeFloatBE=function(t,e){this.writeInt32BE(t,this.float2intbits(e))},t.prototype.writeDoubleLE=function(t,e){var n=this.double2longbits(e);this.writeInt32LE(t,n[0]),this.writeInt32LE(t+4,n[1])},t.prototype.writeDoubleBE=function(t,e){var n=this.double2longbits(e);this.writeInt32BE(t+4,n[0]),this.writeInt32BE(t,n[1])},t.prototype.readInt8=function(t){var e=this.readUInt8(t);return 128&e?4294967168|e:e},t.prototype.readInt16LE=function(t){var e=this.readUInt16LE(t);return 32768&e?4294934528|e:e},t.prototype.readInt16BE=function(t){var e=this.readUInt16BE(t);return 32768&e?4294934528|e:e},t.prototype.readInt32LE=function(t){return 0|this.readUInt32LE(t)},t.prototype.readInt32BE=function(t){return 0|this.readUInt32BE(t)},t.prototype.readUInt8=function(){throw new n.ApiError(14,"BufferCore implementations should implement readUInt8.")},t.prototype.readUInt16LE=function(t){return this.readUInt8(t+1)<<8|this.readUInt8(t)},t.prototype.readUInt16BE=function(t){return this.readUInt8(t)<<8|this.readUInt8(t+1)},t.prototype.readUInt32LE=function(t){return(this.readUInt8(t+3)<<24|this.readUInt8(t+2)<<16|this.readUInt8(t+1)<<8|this.readUInt8(t))>>>0},t.prototype.readUInt32BE=function(t){return(this.readUInt8(t)<<24|this.readUInt8(t+1)<<16|this.readUInt8(t+2)<<8|this.readUInt8(t+3))>>>0},t.prototype.readFloatLE=function(t){return this.intbits2float(this.readInt32LE(t))},t.prototype.readFloatBE=function(t){return this.intbits2float(this.readInt32BE(t))},t.prototype.readDoubleLE=function(t){return this.longbits2double(this.readInt32LE(t+4),this.readInt32LE(t))},t.prototype.readDoubleBE=function(t){return this.longbits2double(this.readInt32BE(t),this.readInt32BE(t+4))},t.prototype.copy=function(){throw new n.ApiError(14,"BufferCore implementations should implement copy.")},t.prototype.fill=function(t,e,n){for(var r=e;n>r;r++)this.writeUInt8(r,t)},t.prototype.float2intbits=function(t){var e,n,r;return 0===t?0:t===Number.POSITIVE_INFINITY?o:t===Number.NEGATIVE_INFINITY?a:isNaN(t)?s:(r=0>t?1:0,t=Math.abs(t),1.1754942106924411e-38>=t&&t>=1.401298464324817e-45?(e=0,n=Math.round(t/Math.pow(2,-126)*Math.pow(2,23)),r<<31|e<<23|n):(e=Math.floor(Math.log(t)/Math.LN2),n=Math.round((t/Math.pow(2,e)-1)*Math.pow(2,23)),r<<31|e+127<<23|n))},t.prototype.double2longbits=function(t){var e,n,r,i;return 0===t?[0,0]:t===Number.POSITIVE_INFINITY?[0,2146435072]:t===Number.NEGATIVE_INFINITY?[0,-1048576]:isNaN(t)?[0,2146959360]:(i=0>t?1<<31:0,t=Math.abs(t),2.225073858507201e-308>=t&&t>=5e-324?(e=0,r=t/Math.pow(2,-1022)*Math.pow(2,52)):(e=Math.floor(Math.log(t)/Math.LN2),t>>31,s=(2139095040&t)>>>23,u=8388607&t;return e=0===s?Math.pow(-1,n)*u*Math.pow(2,-149):Math.pow(-1,n)*(1+u*Math.pow(2,-23))*Math.pow(2,s-127),(i>e||e>r)&&(e=0/0),e},t.prototype.longbits2double=function(t,e){var n=(2147483648&t)>>>31,r=(2146435072&t)>>>20,i=(1048575&t)*Math.pow(2,32)+e;return 0===r&&0===i?0:2047===r?0===i?1===n?Number.NEGATIVE_INFINITY:Number.POSITIVE_INFINITY:0/0:0===r?Math.pow(-1,n)*i*Math.pow(2,-1074):Math.pow(-1,n)*(1+i*Math.pow(2,-52))*Math.pow(2,r-1023)},t}();e.BufferCoreCommon=u});var f=this.__extends||function(t,e){function n(){this.constructor=t}for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r]);n.prototype=e.prototype,t.prototype=new n};u("core/buffer_core_array",["require","exports","./buffer_core"],function(t,e,n){var r=[4294967040,4294902015,4278255615,16777215],i=function(t){function e(e){t.call(this),this.length=e,this.buff=new Array(Math.ceil(e/4));for(var n=this.buff.length,r=0;n>r;r++)this.buff[r]=0}return f(e,t),e.isAvailable=function(){return!0},e.prototype.getLength=function(){return this.length},e.prototype.writeUInt8=function(t,e){e&=255;var n=t>>2,i=3&t;this.buff[n]=this.buff[n]&r[i],this.buff[n]=this.buff[n]|e<<(i<<3)},e.prototype.readUInt8=function(t){var e=t>>2,n=3&t;return 255&this.buff[e]>>(n<<3)},e.prototype.copy=function(t,n){for(var r=new e(n-t),i=t;n>i;i++)r.writeUInt8(i-t,this.readUInt8(i));return r},e}(n.BufferCoreCommon);e.BufferCoreArray=i});var f=this.__extends||function(t,e){function n(){this.constructor=t}for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r]);n.prototype=e.prototype,t.prototype=new n};u("core/buffer_core_arraybuffer",["require","exports","./buffer_core"],function(t,e,n){var r=function(t){function e(e){t.call(this),this.buff="number"==typeof e?new DataView(new ArrayBuffer(e)):e instanceof DataView?e:new DataView(e),this.length=this.buff.byteLength}return f(e,t),e.isAvailable=function(){return"undefined"!=typeof DataView},e.prototype.getLength=function(){return this.length},e.prototype.writeInt8=function(t,e){this.buff.setInt8(t,e)},e.prototype.writeInt16LE=function(t,e){this.buff.setInt16(t,e,!0)},e.prototype.writeInt16BE=function(t,e){this.buff.setInt16(t,e,!1)},e.prototype.writeInt32LE=function(t,e){this.buff.setInt32(t,e,!0)},e.prototype.writeInt32BE=function(t,e){this.buff.setInt32(t,e,!1)},e.prototype.writeUInt8=function(t,e){this.buff.setUint8(t,e)},e.prototype.writeUInt16LE=function(t,e){this.buff.setUint16(t,e,!0)},e.prototype.writeUInt16BE=function(t,e){this.buff.setUint16(t,e,!1)},e.prototype.writeUInt32LE=function(t,e){this.buff.setUint32(t,e,!0)},e.prototype.writeUInt32BE=function(t,e){this.buff.setUint32(t,e,!1)},e.prototype.writeFloatLE=function(t,e){this.buff.setFloat32(t,e,!0)},e.prototype.writeFloatBE=function(t,e){this.buff.setFloat32(t,e,!1)},e.prototype.writeDoubleLE=function(t,e){this.buff.setFloat64(t,e,!0)},e.prototype.writeDoubleBE=function(t,e){this.buff.setFloat64(t,e,!1)},e.prototype.readInt8=function(t){return this.buff.getInt8(t)},e.prototype.readInt16LE=function(t){return this.buff.getInt16(t,!0)},e.prototype.readInt16BE=function(t){return this.buff.getInt16(t,!1)},e.prototype.readInt32LE=function(t){return this.buff.getInt32(t,!0)},e.prototype.readInt32BE=function(t){return this.buff.getInt32(t,!1)},e.prototype.readUInt8=function(t){return this.buff.getUint8(t)},e.prototype.readUInt16LE=function(t){return this.buff.getUint16(t,!0)},e.prototype.readUInt16BE=function(t){return this.buff.getUint16(t,!1)},e.prototype.readUInt32LE=function(t){return this.buff.getUint32(t,!0)},e.prototype.readUInt32BE=function(t){return this.buff.getUint32(t,!1)},e.prototype.readFloatLE=function(t){return this.buff.getFloat32(t,!0)},e.prototype.readFloatBE=function(t){return this.buff.getFloat32(t,!1)},e.prototype.readDoubleLE=function(t){return this.buff.getFloat64(t,!0)},e.prototype.readDoubleBE=function(t){return this.buff.getFloat64(t,!1)},e.prototype.copy=function(t,n){var r,i=this.buff.buffer;if(ArrayBuffer.prototype.slice)r=i.slice(t,n);else{var o=n-t;r=new ArrayBuffer(o);var a=new Uint8Array(r),s=new Uint8Array(i);a.set(s.subarray(t,n))}return new e(r)},e.prototype.fill=function(t,e,n){t=255&t;var r,i=n-e,o=4*(0|i/4),a=t<<24|t<<16|t<<8|t;for(r=0;o>r;r+=4)this.writeInt32LE(r+e,a);for(r=o;i>r;r++)this.writeUInt8(r+e,t)},e.prototype.getDataView=function(){return this.buff},e}(n.BufferCoreCommon);e.BufferCoreArrayBuffer=r});var f=this.__extends||function(t,e){function n(){this.constructor=t}for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r]);n.prototype=e.prototype,t.prototype=new n};u("core/buffer_core_imagedata",["require","exports","./buffer_core"],function(t,e,n){var r=function(t){function e(n){t.call(this),this.length=n,this.buff=e.getCanvasPixelArray(n)}return f(e,t),e.getCanvasPixelArray=function(t){var n=e.imageDataFactory;return void 0===n&&(e.imageDataFactory=n=document.createElement("canvas").getContext("2d")),0===t&&(t=1),n.createImageData(Math.ceil(t/4),1).data},e.isAvailable=function(){return"undefined"!=typeof CanvasPixelArray},e.prototype.getLength=function(){return this.length},e.prototype.writeUInt8=function(t,e){this.buff[t]=e},e.prototype.readUInt8=function(t){return this.buff[t]},e.prototype.copy=function(t,n){for(var r=new e(n-t),i=t;n>i;i++)r.writeUInt8(i-t,this.buff[i]);return r},e}(n.BufferCoreCommon);e.BufferCoreImageData=r}),u("core/string_util",["require","exports"],function(t,e){function n(t){switch(t=function(){switch(typeof t){case"object":return""+t;case"string":return t;default:throw new Error("Invalid encoding argument specified")}}(),t=t.toLowerCase()){case"utf8":case"utf-8":return r;case"ascii":return i;case"binary":return a;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return u;case"hex":return f;case"base64":return s;case"binary_string":return c;case"binary_string_ie":return p;case"extended_ascii":return o;default:throw new Error("Unknown encoding: "+t)}}e.FindUtil=n;var r=function(){function t(){}return t.str2byte=function(t,e){for(var n=e.length,r=0,i=0,o=n,a=0;ri;){var s=t.charCodeAt(r++),u=t.charCodeAt(r);if(s>=55296&&56319>=s&&u>=56320&&57343>=u){if(i+3>=o)break;a++;var f=(1024|1023&s)<<10|1023&u;e.writeUInt8(240|f>>18,i++),e.writeUInt8(128|63&f>>12,i++),e.writeUInt8(128|63&f>>6,i++),e.writeUInt8(128|63&f,i++),r++}else if(128>s)e.writeUInt8(s,i++),a++;else if(2048>s){if(i+1>=o)break;a++,e.writeUInt8(192|s>>6,i++),e.writeUInt8(128|63&s,i++)}else if(65536>s){if(i+2>=o)break;a++,e.writeUInt8(224|s>>12,i++),e.writeUInt8(128|63&s>>6,i++),e.writeUInt8(128|63&s,i++)}}return i},t.byte2str=function(t){for(var e=[],n=0;nr)e.push(String.fromCharCode(r));else{if(192>r)throw new Error("Found incomplete part of character in string.");if(224>r)e.push(String.fromCharCode((31&r)<<6|63&t.readUInt8(n++)));else if(240>r)e.push(String.fromCharCode((15&r)<<12|(63&t.readUInt8(n++))<<6|63&t.readUInt8(n++)));else{if(!(248>r))throw new Error("Unable to represent UTF-8 string as UTF-16 JavaScript string.");var i=t.readUInt8(n+2);e.push(String.fromCharCode(55296|1023&((7&r)<<8|(63&t.readUInt8(n++))<<2|(63&t.readUInt8(n++))>>4))),e.push(String.fromCharCode(56320|((15&i)<<6|63&t.readUInt8(n++))))}}}return e.join("")},t.byteLength=function(t){var e=encodeURIComponent(t).match(/%[89ABab]/g);return t.length+(e?e.length:0)},t}();e.UTF8=r;var i=function(){function t(){}return t.str2byte=function(t,e){for(var n=t.length>e.length?e.length:t.length,r=0;n>r;r++)e.writeUInt8(t.charCodeAt(r)%256,r);return n},t.byte2str=function(t){for(var e=new Array(t.length),n=0;nn.length?n.length:e.length,i=0;r>i;i++){var o=e.charCodeAt(i);if(o>127){var a=t.extendedChars.indexOf(e.charAt(i));a>-1&&(o=a+128)}n.writeUInt8(o,i)}return r},t.byte2str=function(e){for(var n=new Array(e.length),r=0;r127?t.extendedChars[i-128]:String.fromCharCode(i)}return n.join("")},t.byteLength=function(t){return t.length},t.extendedChars=["Ç","ü","é","â","ä","à","å","ç","ê","ë","è","ï","î","ì","Ä","Å","É","æ","Æ","ô","ö","ò","û","ù","ÿ","Ö","Ü","ø","£","Ø","×","ƒ","á","í","ó","ú","ñ","Ñ","ª","º","¿","®","¬","½","¼","¡","«","»","_","_","_","¦","¦","Á","Â","À","©","¦","¦","+","+","¢","¥","+","+","-","-","+","-","+","ã","Ã","+","+","-","-","¦","-","+","¤","ð","Ð","Ê","Ë","È","i","Í","Î","Ï","+","+","_","_","¦","Ì","_","Ó","ß","Ô","Ò","õ","Õ","µ","þ","Þ","Ú","Û","Ù","ý","Ý","¯","´","­","±","_","¾","¶","§","÷","¸","°","¨","·","¹","³","²","_"," "],t}();e.ExtendedASCII=o;var a=function(){function t(){}return t.str2byte=function(t,e){for(var n=t.length>e.length?e.length:t.length,r=0;n>r;r++)e.writeUInt8(255&t.charCodeAt(r),r);return n},t.byte2str=function(t){for(var e=new Array(t.length),n=0;n>2,u=(3&i)<<4|o>>4,f=(15&o)<<2|a>>6,c=63&a;isNaN(o)?f=c=64:isNaN(a)&&(c=64),n=n+t.num2b64[s]+t.num2b64[u]+t.num2b64[f]+t.num2b64[c]}return n},t.str2byte=function(e,n){var r=n.length,i="",o=0;e=e.replace(/[^A-Za-z0-9\+\/\=\-\_]/g,"");for(var a=0;o>4,h=(15&u)<<4|f>>2,l=(3&f)<<6|c;if(n.writeUInt8(p,a++),a===r)break;if(64!==f&&(i+=n.writeUInt8(h,a++)),a===r)break;if(64!==c&&(i+=n.writeUInt8(l,a++)),a===r)break}return a},t.byteLength=function(t){return Math.floor(6*t.replace(/[^A-Za-z0-9\+\/\-\_]/g,"").length/8)},t.b64chars=["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9","+","/","="],t.num2b64=function(){for(var e=new Array(t.b64chars.length),n=0;ne.length&&(n=1===e.length%2?(e.length-1)/2:e.length/2);for(var r=0;n>r;r++)e.writeUInt16LE(t.charCodeAt(r),2*r);return 2*n},t.byte2str=function(t){if(0!==t.length%2)throw new Error("Invalid UCS2 byte array.");for(var e=new Array(t.length/2),n=0;n>1;n>e.length&&(n=e.length);for(var r=0;n>r;r++){var i=this.hex2num[t.charAt(r<<1)],o=this.hex2num[t.charAt((r<<1)+1)];e.writeUInt8(i<<4|o,r)}return n},t.byte2str=function(t){for(var e=t.length,n=new Array(e<<1),r=0,i=0;e>i;i++){var o=15&t.readUInt8(i),a=t.readUInt8(i)>>4;n[r++]=this.num2hex[a],n[r++]=this.num2hex[o]}return n.join("")},t.byteLength=function(t){return t.length>>1},t.HEXCHARS="0123456789abcdef",t.num2hex=function(){for(var e=new Array(t.HEXCHARS.length),n=0;nn.length&&(r=n.length);var i=0,o=0,a=o+r,s=e.charCodeAt(i++);0!==s&&(n.writeUInt8(255&s,0),o=1);for(var u=o;a>u;u+=2){var f=e.charCodeAt(i++);1===a-u&&n.writeUInt8(f>>8,u),a-u>=2&&n.writeUInt16BE(f,u)}return r},t.byte2str=function(t){var e=t.length;if(0===e)return"";for(var n=new Array((e>>1)+1),r=0,i=0;ie.length?e.length:t.length,r=0;n>r;r++)e.writeUInt8(t.charCodeAt(r)-32,r);return n},t.byte2str=function(t){for(var e=new Array(t.length),n=0;n>>0)throw new TypeError("Buffer size must be a uint32.");this.length=e,this.data=new u(e)}else if("undefined"!=typeof DataView&&e instanceof DataView)this.data=new i.BufferCoreArrayBuffer(e),this.length=e.byteLength;else if("undefined"!=typeof ArrayBuffer&&e instanceof ArrayBuffer)this.data=new i.BufferCoreArrayBuffer(e),this.length=e.byteLength;else if(e instanceof t){var c=e;this.data=new u(e.length),this.length=e.length,c.copy(this)}else if(Array.isArray(e)||null!=e&&"object"==typeof e&&"number"==typeof e[0]){for(this.data=new u(e.length),a=0;ae?this.writeInt8(e,t):this.writeUInt8(e,t)},t.prototype.get=function(t){return this.readUInt8(t)},t.prototype.write=function(e,n,r,i){if("undefined"==typeof n&&(n=0),"undefined"==typeof r&&(r=this.length),"undefined"==typeof i&&(i="utf8"),"string"==typeof n?(i=""+n,n=0,r=this.length):"string"==typeof r&&(i=""+r,r=this.length),n>=this.length)return 0;var o=a.FindUtil(i);return r=r+n>this.length?this.length-n:r,n+=this.offset,o.str2byte(e,0===n&&r===this.length?this:new t(this.data,n,r+n))},t.prototype.toString=function(e,n,r){if("undefined"==typeof e&&(e="utf8"),"undefined"==typeof n&&(n=0),"undefined"==typeof r&&(r=this.length),!(r>=n))throw new Error("Invalid start/end positions: "+n+" - "+r);if(n===r)return"";r>this.length&&(r=this.length);var i=a.FindUtil(e);return i.byte2str(0===n&&r===this.length?this:new t(this.data,n+this.offset,r+this.offset))},t.prototype.toJSON=function(){for(var t=this.length,e=new Array(t),n=0;t>n;n++)e[n]=this.readUInt8(n);return{type:"Buffer",data:e}},t.prototype.copy=function(t,e,n,r){if("undefined"==typeof e&&(e=0),"undefined"==typeof n&&(n=0),"undefined"==typeof r&&(r=this.length),e=0>e?0:e,n=0>n?0:n,n>r)throw new RangeError("sourceEnd < sourceStart");if(r===n)return 0;if(e>=t.length)throw new RangeError("targetStart out of bounds");if(n>=this.length)throw new RangeError("sourceStart out of bounds");if(r>this.length)throw new RangeError("sourceEnd out of bounds");for(var i=Math.min(r-n,t.length-e,this.length-n),o=0;i>o;o++)t.writeUInt8(this.readUInt8(n+o),e+o);return i},t.prototype.slice=function(e,n){if("undefined"==typeof e&&(e=0),"undefined"==typeof n&&(n=this.length),0>e&&(e+=this.length,0>e&&(e=0)),0>n&&(n+=this.length,0>n&&(n=0)),n>this.length&&(n=this.length),e>n&&(e=n),0>e||0>n||e>=this.length||n>this.length)throw new Error("Invalid slice indices.");return new t(this.data,e+this.offset,n+this.offset)},t.prototype.sliceCopy=function(e,n){if("undefined"==typeof e&&(e=0),"undefined"==typeof n&&(n=this.length),0>e&&(e+=this.length,0>e&&(e=0)),0>n&&(n+=this.length,0>n&&(n=0)),n>this.length&&(n=this.length),e>n&&(e=n),0>e||0>n||e>=this.length||n>this.length)throw new Error("Invalid slice indices.");return new t(this.data.copy(e+this.offset,n+this.offset))},t.prototype.fill=function(t,e,n){"undefined"==typeof e&&(e=0),"undefined"==typeof n&&(n=this.length);var r=typeof t;switch(r){case"string":t=255&t.charCodeAt(0);break;case"number":break;default:throw new Error("Invalid argument to fill.")}e+=this.offset,n+=this.offset,this.data.fill(t,e,n)},t.prototype.readUInt8=function(t,e){return"undefined"==typeof e&&(e=!1),t+=this.offset,this.data.readUInt8(t)},t.prototype.readUInt16LE=function(t,e){return"undefined"==typeof e&&(e=!1),t+=this.offset,this.data.readUInt16LE(t)},t.prototype.readUInt16BE=function(t,e){return"undefined"==typeof e&&(e=!1),t+=this.offset,this.data.readUInt16BE(t)},t.prototype.readUInt32LE=function(t,e){return"undefined"==typeof e&&(e=!1),t+=this.offset,this.data.readUInt32LE(t)},t.prototype.readUInt32BE=function(t,e){return"undefined"==typeof e&&(e=!1),t+=this.offset,this.data.readUInt32BE(t)},t.prototype.readInt8=function(t,e){return"undefined"==typeof e&&(e=!1),t+=this.offset,this.data.readInt8(t)},t.prototype.readInt16LE=function(t,e){return"undefined"==typeof e&&(e=!1),t+=this.offset,this.data.readInt16LE(t)},t.prototype.readInt16BE=function(t,e){return"undefined"==typeof e&&(e=!1),t+=this.offset,this.data.readInt16BE(t)},t.prototype.readInt32LE=function(t,e){return"undefined"==typeof e&&(e=!1),t+=this.offset,this.data.readInt32LE(t)},t.prototype.readInt32BE=function(t,e){return"undefined"==typeof e&&(e=!1),t+=this.offset,this.data.readInt32BE(t)},t.prototype.readFloatLE=function(t,e){return"undefined"==typeof e&&(e=!1),t+=this.offset,this.data.readFloatLE(t)},t.prototype.readFloatBE=function(t,e){return"undefined"==typeof e&&(e=!1),t+=this.offset,this.data.readFloatBE(t)},t.prototype.readDoubleLE=function(t,e){return"undefined"==typeof e&&(e=!1),t+=this.offset,this.data.readDoubleLE(t)},t.prototype.readDoubleBE=function(t,e){return"undefined"==typeof e&&(e=!1),t+=this.offset,this.data.readDoubleBE(t)},t.prototype.writeUInt8=function(t,e,n){"undefined"==typeof n&&(n=!1),e+=this.offset,this.data.writeUInt8(e,t)},t.prototype.writeUInt16LE=function(t,e,n){"undefined"==typeof n&&(n=!1),e+=this.offset,this.data.writeUInt16LE(e,t)},t.prototype.writeUInt16BE=function(t,e,n){"undefined"==typeof n&&(n=!1),e+=this.offset,this.data.writeUInt16BE(e,t)},t.prototype.writeUInt32LE=function(t,e,n){"undefined"==typeof n&&(n=!1),e+=this.offset,this.data.writeUInt32LE(e,t)},t.prototype.writeUInt32BE=function(t,e,n){"undefined"==typeof n&&(n=!1),e+=this.offset,this.data.writeUInt32BE(e,t)},t.prototype.writeInt8=function(t,e,n){"undefined"==typeof n&&(n=!1),e+=this.offset,this.data.writeInt8(e,t)},t.prototype.writeInt16LE=function(t,e,n){"undefined"==typeof n&&(n=!1),e+=this.offset,this.data.writeInt16LE(e,t)},t.prototype.writeInt16BE=function(t,e,n){"undefined"==typeof n&&(n=!1),e+=this.offset,this.data.writeInt16BE(e,t)},t.prototype.writeInt32LE=function(t,e,n){"undefined"==typeof n&&(n=!1),e+=this.offset,this.data.writeInt32LE(e,t)},t.prototype.writeInt32BE=function(t,e,n){"undefined"==typeof n&&(n=!1),e+=this.offset,this.data.writeInt32BE(e,t)},t.prototype.writeFloatLE=function(t,e,n){"undefined"==typeof n&&(n=!1),e+=this.offset,this.data.writeFloatLE(e,t)},t.prototype.writeFloatBE=function(t,e,n){"undefined"==typeof n&&(n=!1),e+=this.offset,this.data.writeFloatBE(e,t)},t.prototype.writeDoubleLE=function(t,e,n){"undefined"==typeof n&&(n=!1),e+=this.offset,this.data.writeDoubleLE(e,t)},t.prototype.writeDoubleBE=function(t,e,n){"undefined"==typeof n&&(n=!1),e+=this.offset,this.data.writeDoubleBE(e,t)},t.isEncoding=function(t){try{a.FindUtil(t)}catch(e){return!1}return!0},t.isBuffer=function(e){return e instanceof t},t.byteLength=function(t,e){"undefined"==typeof e&&(e="utf8");var n=a.FindUtil(e); +return n.byteLength(t)},t.concat=function(e,n){var r;if(0===e.length||0===n)return new t(0);if(1===e.length)return e[0];if(null==n){n=0;for(var i=0;ithis.maxListeners&&process.stdout.write("Warning: Event "+t+" has more than "+this.maxListeners+" listeners.\n"),this.emit("newListener",t,e),this},t.prototype.on=function(t,e){return this.addListener(t,e)},t.prototype.once=function(t,e){var n=!1,r=function(){this.removeListener(t,r),n||(n=!0,e.apply(this,arguments))};return this.addListener(t,r)},t.prototype._emitRemoveListener=function(t,e){var n;if(this._listeners.removeListener&&this._listeners.removeListener.length>0)for(n=0;n-1&&n.splice(r,1)}return this.emit("removeListener",t,e),this},t.prototype.removeAllListeners=function(t){var e,n,r;if("undefined"!=typeof t)e=this._listeners[t],this._listeners[t]=[],this._emitRemoveListener(t,e);else for(n=Object.keys(this._listeners),r=0;r0&&setTimeout(function(){i.emit("readable")},0):this.resume(),r},e.prototype._processArgs=function(t,e,n){return"string"==typeof e?new a(t,e,n):new a(t,null,e)},e.prototype._processEvents=function(){var t=0===this.buffer.length;this.drained!==t&&this.drained&&this.emit("readable"),this.flowing&&0!==this.buffer.length&&this.emit("data",this.read()),this.drained=0===this.buffer.length},e.prototype.emitEvent=function(t,e){this.emit(t,e.getData(this.encoding)),e.cb&&e.cb()},e.prototype.write=function(t,e,n){if(this.ended)throw new o(0,"Cannot write to an ended stream.");var r=this._processArgs(t,e,n);return this._push(r),this.flowing},e.prototype.end=function(t,e,n){if(this.ended)throw new o(0,"Stream is already closed.");var r=this._processArgs(t,e,n);this.ended=!0,this.endEvent=r,this._processEvents()},e.prototype.read=function(t){var e,n,r,o,s=[],u=[],f=0,c=0,p="number"!=typeof t;for(p&&(t=4294967295),c=0;cf;c++)n=this.buffer[c],s.push(n.getData()),n.cb&&u.push(n.cb),f+=n.size,e=n.cb;if(!p&&t>f)return null;if(this.buffer=this.buffer.slice(s.length),o=f>t?t:f,r=i.concat(s),f>t&&(e&&u.pop(),this._push(new a(r.slice(t),null,e))),u.length>0&&setTimeout(function(){var t;for(t=0;t0&&".."!==i[0])?i.pop():i.push(a))}if(!n&&i.length<2)switch(i.length){case 1:""===i[0]&&i.unshift(".");break;default:i.push(".")}return e=i.join(t.sep),n&&e.charAt(0)!==t.sep&&(e=t.sep+e),e},t.join=function(){for(var e=[],n=0;n1&&s.charAt(s.length-1)===t.sep)return s.substr(0,s.length-1);if(s.charAt(0)!==t.sep){"."!==s.charAt(0)||1!==s.length&&s.charAt(1)!==t.sep||(s=1===s.length?"":s.substr(2));var u=r.cwd();s=""!==s?this.normalize(u+("/"!==u?t.sep:"")+s):u}return s},t.relative=function(e,n){var r;e=t.resolve(e),n=t.resolve(n);var i=e.split(t.sep),o=n.split(t.sep);o.shift(),i.shift();var a=0,s=[];for(r=0;ri.length&&(a=i.length);var f="";for(r=0;a>r;r++)f+="../";return f+=s.join(t.sep),f.length>1&&f.charAt(f.length-1)===t.sep&&(f=f.substr(0,f.length-1)),f},t.dirname=function(e){e=t._removeDuplicateSeps(e);var n=e.charAt(0)===t.sep,r=e.split(t.sep);return""===r.pop()&&r.length>0&&r.pop(),r.length>1?r.join(t.sep):n?t.sep:"."},t.basename=function(e,n){if("undefined"==typeof n&&(n=""),""===e)return e;e=t.normalize(e);var r=e.split(t.sep),i=r[r.length-1];if(""===i&&r.length>1)return r[r.length-2];if(n.length>0){var o=i.substr(i.length-n.length);if(o===n)return i.substr(0,i.length-n.length)}return i},t.extname=function(e){e=t.normalize(e);var n=e.split(t.sep);if(e=n.pop(),""===e&&n.length>0&&(e=n.pop()),".."===e)return"";var r=e.lastIndexOf(".");return-1===r||0===r?"":e.substr(r)},t.isAbsolute=function(e){return e.length>0&&e.charAt(0)===t.sep},t._makeLong=function(t){return t},t._removeDuplicateSeps=function(t){return t=t.replace(this._replaceRegex,this.sep)},t.sep="/",t._replaceRegex=new RegExp("//+","g"),t.delimiter=":",t}();e.path=i}),u("core/node_fs",["require","exports","./api_error","./file_flag","./buffer","./node_path"],function(t,e,n,r,i,o){function a(t,e){if("function"!=typeof t)throw new h(9,"Callback must be a function.");switch("undefined"==typeof __numWaiting&&(__numWaiting=0),__numWaiting++,e){case 1:return function(e){setImmediate(function(){return __numWaiting--,t(e)})};case 2:return function(e,n){setImmediate(function(){return __numWaiting--,t(e,n)})};case 3:return function(e,n,r){setImmediate(function(){return __numWaiting--,t(e,n,r)})};default:throw new Error("Invalid invocation of wrapCb.")}}function s(t){if("function"!=typeof t.write)throw new h(3,"Invalid file descriptor.")}function u(t,e){switch(typeof t){case"number":return t;case"string":var n=parseInt(t,8);if(0/0!==n)return n;default:return e}}function f(t){if(t.indexOf("\x00")>=0)throw new h(9,"Path must be a string without null bytes.");if(""===t)throw new h(9,"Path must not be empty.");return y.resolve(t)}function c(t,e,n,r){switch(typeof t){case"object":return{encoding:"undefined"!=typeof t.encoding?t.encoding:e,flag:"undefined"!=typeof t.flag?t.flag:n,mode:u(t.mode,r)};case"string":return{encoding:t,flag:n,mode:r};default:return{encoding:e,flag:n,mode:r}}}function p(){}var h=n.ApiError;n.ErrorCode;var l=r.FileFlag,d=i.Buffer,y=o.path,g=function(){function t(){}return t._initialize=function(e){if(!e.constructor.isAvailable())throw new h(9,"Tried to instantiate BrowserFS with an unavailable file system.");return t.root=e},t._toUnixTimestamp=function(t){if("number"==typeof t)return t;if(t instanceof Date)return t.getTime()/1e3;throw new Error("Cannot parse time: "+t)},t.getRootFS=function(){return t.root?t.root:null},t.rename=function(e,n,r){"undefined"==typeof r&&(r=p);var i=a(r,1);try{t.root.rename(f(e),f(n),i)}catch(o){i(o)}},t.renameSync=function(e,n){t.root.renameSync(f(e),f(n))},t.exists=function(e,n){"undefined"==typeof n&&(n=p);var r=a(n,1);try{return t.root.exists(f(e),r)}catch(i){return r(!1)}},t.existsSync=function(e){try{return t.root.existsSync(f(e))}catch(n){return!1}},t.stat=function(e,n){"undefined"==typeof n&&(n=p);var r=a(n,2);try{return t.root.stat(f(e),!1,r)}catch(i){return r(i,null)}},t.statSync=function(e){return t.root.statSync(f(e),!1)},t.lstat=function(e,n){"undefined"==typeof n&&(n=p);var r=a(n,2);try{return t.root.stat(f(e),!0,r)}catch(i){return r(i,null)}},t.lstatSync=function(e){return t.root.statSync(f(e),!0)},t.truncate=function(e,n,r){"undefined"==typeof n&&(n=0),"undefined"==typeof r&&(r=p);var i=0;"function"==typeof n?r=n:"number"==typeof n&&(i=n);var o=a(r,1);try{if(0>i)throw new h(9);return t.root.truncate(f(e),i,o)}catch(s){return o(s)}},t.truncateSync=function(e,n){if("undefined"==typeof n&&(n=0),0>n)throw new h(9);return t.root.truncateSync(f(e),n)},t.unlink=function(e,n){"undefined"==typeof n&&(n=p);var r=a(n,1);try{return t.root.unlink(f(e),r)}catch(i){return r(i)}},t.unlinkSync=function(e){return t.root.unlinkSync(f(e))},t.open=function(e,n,r,i){"undefined"==typeof i&&(i=p);var o=u(r,420);i="function"==typeof r?r:i;var s=a(i,2);try{return t.root.open(f(e),l.getFileFlag(n),o,s)}catch(c){return s(c,null)}},t.openSync=function(e,n,r){return"undefined"==typeof r&&(r=420),t.root.openSync(f(e),l.getFileFlag(n),r)},t.readFile=function(e,n,r){"undefined"==typeof n&&(n={}),"undefined"==typeof r&&(r=p);var i=c(n,null,"r",null);r="function"==typeof n?n:r;var o=a(r,2);try{var s=l.getFileFlag(i.flag);return s.isReadable()?t.root.readFile(f(e),i.encoding,s,o):o(new h(9,"Flag passed to readFile must allow for reading."))}catch(u){return o(u,null)}},t.readFileSync=function(e,n){"undefined"==typeof n&&(n={});var r=c(n,null,"r",null),i=l.getFileFlag(r.flag);if(!i.isReadable())throw new h(9,"Flag passed to readFile must allow for reading.");return t.root.readFileSync(f(e),r.encoding,i)},t.writeFile=function(e,n,r,i){"undefined"==typeof r&&(r={}),"undefined"==typeof i&&(i=p);var o=c(r,"utf8","w",420);i="function"==typeof r?r:i;var s=a(i,1);try{var u=l.getFileFlag(o.flag);return u.isWriteable()?t.root.writeFile(f(e),n,o.encoding,u,o.mode,s):s(new h(9,"Flag passed to writeFile must allow for writing."))}catch(d){return s(d)}},t.writeFileSync=function(e,n,r){var i=c(r,"utf8","w",420),o=l.getFileFlag(i.flag);if(!o.isWriteable())throw new h(9,"Flag passed to writeFile must allow for writing.");return t.root.writeFileSync(f(e),n,i.encoding,o,i.mode)},t.appendFile=function(e,n,r,i){"undefined"==typeof i&&(i=p);var o=c(r,"utf8","a",420);i="function"==typeof r?r:i;var s=a(i,1);try{var u=l.getFileFlag(o.flag);if(!u.isAppendable())return s(new h(9,"Flag passed to appendFile must allow for appending."));t.root.appendFile(f(e),n,o.encoding,u,o.mode,s)}catch(d){s(d)}},t.appendFileSync=function(e,n,r){var i=c(r,"utf8","a",420),o=l.getFileFlag(i.flag);if(!o.isAppendable())throw new h(9,"Flag passed to appendFile must allow for appending.");return t.root.appendFileSync(f(e),n,i.encoding,o,i.mode)},t.fstat=function(t,e){"undefined"==typeof e&&(e=p);var n=a(e,2);try{s(t),t.stat(n)}catch(r){n(r)}},t.fstatSync=function(t){return s(t),t.statSync()},t.close=function(t,e){"undefined"==typeof e&&(e=p);var n=a(e,1);try{s(t),t.close(n)}catch(r){n(r)}},t.closeSync=function(t){return s(t),t.closeSync()},t.ftruncate=function(t,e,n){"undefined"==typeof n&&(n=p);var r="number"==typeof e?e:0;n="function"==typeof e?e:n;var i=a(n,1);try{if(s(t),0>r)throw new h(9);t.truncate(r,i)}catch(o){i(o)}},t.ftruncateSync=function(t,e){return"undefined"==typeof e&&(e=0),s(t),t.truncateSync(e)},t.fsync=function(t,e){"undefined"==typeof e&&(e=p);var n=a(e,1);try{s(t),t.sync(n)}catch(r){n(r)}},t.fsyncSync=function(t){return s(t),t.syncSync()},t.fdatasync=function(t,e){"undefined"==typeof e&&(e=p);var n=a(e,1);try{s(t),t.datasync(n)}catch(r){n(r)}},t.fdatasyncSync=function(t){s(t),t.datasyncSync()},t.write=function(t,e,n,r,i,o){"undefined"==typeof o&&(o=p);var u,f,c,l=null;if("string"==typeof e){var y="utf8";switch(typeof n){case"function":o=n;break;case"number":l=n,y="string"==typeof r?r:"utf8",o="function"==typeof i?i:o;break;default:return o="function"==typeof r?r:"function"==typeof i?i:o,o(new h(9,"Invalid arguments."))}u=new d(e,y),f=0,c=u.length}else u=e,f=n,c=r,l="number"==typeof i?i:null,o="function"==typeof i?i:o;var g=a(o,3);try{s(t),null==l&&(l=t.getPos()),t.write(u,f,c,l,g)}catch(w){g(w)}},t.writeSync=function(t,e,n,r,i){var o,a,u,f=0;if("string"==typeof e){u="number"==typeof n?n:null;var c="string"==typeof r?r:"utf8";f=0,o=new d(e,c),a=o.length}else o=e,f=n,a=r,u="number"==typeof i?i:null;return s(t),null==u&&(u=t.getPos()),t.writeSync(o,f,a,u)},t.read=function(t,e,n,r,i,o){"undefined"==typeof o&&(o=p);var u,f,c,h,l;if("number"==typeof e){c=e,u=n;var y=r;o="function"==typeof i?i:o,f=0,h=new d(c),l=a(function(t,e,n){return t?o(t):(o(t,n.toString(y),e),void 0)},3)}else h=e,f=n,c=r,u=i,l=a(o,3);try{s(t),null==u&&(u=t.getPos()),t.read(h,f,c,u,l)}catch(g){l(g)}},t.readSync=function(t,e,n,r,i){var o,a,u,f,c=!1;if("number"==typeof e){u=e,f=n;var p=r;a=0,o=new d(u),c=!0}else o=e,a=n,u=r,f=i;s(t),null==f&&(f=t.getPos());var h=t.readSync(o,a,u,f);return c?[o.toString(p),h]:h},t.fchown=function(t,e,n,r){"undefined"==typeof r&&(r=p);var i=a(r,1);try{s(t),t.chown(e,n,i)}catch(o){i(o)}},t.fchownSync=function(t,e,n){return s(t),t.chownSync(e,n)},t.fchmod=function(t,e,n){"undefined"==typeof n&&(n=p);var r=a(n,1);try{e="string"==typeof e?parseInt(e,8):e,s(t),t.chmod(e,r)}catch(i){r(i)}},t.fchmodSync=function(t,e){return e="string"==typeof e?parseInt(e,8):e,s(t),t.chmodSync(e)},t.futimes=function(t,e,n,r){"undefined"==typeof r&&(r=p);var i=a(r,1);try{s(t),"number"==typeof e&&(e=new Date(1e3*e)),"number"==typeof n&&(n=new Date(1e3*n)),t.utimes(e,n,i)}catch(o){i(o)}},t.futimesSync=function(t,e,n){return s(t),"number"==typeof e&&(e=new Date(1e3*e)),"number"==typeof n&&(n=new Date(1e3*n)),t.utimesSync(e,n)},t.rmdir=function(e,n){"undefined"==typeof n&&(n=p);var r=a(n,1);try{e=f(e),t.root.rmdir(e,r)}catch(i){r(i)}},t.rmdirSync=function(e){return e=f(e),t.root.rmdirSync(e)},t.mkdir=function(e,n,r){"undefined"==typeof r&&(r=p),"function"==typeof n&&(r=n,n=511);var i=a(r,1);try{e=f(e),t.root.mkdir(e,n,i)}catch(o){i(o)}},t.mkdirSync=function(e,n){return"undefined"==typeof n&&(n=511),n="string"==typeof n?parseInt(n,8):n,e=f(e),t.root.mkdirSync(e,n)},t.readdir=function(e,n){"undefined"==typeof n&&(n=p);var r=a(n,2);try{e=f(e),t.root.readdir(e,r)}catch(i){r(i)}},t.readdirSync=function(e){return e=f(e),t.root.readdirSync(e)},t.link=function(e,n,r){"undefined"==typeof r&&(r=p);var i=a(r,1);try{e=f(e),n=f(n),t.root.link(e,n,i)}catch(o){i(o)}},t.linkSync=function(e,n){return e=f(e),n=f(n),t.root.linkSync(e,n)},t.symlink=function(e,n,r,i){"undefined"==typeof i&&(i=p);var o="string"==typeof r?r:"file";i="function"==typeof r?r:i;var s=a(i,1);try{if("file"!==o&&"dir"!==o)return s(new h(9,"Invalid type: "+o));e=f(e),n=f(n),t.root.symlink(e,n,o,s)}catch(u){s(u)}},t.symlinkSync=function(e,n,r){if(null==r)r="file";else if("file"!==r&&"dir"!==r)throw new h(9,"Invalid type: "+r);return e=f(e),n=f(n),t.root.symlinkSync(e,n,r)},t.readlink=function(e,n){"undefined"==typeof n&&(n=p);var r=a(n,2);try{e=f(e),t.root.readlink(e,r)}catch(i){r(i)}},t.readlinkSync=function(e){return e=f(e),t.root.readlinkSync(e)},t.chown=function(e,n,r,i){"undefined"==typeof i&&(i=p);var o=a(i,1);try{e=f(e),t.root.chown(e,!1,n,r,o)}catch(s){o(s)}},t.chownSync=function(e,n,r){e=f(e),t.root.chownSync(e,!1,n,r)},t.lchown=function(e,n,r,i){"undefined"==typeof i&&(i=p);var o=a(i,1);try{e=f(e),t.root.chown(e,!0,n,r,o)}catch(s){o(s)}},t.lchownSync=function(e,n,r){return e=f(e),t.root.chownSync(e,!0,n,r)},t.chmod=function(e,n,r){"undefined"==typeof r&&(r=p);var i=a(r,1);try{n="string"==typeof n?parseInt(n,8):n,e=f(e),t.root.chmod(e,!1,n,i)}catch(o){i(o)}},t.chmodSync=function(e,n){return n="string"==typeof n?parseInt(n,8):n,e=f(e),t.root.chmodSync(e,!1,n)},t.lchmod=function(e,n,r){"undefined"==typeof r&&(r=p);var i=a(r,1);try{n="string"==typeof n?parseInt(n,8):n,e=f(e),t.root.chmod(e,!0,n,i)}catch(o){i(o)}},t.lchmodSync=function(e,n){return e=f(e),n="string"==typeof n?parseInt(n,8):n,t.root.chmodSync(e,!0,n)},t.utimes=function(e,n,r,i){"undefined"==typeof i&&(i=p);var o=a(i,1);try{e=f(e),"number"==typeof n&&(n=new Date(1e3*n)),"number"==typeof r&&(r=new Date(1e3*r)),t.root.utimes(e,n,r,o)}catch(s){o(s)}},t.utimesSync=function(e,n,r){return e=f(e),"number"==typeof n&&(n=new Date(1e3*n)),"number"==typeof r&&(r=new Date(1e3*r)),t.root.utimesSync(e,n,r)},t.realpath=function(e,n,r){"undefined"==typeof r&&(r=p);var i="object"==typeof n?n:{};r="function"==typeof n?n:p;var o=a(r,2);try{e=f(e),t.root.realpath(e,i,o)}catch(s){o(s)}},t.realpathSync=function(e,n){return"undefined"==typeof n&&(n={}),e=f(e),t.root.realpathSync(e,n)},t.root=null,t}();e.fs=g}),u("core/browserfs",["require","exports","./buffer","./node_fs","./node_path","./node_process"],function(t,e,n,r,i,o){function a(t){t.Buffer=n.Buffer,t.process=o.process;var r=null!=t.require?t.require:null;t.require=function(t){var n=e.BFSRequire(t);return null==n?r.apply(null,Array.prototype.slice.call(arguments,0)):n}}function s(t,n){e.FileSystem[t]=n}function u(t){switch(t){case"fs":return r.fs;case"path":return i.path;case"buffer":return n;case"process":return o.process;default:return e.FileSystem[t]}}function f(t){return r.fs._initialize(t)}e.install=a,e.FileSystem={},e.registerFileSystem=s,e.BFSRequire=u,e.initialize=f}),u("generic/emscripten_fs",["require","exports","../core/browserfs","../core/node_fs","../core/buffer","../core/buffer_core_arraybuffer"],function(t,e,n,r,i,o){var a=i.Buffer,s=o.BufferCoreArrayBuffer,u=r.fs,f=function(){function t(t){this.fs=t}return t.prototype.open=function(t){var e=this.fs.realPath(t.node);try{FS.isFile(t.node.mode)&&(t.nfd=u.openSync(e,this.fs.flagsToPermissionString(t.flags)))}catch(n){if(!n.code)throw n;throw new FS.ErrnoError(ERRNO_CODES[n.code])}},t.prototype.close=function(t){try{FS.isFile(t.node.mode)&&t.nfd&&u.closeSync(t.nfd)}catch(e){if(!e.code)throw e;throw new FS.ErrnoError(ERRNO_CODES[e.code])}},t.prototype.read=function(t,e,n,r,i){var o,f=new s(e.buffer),c=new a(f,e.byteOffset+n,e.byteOffset+n+r);try{o=u.readSync(t.nfd,c,0,r,i)}catch(p){throw new FS.ErrnoError(ERRNO_CODES[p.code])}return o},t.prototype.write=function(t,e,n,r,i){var o,f=new s(e.buffer),c=new a(f,e.byteOffset+n,e.byteOffset+n+r);try{o=u.writeSync(t.nfd,c,0,r,i)}catch(p){throw new FS.ErrnoError(ERRNO_CODES[p.code])}return o},t.prototype.llseek=function(t,e,n){var r=e;if(1===n)r+=t.position;else if(2===n&&FS.isFile(t.node.mode))try{var i=u.fstatSync(t.nfd);r+=i.size}catch(o){throw new FS.ErrnoError(ERRNO_CODES[o.code])}if(0>r)throw new FS.ErrnoError(ERRNO_CODES.EINVAL);return t.position=r,r},t}(),c=function(){function t(t){this.fs=t}return t.prototype.getattr=function(t){var e,n=this.fs.realPath(t);try{e=u.lstatSync(n)}catch(r){if(!r.code)throw r;throw new FS.ErrnoError(ERRNO_CODES[r.code])}return{dev:e.dev,ino:e.ino,mode:e.mode,nlink:e.nlink,uid:e.uid,gid:e.gid,rdev:e.rdev,size:e.size,atime:e.atime,mtime:e.mtime,ctime:e.ctime,blksize:e.blksize,blocks:e.blocks}},t.prototype.setattr=function(t,e){var n=this.fs.realPath(t);try{if(void 0!==e.mode&&(u.chmodSync(n,e.mode),t.mode=e.mode),void 0!==e.timestamp){var r=new Date(e.timestamp);u.utimesSync(n,r,r)}void 0!==e.size&&u.truncateSync(n,e.size)}catch(i){if(!i.code)throw i;if("ENOTSUP"===i.code)return;throw new FS.ErrnoError(ERRNO_CODES[i.code])}},t.prototype.lookup=function(t,e){var n=PATH.join2(this.fs.realPath(t),e),r=this.fs.getMode(n);return this.fs.createNode(t,e,r)},t.prototype.mknod=function(t,e,n,r){var i=this.fs.createNode(t,e,n,r),o=this.fs.realPath(i);try{FS.isDir(i.mode)?u.mkdirSync(o,i.mode):u.writeFileSync(o,"",{mode:i.mode})}catch(a){if(!a.code)throw a;throw new FS.ErrnoError(ERRNO_CODES[a.code])}return i},t.prototype.rename=function(t,e,n){var r=this.fs.realPath(t),i=PATH.join2(this.fs.realPath(e),n);try{u.renameSync(r,i)}catch(o){if(!o.code)throw o;throw new FS.ErrnoError(ERRNO_CODES[o.code])}},t.prototype.unlink=function(t,e){var n=PATH.join2(this.fs.realPath(t),e);try{u.unlinkSync(n)}catch(r){if(!r.code)throw r;throw new FS.ErrnoError(ERRNO_CODES[r.code])}},t.prototype.rmdir=function(t,e){var n=PATH.join2(this.fs.realPath(t),e);try{u.rmdirSync(n)}catch(r){if(!r.code)throw r;throw new FS.ErrnoError(ERRNO_CODES[r.code])}},t.prototype.readdir=function(t){var e=this.fs.realPath(t);try{return u.readdirSync(e)}catch(n){if(!n.code)throw n;throw new FS.ErrnoError(ERRNO_CODES[n.code])}},t.prototype.symlink=function(t,e,n){var r=PATH.join2(this.fs.realPath(t),e);try{u.symlinkSync(n,r)}catch(i){if(!i.code)throw i;throw new FS.ErrnoError(ERRNO_CODES[i.code])}},t.prototype.readlink=function(t){var e=this.fs.realPath(t);try{return u.readlinkSync(e)}catch(n){if(!n.code)throw n;throw new FS.ErrnoError(ERRNO_CODES[n.code])}},t}(),p=function(){function t(){if(this.flagsToPermissionStringMap={0:"r",1:"r+",2:"r+",64:"r",65:"r+",66:"r+",129:"rx+",193:"rx+",514:"w+",577:"w",578:"w+",705:"wx",706:"wx+",1024:"a",1025:"a",1026:"a+",1089:"a",1090:"a+",1153:"ax",1154:"ax+",1217:"ax",1218:"ax+",4096:"rs",4098:"rs+"},this.node_ops=new c(this),this.stream_ops=new f(this),"undefined"==typeof n)throw new Error("BrowserFS is not loaded. Please load it before this library.")}return t.prototype.mount=function(t){return this.createNode(null,"/",this.getMode(t.opts.root),0)},t.prototype.createNode=function(t,e,n){if(!FS.isDir(n)&&!FS.isFile(n)&&!FS.isLink(n))throw new FS.ErrnoError(ERRNO_CODES.EINVAL);var r=FS.createNode(t,e,n);return r.node_ops=this.node_ops,r.stream_ops=this.stream_ops,r},t.prototype.getMode=function(t){var e;try{e=u.lstatSync(t)}catch(n){if(!n.code)throw n;throw new FS.ErrnoError(ERRNO_CODES[n.code])}return e.mode},t.prototype.realPath=function(t){for(var e=[];t.parent!==t;)e.push(t.name),t=t.parent;return e.push(t.mount.opts.root),e.reverse(),PATH.join.apply(null,e)},t.prototype.flagsToPermissionString=function(t){return t in this.flagsToPermissionStringMap?this.flagsToPermissionStringMap[t]:t},t}();e.BFSEmscriptenFS=p,n.EmscriptenFS=p});var f=this.__extends||function(t,e){function n(){this.constructor=t}for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r]);n.prototype=e.prototype,t.prototype=new n};u("core/file_system",["require","exports","./api_error","./file_flag","./node_path","./buffer"],function(t,e,n,r,i,o){var a=n.ApiError;n.ErrorCode;var s=i.path,u=o.Buffer;r.ActionType;var c=function(){function t(){}return t.prototype.supportsLinks=function(){return!1},t.prototype.diskSpace=function(t,e){e(0,0)},t.prototype.openFile=function(){throw new a(14)},t.prototype.createFile=function(){throw new a(14)},t.prototype.open=function(t,e,n,r){var i=this,o=function(o,u){if(o)switch(e.pathNotExistsAction()){case 3:return i.stat(s.dirname(t),!1,function(o,u){o?r(o):u.isDirectory()?i.createFile(t,e,n,r):r(new a(7,s.dirname(t)+" is not a directory."))});case 1:return r(new a(1,""+t+" doesn't exist."));default:return r(new a(9,"Invalid FileFlag object."))}else{if(u.isDirectory())return r(new a(8,t+" is a directory."));switch(e.pathExistsAction()){case 1:return r(new a(6,t+" already exists."));case 2:return i.openFile(t,e,function(t,e){t?r(t):e.truncate(0,function(){e.sync(function(){r(null,e)})})});case 0:return i.openFile(t,e,r);default:return r(new a(9,"Invalid FileFlag object."))}}};this.stat(t,!1,o)},t.prototype.rename=function(t,e,n){n(new a(14))},t.prototype.renameSync=function(){throw new a(14)},t.prototype.stat=function(t,e,n){n(new a(14))},t.prototype.statSync=function(){throw new a(14)},t.prototype.openFileSync=function(){throw new a(14)},t.prototype.createFileSync=function(){throw new a(14)},t.prototype.openSync=function(t,e,n){var r;try{r=this.statSync(t,!1)}catch(i){switch(e.pathNotExistsAction()){case 3:var o=this.statSync(s.dirname(t),!1);if(!o.isDirectory())throw new a(7,s.dirname(t)+" is not a directory.");return this.createFileSync(t,e,n);case 1:throw new a(1,""+t+" doesn't exist.");default:throw new a(9,"Invalid FileFlag object.")}}if(r.isDirectory())throw new a(8,t+" is a directory.");switch(e.pathExistsAction()){case 1:throw new a(6,t+" already exists.");case 2:return this.unlinkSync(t),this.createFileSync(t,e,r.mode);case 0:return this.openFileSync(t,e);default:throw new a(9,"Invalid FileFlag object.")}},t.prototype.unlink=function(t,e){e(new a(14))},t.prototype.unlinkSync=function(){throw new a(14)},t.prototype.rmdir=function(t,e){e(new a(14))},t.prototype.rmdirSync=function(){throw new a(14)},t.prototype.mkdir=function(t,e,n){n(new a(14))},t.prototype.mkdirSync=function(){throw new a(14)},t.prototype.readdir=function(t,e){e(new a(14))},t.prototype.readdirSync=function(){throw new a(14)},t.prototype.exists=function(t,e){this.stat(t,null,function(t){e(null==t)})},t.prototype.existsSync=function(t){try{return this.statSync(t,!0),!0}catch(e){return!1}},t.prototype.realpath=function(t,e,n){if(this.supportsLinks())for(var r=t.split(s.sep),i=0;ithis._buffer.length){var e=new u(t-this._buffer.length);return e.fill(0),this.writeSync(e,0,e.length,this._buffer.length),this._flag.isSynchronous()&&s.getRootFS().supportsSynch()&&this.syncSync(),void 0}this._stat.size=t;var n=new u(t);this._buffer.copy(n,0,0,t),this._buffer=n,this._flag.isSynchronous()&&s.getRootFS().supportsSynch()&&this.syncSync()},e.prototype.write=function(t,e,n,r,i){try{i(null,this.writeSync(t,e,n,r),t)}catch(o){i(o)}},e.prototype.writeSync=function(t,e,n,r){if(null==r&&(r=this.getPos()),!this._flag.isWriteable())throw new a(0,"File not opened with a writeable mode.");var i=r+n;if(i>this._stat.size&&(this._stat.size=i,i>this._buffer.length)){var o=new u(i);this._buffer.copy(o),this._buffer=o}var s=t.copy(this._buffer,r,e,e+n);return this._stat.mtime=new Date,this._flag.isSynchronous()?(this.syncSync(),s):(this.setPos(r+s),s)},e.prototype.read=function(t,e,n,r,i){try{i(null,this.readSync(t,e,n,r),t)}catch(o){i(o)}},e.prototype.readSync=function(t,e,n,r){if(!this._flag.isReadable())throw new a(0,"File not opened with a readable mode.");null==r&&(r=this.getPos());var i=r+n;i>this._stat.size&&(n=this._stat.size-r);var o=this._buffer.copy(t,e,r,r+n);return this._stat.atime=new Date,this._pos=r+n,o},e.prototype.chmod=function(t,e){try{this.chmodSync(t),e()}catch(n){e(n)}},e.prototype.chmodSync=function(t){if(!this._fs.supportsProps())throw new a(14);this._stat.chmod(t),this.syncSync()},e}(n.BaseFile);e.PreloadFile=c;var p=function(t){function e(e,n,r,i,o){t.call(this,e,n,r,i,o)}return f(e,t),e.prototype.sync=function(t){t()},e.prototype.syncSync=function(){},e.prototype.close=function(t){t()},e.prototype.closeSync=function(){},e}(c);e.NoSyncFile=p});var f=this.__extends||function(t,e){function n(){this.constructor=t}for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r]);n.prototype=e.prototype,t.prototype=new n};u("generic/key_value_filesystem",["require","exports","../core/file_system","../core/api_error","../core/node_fs_stats","../core/node_path","../generic/inode","../core/buffer","../generic/preload_file"],function(t,e,n,r,i,o,a,s,u){function c(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(t){var e=0|16*Math.random(),n="x"==t?e:8|3&e;return n.toString(16)})}function p(t,e){return t?(e(t),!1):!0}function h(t,e,n){return t?(e.abort(function(){n(t)}),!1):!0}var l="/",d=o.path,y=r.ApiError,g=s.Buffer,w=function(){function t(t){this.store=t,this.originalData={},this.modifiedKeys=[]}return t.prototype.stashOldValue=function(t,e){this.originalData.hasOwnProperty(t)||(this.originalData[t]=e)},t.prototype.markModified=function(t){-1===this.modifiedKeys.indexOf(t)&&(this.modifiedKeys.push(t),this.originalData.hasOwnProperty(t)||(this.originalData[t]=this.store.get(t)))},t.prototype.get=function(t){var e=this.store.get(t);return this.stashOldValue(t,e),e},t.prototype.put=function(t,e,n){return this.markModified(t),this.store.put(t,e,n)},t.prototype.delete=function(t){this.markModified(t),this.store.delete(t)},t.prototype.commit=function(){},t.prototype.abort=function(){var t,e,n;for(t=0;tr;)try{return n=c(),t.put(n,e,!1),n}catch(i){}throw new y(2,"Unable to commit data to key-value store.")},e.prototype.commitNewFile=function(t,e,n,r,i){var o=d.dirname(e),s=d.basename(e),u=this.findINode(t,o),f=this.getDirListing(t,o,u),c=(new Date).getTime();if("/"===e)throw y.EEXIST(e);if(f[s])throw y.EEXIST(e);try{var p=this.addNewNode(t,i),h=new a(p,i.length,r|n,c,c,c),l=this.addNewNode(t,h.toBuffer());f[s]=l,t.put(u.id,new g(JSON.stringify(f)),!0)}catch(w){throw t.abort(),w}return t.commit(),h},e.prototype.empty=function(){this.store.clear(),this.makeRootDirectory()},e.prototype.renameSync=function(t,e){var n=this.store.beginTransaction("readwrite"),r=d.dirname(t),i=d.basename(t),o=d.dirname(e),a=d.basename(e),s=this.findINode(n,r),u=this.getDirListing(n,r,s);if(!u[i])throw y.ENOENT(t);var f=u[i];if(delete u[i],0===(o+"/").indexOf(t+"/"))throw new y(5,r);var c,p;if(o===r?(c=s,p=u):(c=this.findINode(n,o),p=this.getDirListing(n,o,c)),p[a]){var h=this.getINode(n,e,p[a]);if(!h.isFile())throw y.EPERM(e);try{n.delete(h.id),n.delete(p[a])}catch(l){throw n.abort(),l}}p[a]=f;try{n.put(s.id,new g(JSON.stringify(u)),!0),n.put(c.id,new g(JSON.stringify(p)),!0)}catch(l){throw n.abort(),l}n.commit()},e.prototype.statSync=function(t){return this.findINode(this.store.beginTransaction("readonly"),t).toStats()},e.prototype.createFileSync=function(t,e,n){var r=this.store.beginTransaction("readwrite"),i=new g(0),o=this.commitNewFile(r,t,32768,n,i);return new m(this,t,e,o.toStats(),i)},e.prototype.openFileSync=function(t,e){var n=this.store.beginTransaction("readonly"),r=this.findINode(n,t),i=n.get(r.id);if(void 0===i)throw y.ENOENT(t);return new m(this,t,e,r.toStats(),i)},e.prototype.removeEntry=function(t,e){var n=this.store.beginTransaction("readwrite"),r=d.dirname(t),i=this.findINode(n,r),o=this.getDirListing(n,r,i),a=d.basename(t);if(!o[a])throw y.ENOENT(t);var s=o[a];delete o[a];var u=this.getINode(n,t,s);if(!e&&u.isDirectory())throw y.EISDIR(t);if(e&&!u.isDirectory())throw y.ENOTDIR(t);try{n.delete(u.id),n.delete(s),n.put(i.id,new g(JSON.stringify(o)),!0)}catch(f){throw n.abort(),f}n.commit()},e.prototype.unlinkSync=function(t){this.removeEntry(t,!1)},e.prototype.rmdirSync=function(t){this.removeEntry(t,!0)},e.prototype.mkdirSync=function(t,e){var n=this.store.beginTransaction("readwrite"),r=new g("{}");this.commitNewFile(n,t,16384,e,r)},e.prototype.readdirSync=function(t){var e=this.store.beginTransaction("readonly");return Object.keys(this.getDirListing(e,t,this.findINode(e,t)))},e.prototype._syncSync=function(t,e,n){var r=this.store.beginTransaction("readwrite"),i=this._findINode(r,d.dirname(t),d.basename(t)),o=this.getINode(r,t,i),a=o.update(n);try{r.put(o.id,e,!0),a&&r.put(i,o.toBuffer(),!0)}catch(s){throw r.abort(),s}r.commit()},e}(n.SynchronousFileSystem);e.SyncKeyValueFileSystem=v;var b=function(t){function e(e,n,r,i,o){t.call(this,e,n,r,i,o)}return f(e,t),e.prototype.sync=function(t){this._fs._sync(this._path,this._buffer,this._stat,t)},e.prototype.close=function(t){this.sync(t)},e}(u.PreloadFile);e.AsyncKeyValueFile=b;var E=function(t){function e(){t.apply(this,arguments)}return f(e,t),e.prototype.init=function(t,e){this.store=t,this.makeRootDirectory(e)},e.isAvailable=function(){return!0},e.prototype.getName=function(){return this.store.name()},e.prototype.isReadOnly=function(){return!1},e.prototype.supportsSymlinks=function(){return!1},e.prototype.supportsProps=function(){return!1},e.prototype.supportsSynch=function(){return!1},e.prototype.makeRootDirectory=function(t){var e=this.store.beginTransaction("readwrite");e.get(l,function(n,r){if(n||void 0===r){var i=(new Date).getTime(),o=new a(c(),4096,16895,i,i,i);e.put(o.id,new g("{}"),!1,function(n){h(n,e,t)&&e.put(l,o.toBuffer(),!1,function(n){n?e.abort(function(){t(n)}):e.commit(t)})})}else e.commit(t)})},e.prototype._findINode=function(t,e,n,r){var i=this,o=function(t,i,o){t?r(t):o[n]?r(null,o[n]):r(y.ENOENT(d.resolve(e,n)))};"/"===e?""===n?r(null,l):this.getINode(t,e,l,function(n,a){p(n,r)&&i.getDirListing(t,e,a,function(t,e){o(t,a,e)})}):this.findINodeAndDirListing(t,e,o)},e.prototype.findINode=function(t,e,n){var r=this;this._findINode(t,d.dirname(e),d.basename(e),function(i,o){p(i,n)&&r.getINode(t,e,o,n)})},e.prototype.getINode=function(t,e,n,r){t.get(n,function(t,n){p(t,r)&&(void 0===n?r(y.ENOENT(e)):r(null,a.fromBuffer(n)))})},e.prototype.getDirListing=function(t,e,n,r){n.isDirectory()?t.get(n.id,function(t,n){if(p(t,r))try{r(null,JSON.parse(n.toString()))}catch(t){r(y.ENOENT(e))}}):r(y.ENOTDIR(e))},e.prototype.findINodeAndDirListing=function(t,e,n){var r=this;this.findINode(t,e,function(i,o){p(i,n)&&r.getDirListing(t,e,o,function(t,e){p(t,n)&&n(null,o,e)})})},e.prototype.addNewNode=function(t,e,n){var r,i=0,o=function(){5===++i?n(new y(2,"Unable to commit data to key-value store.")):(r=c(),t.put(r,e,!1,function(t,e){t||!e?o():n(null,r)}))};o()},e.prototype.commitNewFile=function(t,e,n,r,i,o){var s=this,u=d.dirname(e),f=d.basename(e),c=(new Date).getTime();return"/"===e?o(y.EEXIST(e)):(this.findINodeAndDirListing(t,u,function(u,p,l){h(u,t,o)&&(l[f]?t.abort(function(){o(y.EEXIST(e))}):s.addNewNode(t,i,function(e,u){if(h(e,t,o)){var d=new a(u,i.length,r|n,c,c,c);s.addNewNode(t,d.toBuffer(),function(e,n){h(e,t,o)&&(l[f]=n,t.put(p.id,new g(JSON.stringify(l)),!0,function(e){h(e,t,o)&&t.commit(function(e){h(e,t,o)&&o(null,d)})}))})}}))}),void 0)},e.prototype.empty=function(t){var e=this;this.store.clear(function(n){p(n,t)&&e.makeRootDirectory(t)})},e.prototype.rename=function(t,e,n){var r=this,i=this.store.beginTransaction("readwrite"),o=d.dirname(t),a=d.basename(t),s=d.dirname(e),u=d.basename(e),f={},c={},p=!1;if(0===(s+"/").indexOf(t+"/"))return n(new y(5,o));var l=function(){if(!p&&c.hasOwnProperty(o)&&c.hasOwnProperty(s)){var l=c[o],d=f[o],w=c[s],m=f[s];if(l[a]){var v=l[a];delete l[a];var b=function(){w[u]=v,i.put(d.id,new g(JSON.stringify(l)),!0,function(t){h(t,i,n)&&(o===s?i.commit(n):i.put(m.id,new g(JSON.stringify(w)),!0,function(t){h(t,i,n)&&i.commit(n)}))})};w[u]?r.getINode(i,e,w[u],function(t,r){h(t,i,n)&&(r.isFile()?i.delete(r.id,function(t){h(t,i,n)&&i.delete(w[u],function(t){h(t,i,n)&&b()})}):i.abort(function(){n(y.EPERM(e))}))}):b()}else n(y.ENOENT(t))}},w=function(t){r.findINodeAndDirListing(i,t,function(e,r,o){e?p||(p=!0,i.abort(function(){n(e)})):(f[t]=r,c[t]=o,l())})};w(o),o!==s&&w(s)},e.prototype.stat=function(t,e,n){var r=this.store.beginTransaction("readonly");this.findINode(r,t,function(t,e){p(t,n)&&n(null,e.toStats())})},e.prototype.createFile=function(t,e,n,r){var i=this,o=this.store.beginTransaction("readwrite"),a=new g(0);this.commitNewFile(o,t,32768,n,a,function(n,o){p(n,r)&&r(null,new b(i,t,e,o.toStats(),a))})},e.prototype.openFile=function(t,e,n){var r=this,i=this.store.beginTransaction("readonly");this.findINode(i,t,function(o,a){p(o,n)&&i.get(a.id,function(i,o){p(i,n)&&(void 0===o?n(y.ENOENT(t)):n(null,new b(r,t,e,a.toStats(),o)))})})},e.prototype.removeEntry=function(t,e,n){var r=this,i=this.store.beginTransaction("readwrite"),o=d.dirname(t),a=d.basename(t);this.findINodeAndDirListing(i,o,function(o,s,u){if(h(o,i,n))if(u[a]){var f=u[a];delete u[a],r.getINode(i,t,f,function(r,o){h(r,i,n)&&(!e&&o.isDirectory()?i.abort(function(){n(y.EISDIR(t))}):e&&!o.isDirectory()?i.abort(function(){n(y.ENOTDIR(t))}):i.delete(o.id,function(t){h(t,i,n)&&i.delete(f,function(t){h(t,i,n)&&i.put(s.id,new g(JSON.stringify(u)),!0,function(t){h(t,i,n)&&i.commit(n)})})}))})}else i.abort(function(){n(y.ENOENT(t))})})},e.prototype.unlink=function(t,e){this.removeEntry(t,!1,e)},e.prototype.rmdir=function(t,e){this.removeEntry(t,!0,e)},e.prototype.mkdir=function(t,e,n){var r=this.store.beginTransaction("readwrite"),i=new g("{}");this.commitNewFile(r,t,16384,e,i,n)},e.prototype.readdir=function(t,e){var n=this,r=this.store.beginTransaction("readonly");this.findINode(r,t,function(i,o){p(i,e)&&n.getDirListing(r,t,o,function(t,n){p(t,e)&&e(null,Object.keys(n))})})},e.prototype._sync=function(t,e,n,r){var i=this,o=this.store.beginTransaction("readwrite");this._findINode(o,d.dirname(t),d.basename(t),function(a,s){h(a,o,r)&&i.getINode(o,t,s,function(t,i){if(h(t,o,r)){var a=i.update(n);o.put(i.id,e,!0,function(t){h(t,o,r)&&(a?o.put(s,i.toBuffer(),!0,function(t){h(t,o,r)&&o.commit(r)}):o.commit(r))})}})})},e}(n.BaseFileSystem);e.AsyncKeyValueFileSystem=E}),u("core/global",["require","exports"],function(){var t;return t="undefined"!=typeof window?window:"undefined"!=typeof self?self:global});var f=this.__extends||function(t,e){function n(){this.constructor=t}for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r]);n.prototype=e.prototype,t.prototype=new n};u("backend/IndexedDB",["require","exports","../core/buffer","../core/browserfs","../generic/key_value_filesystem","../core/api_error","../core/buffer_core_arraybuffer","../core/global"],function(t,e,n,r,i,o,a,s){function u(t,e){switch("undefined"==typeof e&&(e=t.toString()),t.name){case"NotFoundError":return new l(1,e);case"QuotaExceededError":return new l(11,e);default:return new l(2,e)}}function c(t,e,n){return"undefined"==typeof e&&(e=2),"undefined"==typeof n&&(n=null),function(r){r.preventDefault(),t(new l(e,n))}}function p(t){var e=t.getBufferCore();e instanceof a.BufferCoreArrayBuffer||(t=new h(this._buffer.length),this._buffer.copy(t),e=t.getBufferCore());var n=e.getDataView();return n.buffer}var h=n.Buffer,l=o.ApiError,d=(o.ErrorCode,s.indexedDB||s.mozIndexedDB||s.webkitIndexedDB||s.msIndexedDB),y=function(){function t(t,e){this.tx=t,this.store=e}return t.prototype.get=function(t,e){try{var n=this.store.get(t);n.onerror=c(e),n.onsuccess=function(t){var n=t.target.result;void 0===n?e(null,n):e(null,new h(n))}}catch(r){e(u(r))}},t}();e.IndexedDBROTransaction=y;var g=function(t){function e(e,n){t.call(this,e,n)}return f(e,t),e.prototype.put=function(t,e,n,r){try{var i,o=p(e);i=n?this.store.put(o,t):this.store.add(o,t),i.onerror=c(r),i.onsuccess=function(){r(null,!0)}}catch(a){r(u(a))}},e.prototype.delete=function(t,e){try{var n=this.store.delete(t);n.onerror=c(e),n.onsuccess=function(){e()}}catch(r){e(u(r))}},e.prototype.commit=function(t){setTimeout(t,0)},e.prototype.abort=function(t){var e;try{this.tx.abort()}catch(n){e=u(n)}finally{t(e)}},e}(y);e.IndexedDBRWTransaction=g;var w=function(){function t(t,e){"undefined"==typeof e&&(e="browserfs");var n=this;this.storeName=e;var r=d.open(this.storeName,1);r.onupgradeneeded=function(t){var e=t.target.result;e.objectStoreNames.contains(n.storeName)&&e.deleteObjectStore(n.storeName),e.createObjectStore(n.storeName)},r.onsuccess=function(e){n.db=e.target.result,t(null,n)},r.onerror=c(t,4)}return t.prototype.name=function(){return"IndexedDB - "+this.storeName},t.prototype.clear=function(t){try{var e=this.db.transaction(this.storeName,"readwrite"),n=e.objectStore(this.storeName),r=n.clear();r.onsuccess=function(){setTimeout(t,0)},r.onerror=c(t)}catch(i){t(u(i))}},t.prototype.beginTransaction=function(t){"undefined"==typeof t&&(t="readonly");var e=this.db.transaction(this.storeName,t),n=e.objectStore(this.storeName);if("readwrite"===t)return new g(e,n);if("readonly"===t)return new y(e,n);throw new l(9,"Invalid transaction type.")},t}();e.IndexedDBStore=w;var m=function(t){function e(e,n){var r=this;t.call(this),new w(function(t,n){t?e(t):r.init(n,function(t){e(t,r)})},n)}return f(e,t),e.isAvailable=function(){return"undefined"!=typeof d},e}(i.AsyncKeyValueFileSystem);e.IndexedDBFileSystem=m,r.registerFileSystem("IndexedDB",m)}),u("generic/file_index",["require","exports","../core/node_fs_stats","../core/node_path"],function(t,e,n,r){var i=n.Stats,o=r.path,a=function(){function t(){this._index={},this.addPath("/",new u)}return t.prototype._split_path=function(t){var e=o.dirname(t),n=t.substr(e.length+("/"===e?0:1));return[e,n]},t.prototype.fileIterator=function(t){for(var e in this._index)for(var n=this._index[e],r=n.getListing(),i=0;i0;){var a,f=o.pop(),c=f[0],p=f[1],h=f[2];for(var l in p){var d=p[l],y=""+c+"/"+l;null!=d?(n._index[y]=a=new u,o.push([y,d,a])):a=new s(new i(32768,-1,365)),null!=h&&(h._ls[l]=a)}}return n},t}();e.FileIndex=a;var s=function(){function t(t){this.data=t}return t.prototype.isFile=function(){return!0},t.prototype.isDir=function(){return!1},t.prototype.getData=function(){return this.data},t.prototype.setData=function(t){this.data=t},t}();e.FileInode=s;var u=function(){function t(){this._ls={}}return t.prototype.isFile=function(){return!1},t.prototype.isDir=function(){return!0},t.prototype.getStats=function(){return new i(16384,4096,365)},t.prototype.getListing=function(){return Object.keys(this._ls)},t.prototype.getItem=function(t){var e;return null!=(e=this._ls[t])?e:null},t.prototype.addItem=function(t,e){return t in this._ls?!1:(this._ls[t]=e,!0)},t.prototype.remItem=function(t){var e=this._ls[t];return void 0===e?null:(delete this._ls[t],e)},t}();e.DirInode=u}),u("core/util",["require","exports"],function(t,e){function n(t){var e,n,r,i,o,a;for(r=[],o=[t],e=0;0!==o.length;)if(a=o.pop(),"boolean"==typeof a)e+=4;else if("string"==typeof a)e+=2*a.length;else if("number"==typeof a)e+=8;else if("object"==typeof a&&r.indexOf(a)<0){r.push(a),e+=4;for(n in a)i=a[n],e+=2*n.length,o.push(i)}return e}e.roughSizeOfObject=n,e.isIE=null!=/(msie) ([\w.]+)/.exec(navigator.userAgent.toLowerCase())||-1!==navigator.userAgent.indexOf("Trident")}),u("generic/xhr",["require","exports","../core/util","../core/buffer","../core/api_error"],function(t,e,n,r,i){function o(t){for(var e=IEBinaryToArray_ByteStr(t),n=IEBinaryToArray_ByteStr_Last(t),r=e.replace(/[\s\S]/g,function(t){var e=t.charCodeAt(0);return String.fromCharCode(255&e,e>>8)})+n,i=new Array(r.length),o=0;o0&&"/"!==n.charAt(n.length-1)&&(n+="/");var i=this._requestFileSync(e,"json");if(null==i)throw new Error("Unable to find listing at URL: "+e);this._index=r.FileIndex.from_listing(i)}return f(e,t),e.prototype.empty=function(){this._index.fileIterator(function(t){t.file_data=null})},e.prototype.getXhrPath=function(t){return"/"===t.charAt(0)&&(t=t.slice(1)),this.prefix_url+t},e.prototype._requestFileSizeAsync=function(t,e){c.getFileSizeAsync(this.getXhrPath(t),e)},e.prototype._requestFileSizeSync=function(t){return c.getFileSizeSync(this.getXhrPath(t))},e.prototype._requestFileAsync=function(t,e,n){c.asyncDownloadFile(this.getXhrPath(t),e,n)},e.prototype._requestFileSync=function(t,e){return c.syncDownloadFile(this.getXhrPath(t),e)},e.prototype.getName=function(){return"XmlHttpRequest"},e.isAvailable=function(){return"undefined"!=typeof XMLHttpRequest&&null!==XMLHttpRequest},e.prototype.diskSpace=function(t,e){e(0,0)},e.prototype.isReadOnly=function(){return!0},e.prototype.supportsLinks=function(){return!1},e.prototype.supportsProps=function(){return!1},e.prototype.supportsSynch=function(){return!0},e.prototype.preloadFile=function(t,e){var n=this._index.getInode(t);if(null===n)throw p.ENOENT(t);var r=n.getData();r.size=e.length,r.file_data=e},e.prototype.stat=function(t,e,n){var r=this._index.getInode(t);if(null===r)return n(p.ENOENT(t));var i;r.isFile()?(i=r.getData(),i.size<0?this._requestFileSizeAsync(t,function(t,e){return t?n(t):(i.size=e,n(null,i.clone()),void 0)}):n(null,i.clone())):(i=r.getStats(),n(null,i))},e.prototype.statSync=function(t){var e=this._index.getInode(t);if(null===e)throw p.ENOENT(t);var n;return e.isFile()?(n=e.getData(),n.size<0&&(n.size=this._requestFileSizeSync(t))):n=e.getStats(),n},e.prototype.open=function(t,e,n,r){if(e.isWriteable())return r(new p(0,t));var i=this,o=this._index.getInode(t);if(null===o)return r(p.ENOENT(t));if(o.isDir())return r(p.EISDIR(t));var a=o.getData();switch(e.pathExistsAction()){case 1:case 2:return r(p.EEXIST(t));case 0:if(null!=a.file_data)return r(null,new s.NoSyncFile(i,t,e,a.clone(),a.file_data));this._requestFileAsync(t,"buffer",function(n,o){return n?r(n):(a.size=o.length,a.file_data=o,r(null,new s.NoSyncFile(i,t,e,a.clone(),o)))});break;default:return r(new p(9,"Invalid FileMode object."))}},e.prototype.openSync=function(t,e){if(e.isWriteable())throw new p(0,t);var n=this._index.getInode(t);if(null===n)throw p.ENOENT(t);if(n.isDir())throw p.EISDIR(t);var r=n.getData();switch(e.pathExistsAction()){case 1:case 2:throw p.EEXIST(t);case 0:if(null!=r.file_data)return new s.NoSyncFile(this,t,e,r.clone(),r.file_data);var i=this._requestFileSync(t,"buffer");return r.size=i.length,r.file_data=i,new s.NoSyncFile(this,t,e,r.clone(),i);default:throw new p(9,"Invalid FileMode object.")}},e.prototype.readdir=function(t,e){try{e(null,this.readdirSync(t))}catch(n){e(n)}},e.prototype.readdirSync=function(t){var e=this._index.getInode(t);if(null===e)throw p.ENOENT(t);if(e.isFile())throw p.ENOTDIR(t);return e.getListing()},e.prototype.readFile=function(t,e,n,r){var o=r;this.open(t,n,420,function(t,n){if(t)return r(t);r=function(t,e){n.close(function(n){return null==t&&(t=n),o(t,e)})}; +var a=n,s=a._buffer;if(null===e)return s.length>0?r(t,s.sliceCopy()):r(t,new i.Buffer(0));try{r(null,s.toString(e))}catch(u){r(u)}})},e.prototype.readFileSync=function(t,e,n){var r=this.openSync(t,n,420);try{var o=r,a=o._buffer;return null===e?a.length>0?a.sliceCopy():new i.Buffer(0):a.toString(e)}finally{r.closeSync()}},e}(n.BaseFileSystem);e.XmlHttpRequest=h,u.registerFileSystem("XmlHttpRequest",h)}),function(){function t(t){var n=!1;return function(){if(n)throw new Error("Callback was already called.");n=!0,t.apply(e,arguments)}}var e,n,r={};e=this,null!=e&&(n=e.async),r.noConflict=function(){return e.async=n,r};var i=function(t,e){if(t.forEach)return t.forEach(e);for(var n=0;n=e.length&&r(null))}))})},r.forEach=r.each,r.eachSeries=function(t,e,n){if(n=n||function(){},!t.length)return n();var r=0,i=function(){e(t[r],function(e){e?(n(e),n=function(){}):(r+=1,r>=t.length?n(null):i())})};i()},r.forEachSeries=r.eachSeries,r.eachLimit=function(t,e,n,r){var i=f(e);i.apply(null,[t,n,r])},r.forEachLimit=r.eachLimit;var f=function(t){return function(e,n,r){if(r=r||function(){},!e.length||0>=t)return r();var i=0,o=0,a=0;!function s(){if(i>=e.length)return r();for(;t>a&&o=e.length?r():s())})}()}},c=function(t){return function(){var e=Array.prototype.slice.call(arguments);return t.apply(null,[r.each].concat(e))}},p=function(t,e){return function(){var n=Array.prototype.slice.call(arguments);return e.apply(null,[f(t)].concat(n))}},h=function(t){return function(){var e=Array.prototype.slice.call(arguments);return t.apply(null,[r.eachSeries].concat(e))}},l=function(t,e,n,r){var i=[];e=o(e,function(t,e){return{index:e,value:t}}),t(e,function(t,e){n(t.value,function(n,r){i[t.index]=r,e(n)})},function(t){r(t,i)})};r.map=c(l),r.mapSeries=h(l),r.mapLimit=function(t,e,n,r){return d(e)(t,n,r)};var d=function(t){return p(t,l)};r.reduce=function(t,e,n,i){r.eachSeries(t,function(t,r){n(e,t,function(t,n){e=n,r(t)})},function(t){i(t,e)})},r.inject=r.reduce,r.foldl=r.reduce,r.reduceRight=function(t,e,n,i){var a=o(t,function(t){return t}).reverse();r.reduce(a,e,n,i)},r.foldr=r.reduceRight;var y=function(t,e,n,r){var i=[];e=o(e,function(t,e){return{index:e,value:t}}),t(e,function(t,e){n(t.value,function(n){n&&i.push(t),e()})},function(){r(o(i.sort(function(t,e){return t.index-e.index}),function(t){return t.value}))})};r.filter=c(y),r.filterSeries=h(y),r.select=r.filter,r.selectSeries=r.filterSeries;var g=function(t,e,n,r){var i=[];e=o(e,function(t,e){return{index:e,value:t}}),t(e,function(t,e){n(t.value,function(n){n||i.push(t),e()})},function(){r(o(i.sort(function(t,e){return t.index-e.index}),function(t){return t.value}))})};r.reject=c(g),r.rejectSeries=h(g);var w=function(t,e,n,r){t(e,function(t,e){n(t,function(n){n?(r(t),r=function(){}):e()})},function(){r()})};r.detect=c(w),r.detectSeries=h(w),r.some=function(t,e,n){r.each(t,function(t,r){e(t,function(t){t&&(n(!0),n=function(){}),r()})},function(){n(!1)})},r.any=r.some,r.every=function(t,e,n){r.each(t,function(t,r){e(t,function(t){t||(n(!1),n=function(){}),r()})},function(){n(!0)})},r.all=r.every,r.sortBy=function(t,e,n){r.map(t,function(t,n){e(t,function(e,r){e?n(e):n(null,{value:t,criteria:r})})},function(t,e){if(t)return n(t);var r=function(t,e){var n=t.criteria,r=e.criteria;return r>n?-1:n>r?1:0};n(null,o(e.sort(r),function(t){return t.value}))})},r.auto=function(t,e){e=e||function(){};var n=s(t);if(!n.length)return e(null);var o={},u=[],f=function(t){u.unshift(t)},c=function(t){for(var e=0;ee;e++)t[e].apply(null,arguments)}])))};return i.memo=n,i.unmemoized=t,i},r.unmemoize=function(t){return function(){return(t.unmemoized||t).apply(null,arguments)}},r.times=function(t,e,n){for(var i=[],o=0;t>o;o++)i.push(o);return r.map(i,e,n)},r.timesSeries=function(t,e,n){for(var i=[],o=0;t>o;o++)i.push(o);return r.mapSeries(i,e,n)},r.compose=function(){var t=Array.prototype.reverse.call(arguments);return function(){var e=this,n=Array.prototype.slice.call(arguments),i=n.pop();r.reduce(t,n,function(t,n,r){n.apply(e,t.concat([function(){var t=arguments[0],e=Array.prototype.slice.call(arguments,1);r(t,e)}]))},function(t,n){i.apply(e,[t].concat(n))})}};var E=function(t,e){var n=function(){var n=this,r=Array.prototype.slice.call(arguments),i=r.pop();return t(e,function(t,e){t.apply(n,r.concat([e]))},i)};if(arguments.length>2){var r=Array.prototype.slice.call(arguments,2);return n.apply(this,r)}return n};r.applyEach=c(E),r.applyEachSeries=h(E),r.forever=function(t,e){function n(r){if(r){if(e)return e(r);throw r}t(n)}n()},"undefined"!=typeof u&&u.amd?u("async",[],function(){return r}):"undefined"!=typeof module&&module.exports?module.exports=r:e.async=r}();var f=this.__extends||function(t,e){function n(){this.constructor=t}for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r]);n.prototype=e.prototype,t.prototype=new n};u("backend/dropbox",["require","exports","../generic/preload_file","../core/file_system","../core/node_fs_stats","../core/buffer","../core/api_error","../core/node_path","../core/browserfs","../core/buffer_core_arraybuffer","async"],function(t,e,n,r,i,o,a,s,u,c){var p=o.Buffer,h=i.Stats,l=a.ApiError;a.ErrorCode;var d=s.path;i.FileType;var y=t("async"),p=o.Buffer,g=function(t){function e(e,n,r,i,o){t.call(this,e,n,r,i,o)}return f(e,t),e.prototype.sync=function(t){var e=this._buffer,n=this._buffer.getBufferCore();n instanceof c.BufferCoreArrayBuffer||(e=new p(this._buffer.length),this._buffer.copy(e),n=e.getBufferCore());var r=n.getDataView(),i=new DataView(r.buffer,r.byteOffset+e.getOffset(),e.length);this._fs._writeFileStrict(this._path,i,t)},e.prototype.close=function(t){this.sync(t)},e}(n.PreloadFile);e.DropboxFile=g;var w=function(t){function e(e){t.call(this),this.client=e}return f(e,t),e.prototype.getName=function(){return"Dropbox"},e.isAvailable=function(){return"undefined"!=typeof Dropbox},e.prototype.isReadOnly=function(){return!1},e.prototype.supportsSymlinks=function(){return!1},e.prototype.supportsProps=function(){return!1},e.prototype.supportsSynch=function(){return!1},e.prototype.empty=function(t){var e=this;this.client.readdir("/",function(n,r,i,o){if(n)t(e.convert(n));else{var a=function(t,n){e.client.remove(t.path,function(t){n(t?e.convert(t):t)})},s=function(n){n?t(e.convert(n)):t()};y.each(o,a,s)}})},e.prototype.rename=function(t,e,n){this.client.move(t,e,function(r){if(r){var i=r.response.error.indexOf(t)>-1?t:e;n(new l(1,i+" doesn't exist"))}else n()})},e.prototype.stat=function(t,e,n){var r=this;this.client.stat(t,function(e,i){if(!(e||null!=i&&i.isRemoved)){var o=new h(r._statType(i),i.size);return n(null,o)}n(new l(1,t+" doesn't exist"))})},e.prototype.open=function(t,e,n,r){var i=this;this.client.readFile(t,{arrayBuffer:!0},function(n,o,a){if(!n){var s;s=null===o?new p(0):new p(o);var u=i._makeFile(t,e,a,s);return r(null,u)}if(e.isReadable())r(new l(1,t+" doesn't exist"));else switch(n.status){case 0:return console.error("No connection");case 404:var f=new ArrayBuffer(0);return i._writeFileStrict(t,f,function(n,o){if(n)r(n);else{var a=i._makeFile(t,e,o,new p(f));r(null,a)}});default:return console.log("Unhandled error: "+n)}})},e.prototype._writeFileStrict=function(t,e,n){var r=this,i=d.dirname(t);this.stat(i,!1,function(o){o?n(new l(1,"Can't create "+t+" because "+i+" doesn't exist")):r.client.writeFile(t,e,function(t,e){t?n(r.convert(t)):n(null,e)})})},e.prototype._statType=function(t){return t.isFile?32768:16384},e.prototype._makeFile=function(t,e,n,r){var i=this._statType(n),o=new h(i,n.size);return new g(this,t,e,o,r)},e.prototype._remove=function(t,e,n){var r=this;this.client.stat(t,function(i,o){i?e(new l(1,t+" doesn't exist")):o.isFile&&!n?e(new l(7,t+" is a file.")):!o.isFile&&n?e(new l(8,t+" is a directory.")):r.client.remove(t,function(n){n?e(new l(2,"Failed to remove "+t)):e(null)})})},e.prototype.unlink=function(t,e){this._remove(t,e,!0)},e.prototype.rmdir=function(t,e){this._remove(t,e,!1)},e.prototype.mkdir=function(t,e,n){var r=this,i=d.dirname(t);this.client.stat(i,function(e){e?n(new l(1,"Can't create "+t+" because "+i+" doesn't exist")):r.client.mkdir(t,function(e){e?n(new l(6,t+" already exists")):n(null)})})},e.prototype.readdir=function(t,e){var n=this;this.client.readdir(t,function(t,r){return t?e(n.convert(t)):e(null,r)})},e.prototype.convert=function(t,e){switch("undefined"==typeof e&&(e=""),t.status){case 400:return new l(9,e);case 401:case 403:return new l(2,e);case 404:return new l(1,e);case 405:return new l(14,e);case 0:case 304:case 406:case 409:default:return new l(2,e)}},e}(r.BaseFileSystem);e.DropboxFileSystem=w,u.registerFileSystem("Dropbox",w)});var f=this.__extends||function(t,e){function n(){this.constructor=t}for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r]);n.prototype=e.prototype,t.prototype=new n};u("backend/html5fs",["require","exports","../generic/preload_file","../core/file_system","../core/api_error","../core/file_flag","../core/node_fs_stats","../core/buffer","../core/browserfs","../core/buffer_core_arraybuffer","../core/node_path","../core/global","async"],function(t,e,n,r,i,o,a,s,u,c,p,h){function l(t,e,n,r){if("undefined"!=typeof navigator.webkitPersistentStorage)switch(t){case h.PERSISTENT:navigator.webkitPersistentStorage.requestQuota(e,n,r);break;case h.TEMPORARY:navigator.webkitTemporaryStorage.requestQuota(e,n,r);break;default:r(null)}else h.webkitStorageInfo.requestQuota(t,e,n,r)}function d(t){return Array.prototype.slice.call(t||[],0)}var y=s.Buffer,g=a.Stats;a.FileType;var w=i.ApiError;i.ErrorCode,o.ActionType;var m=t("async"),v=h.webkitRequestFileSystem||h.requestFileSystem||null,b=function(t){function e(e,n,r,i,o){t.call(this,e,n,r,i,o)}return f(e,t),e.prototype.sync=function(t){var e=this,n={create:!1},r=this._fs,i=function(n){n.createWriter(function(n){var i=e._buffer,o=e._buffer.getBufferCore();o instanceof c.BufferCoreArrayBuffer||(i=new y(e._buffer.length),e._buffer.copy(i),o=i.getBufferCore());var a=o.getDataView(),s=new DataView(a.buffer,a.byteOffset+i.getOffset(),i.length),u=new Blob([s]),f=u.size;n.onwriteend=function(){n.onwriteend=null,n.truncate(f),t()},n.onerror=function(e){t(r.convert(e))},n.write(u)})},o=function(e){t(r.convert(e))};r.fs.root.getFile(this._path,n,i,o)},e.prototype.close=function(t){this.sync(t)},e}(n.PreloadFile);e.HTML5FSFile=b;var E=function(t){function e(e,n){t.call(this),this.size=null!=e?e:5,this.type=null!=n?n:h.PERSISTENT;var r=1024,i=r*r;this.size*=i}return f(e,t),e.prototype.getName=function(){return"HTML5 FileSystem"},e.isAvailable=function(){return null!=v},e.prototype.isReadOnly=function(){return!1},e.prototype.supportsSymlinks=function(){return!1},e.prototype.supportsProps=function(){return!1},e.prototype.supportsSynch=function(){return!1},e.prototype.convert=function(t,e){switch("undefined"==typeof e&&(e=""),t.name){case"QuotaExceededError":return new w(11,e);case"NotFoundError":return new w(1,e);case"SecurityError":return new w(4,e);case"InvalidModificationError":return new w(0,e);case"SyntaxError":case"TypeMismatchError":return new w(9,e);default:return new w(9,e)}},e.prototype.convertErrorEvent=function(t,e){return"undefined"==typeof e&&(e=""),new w(1,t.message+"; "+e)},e.prototype.allocate=function(t){var e=this;"undefined"==typeof t&&(t=function(){});var n=function(n){e.fs=n,t()},r=function(n){t(e.convert(n))};this.type===h.PERSISTENT?l(this.type,this.size,function(t){v(e.type,t,n,r)},r):v(this.type,this.size,n,r)},e.prototype.empty=function(t){var e=this;this._readdir("/",function(n,r){if(n)console.error("Failed to empty FS"),t(n);else{var i=function(){n?(console.error("Failed to empty FS"),t(n)):t()},o=function(t,n){var r=function(){n()},i=function(r){n(e.convert(r,t.fullPath))};t.isFile?t.remove(r,i):t.removeRecursively(r,i)};m.each(r,o,i)}})},e.prototype.rename=function(t,e,n){var r=this,i=2,o=0,a=this.fs.root,s=function(o){0===--i&&n(r.convert(o,"Failed to rename "+t+" to "+e+"."))},u=function(i){return 2===++o?(console.error("Something was identified as both a file and a directory. This should never happen."),void 0):t===e?n():(a.getDirectory(p.path.dirname(e),{},function(o){i.moveTo(o,p.path.basename(e),function(){n()},function(o){i.isDirectory?r.unlink(e,function(i){i?s(o):r.rename(t,e,n)}):s(o)})},s),void 0)};a.getFile(t,{},u,s),a.getDirectory(t,{},u,s)},e.prototype.stat=function(t,e,n){var r=this,i={create:!1},o=function(t){var e=function(t){var e=new g(32768,t.size);n(null,e)};t.file(e,s)},a=function(){var t=4096,e=new g(16384,t);n(null,e)},s=function(e){n(r.convert(e,t))},u=function(){r.fs.root.getDirectory(t,i,a,s)};this.fs.root.getFile(t,i,o,u)},e.prototype.open=function(t,e,n,r){var i=this,o={create:3===e.pathNotExistsAction(),exclusive:e.isExclusive()},a=function(e){r(i.convertErrorEvent(e,t))},s=function(e){r(i.convert(e,t))},u=function(n){var o=function(n){var o=new FileReader;o.onloadend=function(){var a=i._makeFile(t,e,n,o.result);r(null,a)},o.onerror=a,o.readAsArrayBuffer(n)};n.file(o,s)};this.fs.root.getFile(t,o,u,a)},e.prototype._statType=function(t){return t.isFile?32768:16384},e.prototype._makeFile=function(t,e,n,r){"undefined"==typeof r&&(r=new ArrayBuffer(0));var i=new g(32768,n.size),o=new y(r);return new b(this,t,e,i,o)},e.prototype._remove=function(t,e,n){var r=this,i=function(n){var i=function(){e()},o=function(n){e(r.convert(n,t))};n.remove(i,o)},o=function(n){e(r.convert(n,t))},a={create:!1};n?this.fs.root.getFile(t,a,i,o):this.fs.root.getDirectory(t,a,i,o)},e.prototype.unlink=function(t,e){this._remove(t,e,!0)},e.prototype.rmdir=function(t,e){this._remove(t,e,!1)},e.prototype.mkdir=function(t,e,n){var r=this,i={create:!0,exclusive:!0},o=function(){n()},a=function(e){n(r.convert(e,t))};this.fs.root.getDirectory(t,i,o,a)},e.prototype._readdir=function(t,e){var n=this;this.fs.root.getDirectory(t,{create:!1},function(r){var i=r.createReader(),o=[],a=function(r){e(n.convert(r,t))},s=function(){i.readEntries(function(t){t.length?(o=o.concat(d(t)),s()):e(null,o)},a)};s()})},e.prototype.readdir=function(t,e){this._readdir(t,function(t,n){if(null!=t)return e(t);for(var r=[],i=0;i0&&t[0]instanceof i.ApiError&&s.standardizeError(t[0],o.path,r),a.apply(null,t)}}return o.fs[t].apply(o.fs,e)}}var u=i.ApiError;i.ErrorCode;var c=o.fs,p=function(t){function e(){t.call(this),this.mntMap={},this.rootFs=new r.InMemoryFileSystem}return f(e,t),e.prototype.mount=function(t,e){if(this.mntMap[t])throw new u(9,"Mount point "+t+" is already taken.");this.rootFs.mkdirSync(t,511),this.mntMap[t]=e},e.prototype.umount=function(t){if(!this.mntMap[t])throw new u(9,"Mount point "+t+" is already unmounted.");delete this.mntMap[t],this.rootFs.rmdirSync(t)},e.prototype._get_fs=function(t){for(var e in this.mntMap){var n=this.mntMap[e];if(0===t.indexOf(e))return t=t.substr(e.length>1?e.length:0),""===t&&(t="/"),{fs:n,path:t}}return{fs:this.rootFs,path:t}},e.prototype.getName=function(){return"MountableFileSystem"},e.isAvailable=function(){return!0},e.prototype.diskSpace=function(t,e){e(0,0)},e.prototype.isReadOnly=function(){return!1},e.prototype.supportsLinks=function(){return!1},e.prototype.supportsProps=function(){return!1},e.prototype.supportsSynch=function(){return!0},e.prototype.standardizeError=function(t,e,n){var r;return-1!==(r=t.message.indexOf(e))&&(t.message=t.message.substr(0,r)+n+t.message.substr(r+e.length)),t},e.prototype.rename=function(t,e,n){var r=this._get_fs(t),i=this._get_fs(e);if(r.fs===i.fs){var o=this;return r.fs.rename(r.path,i.path,function(a){a&&o.standardizeError(o.standardizeError(a,r.path,t),i.path,e),n(a)})}return c.readFile(t,function(r,i){return r?n(r):(c.writeFile(e,i,function(e){return e?n(e):(c.unlink(t,n),void 0)}),void 0)})},e.prototype.renameSync=function(t,e){var n=this._get_fs(t),r=this._get_fs(e);if(n.fs===r.fs)try{return n.fs.renameSync(n.path,r.path)}catch(i){throw this.standardizeError(this.standardizeError(i,n.path,t),r.path,e),i}var o=c.readFileSync(t);return c.writeFileSync(e,o),c.unlinkSync(t)},e}(n.BaseFileSystem);e.MountableFileSystem=p;for(var h=[["readdir","exists","unlink","rmdir","readlink"],["stat","mkdir","realpath","truncate"],["open","readFile","chmod","utimes"],["chown"],["writeFile","appendFile"]],l=0;lf;++f)t[f]>h&&(h=t[f]),t[f]=r;){for(f=0;p>f;++f)if(t[f]===r){for(a=0,s=i,c=0;r>c;++c)a=a<<1|1&s,s>>=1;for(c=a;e>c;c+=o)n[c]=r<<16|f;++i}++r,i<<=1,o<<=1}return[n,h,l]}function n(t,e){switch(this.g=[],this.h=32768,this.c=this.f=this.d=this.k=0,this.input=u?new Uint8Array(t):t,this.l=!1,this.i=c,this.p=!1,(e||!(e={}))&&(e.index&&(this.d=e.index),e.bufferSize&&(this.h=e.bufferSize),e.bufferType&&(this.i=e.bufferType),e.resize&&(this.p=e.resize)),this.i){case f:this.a=32768,this.b=new(u?Uint8Array:Array)(32768+this.h+258);break;case c:this.a=0,this.b=new(u?Uint8Array:Array)(this.h),this.e=this.u,this.m=this.r,this.j=this.s;break;default:throw Error("invalid inflate mode")}}function r(t,e){for(var n,r=t.f,i=t.c,o=t.input,s=t.d;e>i;){if(n=o[s++],n===a)throw Error("input buffer is broken");r|=n<>>e,t.c=i-e,t.d=s,n}function i(t,e){for(var n,r,i,o=t.f,s=t.c,u=t.input,f=t.d,c=e[0],p=e[1];p>s&&(n=u[f++],n!==a);)o|=n<>>16,t.f=o>>i,t.c=s-i,t.d=f,65535&r}function o(t){function n(t,e,n){var o,a,s,u;for(u=0;t>u;)switch(o=i(this,e)){case 16:for(s=3+r(this,2);s--;)n[u++]=a;break;case 17:for(s=3+r(this,3);s--;)n[u++]=0;a=0;break;case 18:for(s=11+r(this,7);s--;)n[u++]=0;a=0;break;default:a=n[u++]=o}return n}var o,a,s,f,c=r(t,5)+257,p=r(t,5)+1,h=r(t,4)+4,l=new(u?Uint8Array:Array)(d.length);for(f=0;h>f;++f)l[d[f]]=r(t,3);o=e(l),a=new(u?Uint8Array:Array)(c),s=new(u?Uint8Array:Array)(p),t.j(e(n.call(t,c,o,a)),e(n.call(t,p,o,s)))}var a=void 0,s=this,u="undefined"!=typeof Uint8Array&&"undefined"!=typeof Uint16Array&&"undefined"!=typeof Uint32Array,f=0,c=1;n.prototype.t=function(){for(;!this.l;){var t=r(this,3);switch(1&t&&(this.l=!0),t>>>=1){case 0:var e=this.input,n=this.d,i=this.b,s=this.a,p=a,h=a,l=a,d=i.length,y=a;if(this.c=this.f=0,p=e[n++],p===a)throw Error("invalid uncompressed block header: LEN (first byte)");if(h=p,p=e[n++],p===a)throw Error("invalid uncompressed block header: LEN (second byte)");if(h|=p<<8,p=e[n++],p===a)throw Error("invalid uncompressed block header: NLEN (first byte)");if(l=p,p=e[n++],p===a)throw Error("invalid uncompressed block header: NLEN (second byte)");if(l|=p<<8,h===~l)throw Error("invalid uncompressed block header: length verify");if(n+h>e.length)throw Error("input buffer is broken");switch(this.i){case f:for(;s+h>i.length;){if(y=d-s,h-=y,u)i.set(e.subarray(n,n+y),s),s+=y,n+=y;else for(;y--;)i[s++]=e[n++];this.a=s,i=this.e(),s=this.a}break;case c:for(;s+h>i.length;)i=this.e({o:2});break;default:throw Error("invalid inflate mode")}if(u)i.set(e.subarray(n,n+h),s),s+=h,n+=h;else for(;h--;)i[s++]=e[n++];this.d=n,this.a=s,this.b=i;break;case 1:this.j(A,N);break;case 2:o(this);break;default:throw Error("unknown BTYPE: "+t)}}return this.m()};var p,h,l=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],d=u?new Uint16Array(l):l,y=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,258,258],g=u?new Uint16Array(y):y,w=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0],m=u?new Uint8Array(w):w,v=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],b=u?new Uint16Array(v):v,E=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13],S=u?new Uint8Array(E):E,I=new(u?Uint8Array:Array)(288);for(p=0,h=I.length;h>p;++p)I[p]=143>=p?8:255>=p?9:279>=p?7:8;var _,F,A=e(I),x=new(u?Uint8Array:Array)(30);for(_=0,F=x.length;F>_;++_)x[_]=5;var N=e(x);n.prototype.j=function(t,e){var n=this.b,o=this.a;this.n=t;for(var a,s,u,f,c=n.length-258;256!==(a=i(this,t));)if(256>a)o>=c&&(this.a=o,n=this.e(),o=this.a),n[o++]=a;else for(s=a-257,f=g[s],0=c&&(this.a=o,n=this.e(),o=this.a);f--;)n[o]=n[o++-u];for(;8<=this.c;)this.c-=8,this.d--;this.a=o},n.prototype.s=function(t,e){var n=this.b,o=this.a;this.n=t;for(var a,s,u,f,c=n.length;256!==(a=i(this,t));)if(256>a)o>=c&&(n=this.e(),c=n.length),n[o++]=a;else for(s=a-257,f=g[s],0c&&(n=this.e(),c=n.length);f--;)n[o]=n[o++-u];for(;8<=this.c;)this.c-=8,this.d--;this.a=o},n.prototype.e=function(){var t,e,n=new(u?Uint8Array:Array)(this.a-32768),r=this.a-32768,i=this.b;if(u)n.set(i.subarray(32768,n.length));else for(t=0,e=n.length;e>t;++t)n[t]=i[t+32768];if(this.g.push(n),this.k+=n.length,u)i.set(i.subarray(r,r+32768));else for(t=0;32768>t;++t)i[t]=i[r+t];return this.a=32768,i},n.prototype.u=function(t){var e,n,r,i,o=0|this.input.length/this.d+1,a=this.input,s=this.b;return t&&("number"==typeof t.o&&(o=t.o),"number"==typeof t.q&&(o+=t.q)),2>o?(n=(a.length-this.d)/this.n[2],i=0|258*(n/2),r=ie;++e)for(t=s[e],r=0,i=t.length;i>r;++r)f[o++]=t[r];for(e=32768,n=this.a;n>e;++e)f[o++]=a[e];return this.g=[],this.buffer=f},n.prototype.r=function(){var t,e=this.a;return u?this.p?(t=new Uint8Array(e),t.set(this.b.subarray(0,e))):t=this.b.subarray(0,e):(this.b.length>e&&(this.b.length=e),t=this.b),this.buffer=t},t("Zlib.RawInflate",n),t("Zlib.RawInflate.prototype.decompress",n.prototype.t);var L,U,D,T,O={ADAPTIVE:c,BLOCK:f};if(Object.keys)L=Object.keys(O);else for(U in L=[],D=0,O)L[D++]=U;for(D=0,T=L.length;T>D;++D)U=L[D],t("Zlib.RawInflate.BufferType."+U,O[U])}.call(this),u("zlib",function(t){return function(){var e;return e||t.Zlib.RawInflate}}(this));var f=this.__extends||function(t,e){function n(){this.constructor=t}for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r]);n.prototype=e.prototype,t.prototype=new n};u("backend/zipfs",["require","exports","../core/buffer","../core/api_error","../generic/file_index","../core/browserfs","../core/node_fs_stats","../core/file_system","../core/file_flag","../core/buffer_core_arraybuffer","../generic/preload_file","zlib"],function(t,e,n,r,i,o,a,s,u,c,p){function h(t,e){var n=31&e,r=(15&e>>5)-1,i=(e>>9)+1980,o=31&t,a=63&t>>5,s=t>>11;return new Date(i,r,n,s,a,o)}function l(t,e,n,r){return 0===r?"":t.toString(e?"utf8":"extended_ascii",n,n+r)}var d=r.ApiError;r.ErrorCode,u.ActionType;var y=Zlib.RawInflate;!function(t){t[t.MSDOS=0]="MSDOS",t[t.AMIGA=1]="AMIGA",t[t.OPENVMS=2]="OPENVMS",t[t.UNIX=3]="UNIX",t[t.VM_CMS=4]="VM_CMS",t[t.ATARI_ST=5]="ATARI_ST",t[t.OS2_HPFS=6]="OS2_HPFS",t[t.MAC=7]="MAC",t[t.Z_SYSTEM=8]="Z_SYSTEM",t[t.CP_M=9]="CP_M",t[t.NTFS=10]="NTFS",t[t.MVS=11]="MVS",t[t.VSE=12]="VSE",t[t.ACORN_RISC=13]="ACORN_RISC",t[t.VFAT=14]="VFAT",t[t.ALT_MVS=15]="ALT_MVS",t[t.BEOS=16]="BEOS",t[t.TANDEM=17]="TANDEM",t[t.OS_400=18]="OS_400",t[t.OSX=19]="OSX" +}(e.ExternalFileAttributeType||(e.ExternalFileAttributeType={})),e.ExternalFileAttributeType,function(t){t[t.STORED=0]="STORED",t[t.SHRUNK=1]="SHRUNK",t[t.REDUCED_1=2]="REDUCED_1",t[t.REDUCED_2=3]="REDUCED_2",t[t.REDUCED_3=4]="REDUCED_3",t[t.REDUCED_4=5]="REDUCED_4",t[t.IMPLODE=6]="IMPLODE",t[t.DEFLATE=8]="DEFLATE",t[t.DEFLATE64=9]="DEFLATE64",t[t.TERSE_OLD=10]="TERSE_OLD",t[t.BZIP2=12]="BZIP2",t[t.LZMA=14]="LZMA",t[t.TERSE_NEW=18]="TERSE_NEW",t[t.LZ77=19]="LZ77",t[t.WAVPACK=97]="WAVPACK",t[t.PPMD=98]="PPMD"}(e.CompressionMethod||(e.CompressionMethod={}));var g=e.CompressionMethod,w=function(){function t(t){if(this.data=t,67324752!==t.readUInt32LE(0))throw new d(9,"Invalid Zip file: Local file header has invalid signature: "+this.data.readUInt32LE(0))}return t.prototype.versionNeeded=function(){return this.data.readUInt16LE(4)},t.prototype.flags=function(){return this.data.readUInt16LE(6)},t.prototype.compressionMethod=function(){return this.data.readUInt16LE(8)},t.prototype.lastModFileTime=function(){return h(this.data.readUInt16LE(10),this.data.readUInt16LE(12))},t.prototype.crc32=function(){return this.data.readUInt32LE(14)},t.prototype.fileNameLength=function(){return this.data.readUInt16LE(26)},t.prototype.extraFieldLength=function(){return this.data.readUInt16LE(28)},t.prototype.fileName=function(){return l(this.data,this.useUTF8(),30,this.fileNameLength())},t.prototype.extraField=function(){var t=30+this.fileNameLength();return this.data.slice(t,t+this.extraFieldLength())},t.prototype.totalSize=function(){return 30+this.fileNameLength()+this.extraFieldLength()},t.prototype.useUTF8=function(){return 2048===(2048&this.flags())},t}();e.FileHeader=w;var m=function(){function t(t,e,n){this.header=t,this.record=e,this.data=n}return t.prototype.decompress=function(){var t=this.data,e=this.header.compressionMethod();switch(e){case 8:if(t.getBufferCore()instanceof c.BufferCoreArrayBuffer){var r=t.getBufferCore(),i=r.getDataView(),o=i.byteOffset+t.getOffset(),a=new Uint8Array(i.buffer).subarray(o,o+this.record.compressedSize()),s=new y(a).decompress();return new n.Buffer(new c.BufferCoreArrayBuffer(s.buffer),s.byteOffset,s.byteOffset+s.length)}var u=t.slice(0,this.record.compressedSize());return new n.Buffer(new y(u.toJSON().data).decompress());case 0:return t.sliceCopy(0,this.record.uncompressedSize());default:var f=g[e];throw f=f?f:"Unknown: "+e,new d(9,"Invalid compression method on file '"+this.header.fileName()+"': "+f)}},t}();e.FileData=m;var v=function(){function t(t){this.data=t}return t.prototype.crc32=function(){return this.data.readUInt32LE(0)},t.prototype.compressedSize=function(){return this.data.readUInt32LE(4)},t.prototype.uncompressedSize=function(){return this.data.readUInt32LE(8)},t}();e.DataDescriptor=v;var b=function(){function t(t){if(this.data=t,134630224!==this.data.readUInt32LE(0))throw new d(9,"Invalid archive extra data record signature: "+this.data.readUInt32LE(0))}return t.prototype.length=function(){return this.data.readUInt32LE(4)},t.prototype.extraFieldData=function(){return this.data.slice(8,8+this.length())},t}();e.ArchiveExtraDataRecord=b;var E=function(){function t(t){if(this.data=t,84233040!==this.data.readUInt32LE(0))throw new d(9,"Invalid digital signature signature: "+this.data.readUInt32LE(0))}return t.prototype.size=function(){return this.data.readUInt16LE(4)},t.prototype.signatureData=function(){return this.data.slice(6,6+this.size())},t}();e.DigitalSignature=E;var S=function(){function t(t,e){if(this.zipData=t,this.data=e,33639248!==this.data.readUInt32LE(0))throw new d(9,"Invalid Zip file: Central directory record has invalid signature: "+this.data.readUInt32LE(0))}return t.prototype.versionMadeBy=function(){return this.data.readUInt16LE(4)},t.prototype.versionNeeded=function(){return this.data.readUInt16LE(6)},t.prototype.flag=function(){return this.data.readUInt16LE(8)},t.prototype.compressionMethod=function(){return this.data.readUInt16LE(10)},t.prototype.lastModFileTime=function(){return h(this.data.readUInt16LE(12),this.data.readUInt16LE(14))},t.prototype.crc32=function(){return this.data.readUInt32LE(16)},t.prototype.compressedSize=function(){return this.data.readUInt32LE(20)},t.prototype.uncompressedSize=function(){return this.data.readUInt32LE(24)},t.prototype.fileNameLength=function(){return this.data.readUInt16LE(28)},t.prototype.extraFieldLength=function(){return this.data.readUInt16LE(30)},t.prototype.fileCommentLength=function(){return this.data.readUInt16LE(32)},t.prototype.diskNumberStart=function(){return this.data.readUInt16LE(34)},t.prototype.internalAttributes=function(){return this.data.readUInt16LE(36)},t.prototype.externalAttributes=function(){return this.data.readUInt32LE(38)},t.prototype.headerRelativeOffset=function(){return this.data.readUInt32LE(42)},t.prototype.fileName=function(){var t=l(this.data,this.useUTF8(),46,this.fileNameLength());return t.replace(/\\/g,"/")},t.prototype.extraField=function(){var t=44+this.fileNameLength();return this.data.slice(t,t+this.extraFieldLength())},t.prototype.fileComment=function(){var t=46+this.fileNameLength()+this.extraFieldLength();return l(this.data,this.useUTF8(),t,this.fileCommentLength())},t.prototype.totalSize=function(){return 46+this.fileNameLength()+this.extraFieldLength()+this.fileCommentLength()},t.prototype.isDirectory=function(){var t=this.fileName();return(16&this.externalAttributes()?!0:!1)||"/"===t.charAt(t.length-1)},t.prototype.isFile=function(){return!this.isDirectory()},t.prototype.useUTF8=function(){return 2048===(2048&this.flag())},t.prototype.isEncrypted=function(){return 1===(1&this.flag())},t.prototype.getData=function(){var t=this.headerRelativeOffset(),e=new w(this.zipData.slice(t)),n=new m(e,this,this.zipData.slice(t+e.totalSize()));return n.decompress()},t.prototype.getStats=function(){return new a.Stats(32768,this.uncompressedSize(),365,new Date,this.lastModFileTime())},t}();e.CentralDirectory=S;var I=function(){function t(t){if(this.data=t,101010256!==this.data.readUInt32LE(0))throw new d(9,"Invalid Zip file: End of central directory record has invalid signature: "+this.data.readUInt32LE(0))}return t.prototype.diskNumber=function(){return this.data.readUInt16LE(4)},t.prototype.cdDiskNumber=function(){return this.data.readUInt16LE(6)},t.prototype.cdDiskEntryCount=function(){return this.data.readUInt16LE(8)},t.prototype.cdTotalEntryCount=function(){return this.data.readUInt16LE(10)},t.prototype.cdSize=function(){return this.data.readUInt32LE(12)},t.prototype.cdOffset=function(){return this.data.readUInt32LE(16)},t.prototype.cdZipComment=function(){return l(this.data,!0,22,this.data.readUInt16LE(20))},t}();e.EndOfCentralDirectory=I;var _=function(t){function e(e,n){"undefined"==typeof n&&(n=""),t.call(this),this.data=e,this.name=n,this._index=new i.FileIndex,this.populateIndex()}return f(e,t),e.prototype.getName=function(){return"ZipFS"+(""!==this.name?" "+this.name:"")},e.isAvailable=function(){return!0},e.prototype.diskSpace=function(t,e){e(this.data.length,0)},e.prototype.isReadOnly=function(){return!0},e.prototype.supportsLinks=function(){return!1},e.prototype.supportsProps=function(){return!1},e.prototype.supportsSynch=function(){return!0},e.prototype.statSync=function(t){var e=this._index.getInode(t);if(null===e)throw new d(1,""+t+" not found.");var n;return n=e.isFile()?e.getData().getStats():e.getStats()},e.prototype.openSync=function(t,e){if(e.isWriteable())throw new d(0,t);var n=this._index.getInode(t);if(null===n)throw new d(1,""+t+" is not in the FileIndex.");if(n.isDir())throw new d(8,""+t+" is a directory.");var r=n.getData(),i=r.getStats();switch(e.pathExistsAction()){case 1:case 2:throw new d(6,""+t+" already exists.");case 0:return new p.NoSyncFile(this,t,e,i,r.getData());default:throw new d(9,"Invalid FileMode object.")}return null},e.prototype.readdirSync=function(t){var e=this._index.getInode(t);if(null===e)throw new d(1,""+t+" not found.");if(e.isFile())throw new d(7,""+t+" is a file, not a directory.");return e.getListing()},e.prototype.readFileSync=function(t,e,r){var i=this.openSync(t,r,420);try{var o=i,a=o._buffer;return null===e?a.length>0?a.sliceCopy():new n.Buffer(0):a.toString(e)}finally{i.closeSync()}},e.prototype.getEOCD=function(){for(var t=22,e=Math.min(t+65535,this.data.length-1),n=t;e>n;n++)if(101010256===this.data.readUInt32LE(this.data.length-n))return new I(this.data.slice(this.data.length-n));throw new d(9,"Invalid ZIP file: Could not locate End of Central Directory signature.")},e.prototype.populateIndex=function(){var t=this.getEOCD();if(t.diskNumber()!==t.cdDiskNumber())throw new d(9,"ZipFS does not support spanned zip files.");var e=t.cdOffset();if(4294967295===e)throw new d(9,"ZipFS does not support Zip64.");for(var n=e+t.cdSize();n>e;){var r=new S(this.data,this.data.slice(e));e+=r.totalSize();var o=r.fileName();if("/"===o.charAt(0))throw new Error("WHY IS THIS ABSOLUTE");"/"===o.charAt(o.length-1)&&(o=o.substr(0,o.length-1)),r.isDirectory()?this._index.addPath("/"+o,new i.DirInode):this._index.addPath("/"+o,new i.FileInode(r))}},e}(s.SynchronousFileSystem);e.ZipFS=_,o.registerFileSystem("ZipFS",_)}),s("core/global").BrowserFS=s("core/browserfs"),s("generic/emscripten_fs"),s("backend/IndexedDB"),s("backend/XmlHttpRequest"),s("backend/dropbox"),s("backend/html5fs"),s("backend/in_memory"),s("backend/localStorage"),s("backend/mountable_file_system"),s("backend/zipfs")}(); From 5b45ebaac7bfa03f03979be7244a6940301244e7 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 3 Mar 2015 23:52:20 -0800 Subject: [PATCH 35/60] IA's mess/mame config files are loaded from a different url --- loader.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/loader.js b/loader.js index 4072073..07e91f8 100644 --- a/loader.js +++ b/loader.js @@ -155,7 +155,7 @@ var Module = null; get_zip_url(game)))); files.push(cfgr.mountFile('/'+ modulecfg['driver'] + '.cfg', cfgr.fetchOptionalFile("CFG File", - get_zip_url(get_item_name(game) +'/'+ modulecfg['driver'] + '.cfg')))); + get_other_emulator_config_url(module)))); return files; } @@ -175,7 +175,7 @@ var Module = null; get_zip_url(game)))); files.push(cfgr.mountFile('/'+ modulecfg['driver'] + '.cfg', cfgr.fetchOptionalFile("CFG File", - get_zip_url(get_item_name(game) +'/'+ modulecfg['driver'] + '.cfg')))); + get_other_emulator_config_url(module)))); return files; } @@ -193,6 +193,10 @@ var Module = null; return '//archive.org/cors/jsmess_engine_v2/' + module + '.json'; }; + var get_other_emulator_config_url = function (module) { + return '//archive.org/cors/jsmess_config_v2/' + module + '.cfg'; + }; + var get_meta_url = function (game_path) { var path = game_path.split('/'); return "//cors.archive.org/cors/"+ path[0] +"/"+ path[0] +"_meta.xml"; From 5606ee52abbcd5809263638b94dde15242256019 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 3 Mar 2015 23:52:52 -0800 Subject: [PATCH 36/60] use computed style to get width/height of canvas --- loader.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/loader.js b/loader.js index 07e91f8..a63c48d 100644 --- a/loader.js +++ b/loader.js @@ -439,8 +439,8 @@ var Module = null; // the same size that dosbox/jsmess will set it to, but it avoids // the case where the size was left at the default 300x150 if (!canvas.hasAttribute("width")) { - canvas.width = canvas.style.width; - canvas.height = canvas.style.height; + canvas.width = parseInt(getComputedStyle(canvas).width, 10); + canvas.height = parseInt(getComputedStyle(canvas).height, 10); } this.setScale = function(_scale) { From 77591b84418e26e0b5303169f71c15fcdfac950f Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 3 Mar 2015 23:53:06 -0800 Subject: [PATCH 37/60] typo --- loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader.js b/loader.js index a63c48d..d56f931 100644 --- a/loader.js +++ b/loader.js @@ -471,7 +471,7 @@ var Module = null; }; this.setSplashColors = function (colors) { - this.splash.colors = colors; + splash.colors = colors; return this; }; From 3371774d1c505deb858b61c89075856c2b97ffdd Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 3 Mar 2015 23:53:47 -0800 Subject: [PATCH 38/60] finally, we need not be given a promise of a config; a config will work just fine --- loader.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/loader.js b/loader.js index d56f931..848b48a 100644 --- a/loader.js +++ b/loader.js @@ -488,7 +488,13 @@ var Module = null; var k, c, game_data; drawsplash(); - var loading = loadFiles(fetch_file, splash); + var loading + + if (typeof loadFiles === 'function') { + loading = loadFiles(fetch_file, splash); + } else { + loading = Promise.resolve(loadFiles); + } loading.then(function (_game_data) { game_data = _game_data; game_data.fs = new BrowserFS.FileSystem.MountableFileSystem(); From 11804214ca5b6b6694392b38de3c3358ae246637 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Wed, 4 Mar 2015 00:11:02 -0800 Subject: [PATCH 39/60] tweaks to the readme --- README.md | 99 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index d1161f6..96649da 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,27 @@ js-emulators # Synopsis # -The goal of this little project is to make it easy to embed a javascript-based emulator in your own webpage. It downloads the files you specify (with aprogress ui to show what is happening), arranges them to form a filesystem, constructs the necessary arguments for the emulator, handles transitions to and from full-screen mode, and detects and enables game pads. +The goal of this little project is to make it easy to embed a +javascript-based emulator in your own webpage. It downloads the files +you specify (with aprogress ui to show what is happening), arranges +them to form a filesystem, constructs the necessary arguments for the +emulator, handles transitions to and from full-screen mode, and +detects and enables game pads. -To use this project you'll need to provide it with a canvas element, styled as necessary so that it has the correct size on screen (the program will be scaled up automatically to fit, controlling for aspect ratio). You will also likely want to provide a simple UI for entering full-screen mode or muting the audio; these can simply call methods on the emulator when activated. +To use this project you'll need to provide it with a canvas element, +styled as necessary so that it has the correct size on screen (the +program will be scaled up automatically to fit, controlling for aspect +ratio). You will also likely want to provide a simple UI for entering +full-screen mode or muting the audio; these can simply call methods on +the emulator when activated. + +# Emulator API # + +The `Emulator` constructor takes three arguments: a canvas element, an +optional callback (which will be called after fully initializing the +emulator but just before it starts running the emulated program), and +a config (as detailed below) or a function which returns a `Promise` +of a config. # Configuration # @@ -14,43 +32,64 @@ To use this project you'll need to provide it with a canvas element, styled as n ### Arcade game ### -Loads the emulator for the arcade game 1943, and gives it a compressed copy of the rom (assumes that this is in games/1943.zip). +Loads the emulator for the arcade game 1943, and gives it a compressed +copy of the rom (which it loads from examples/1943.zip). - var emulator = new Emulator("#canvas", null, - new JSMAMELoader(JSMAMELoader.driver("1943"), - JSMAMELoader.emulatorJS("emulators/mess1943.js.gz"), - JSMAMELoader.mountFile("1943.zip", - JSMAMELoader.fetchFile("Game File", "games/1943.zip")))) + var emulator = new Emulator(document.querySelector("#canvas"), + null, + new JSMAMELoader(JSMAMELoader.driver("1943"), + JSMAMELoader.emulatorJS("emulators/mess1943.js.gz"), + JSMAMELoader.mountFile("1943.zip", + JSMAMELoader.fetchFile("Game File", "examples/1943.zip")))) ### Console game for Atari 2600 ### -Loads the emulator for the Atari 2600 console, and an image of a catridge for Pitfall. Notice how we download the image, storing it in a file, then set up a "cart" peripheral so that the emulator can find it. We also load a configuration file that preconfigures some keybindings needed to use the 2600. +Loads the emulator for the Atari 2600 console, and an image of a +catridge for Pitfall. Notice how we download the image, storing it in +a file, then set up a "cart" peripheral so that the emulator can find +it. We also load a configuration file that preconfigures some +keybindings needed to use the 2600. - var emulator = new Emulator("#canvas", null, - new JSMESSLoader(JSMESSLoader.driver("a2600"), - JSMESSLoader.emulatorJS("emulators/messa2600.js.gz"), - JSMESSLoader.mountFile("atari_2600_pitfall_1983_cce_c-813.bin", - JSMESSLoader.fetchFile("Game File", - "games/atari_2600_pitfall_1983_cce_c-813.bin")), - JSMESSLoader.mountFile("foo.cfg", - JSMESSLoader.fetchFile("Config File", - "emulators/a2600.cfg")), - JSMESSLoader.peripheral("cart", "atari_2600_pitfall_1983_cce_c-813.bin"))) + var emulator = new Emulator(document.querySelector("#canvas"), + null, + new JSMESSLoader(JSMESSLoader.driver("a2600"), + JSMESSLoader.emulatorJS("emulators/messa2600.js.gz"), + JSMESSLoader.mountFile("Pitfall_Activision_1982.bin", + JSMESSLoader.fetchFile("Game File", + "examples/Pitfall_Activision_1982.bin")), + JSMESSLoader.mountFile("a2600.cfg", + JSMESSLoader.fetchFile("Config File", + "emulators/a2600.cfg")), + JSMESSLoader.peripheral("cart", "Pitfall_Activision_1982.bin"))) ### DOS game ### -Here we load the dosbox emulator, and a zip file containing the game ZZT which we mount as the C drive. We also tell DosBox to immediately start running zzt.exe. +Here we load the dosbox emulator, and a zip file containing the game +ZZT which we decompress and then mount as the C drive. We also tell +DosBox to immediately start running zzt.exe, which is inside the zip. - var emulator = new Emulator("#canvas", null, - new DosBoxLoader(DosBoxLoader.emulatorJS("emulators/dosbox.js.gz"), - DosBoxLoader.mountZip("c", DosBoxLoader.fetchFile("Game File", "games/zzt.zip")), - DosBoxLoader.startExe("zzt.exe"))) + var emulator = new Emulator(document.querySelector("#canvas"), + null, + new DosBoxLoader(DosBoxLoader.emulatorJS("emulators/dosbox.js.gz"), + DosBoxLoader.mountZip("c", + DosBoxLoader.fetchFile("Game File", + "examples/Zzt_1991_Epic_Megagames_Inc.zip")), + DosBoxLoader.startExe("zzt.exe"))) ## Configuration API ## -Currently there are two supported emulators, JSMESS and EM-DosBox. JSMESS provides emulation for arcade games, consoles, and early personal computers. As this emulator supports such a wide variety of hardware it has been broken up into several dozen emulators each supporting one machine lest the resulting javascript be intractably large (60+ megabytes). EM-DosBox provides emulation for software that runs on x86 PCs using the DOS operating systems common to the era. +Currently there are two supported emulators, JSMESS and +EM-DosBox. JSMESS provides emulation for arcade games, consoles, and +early personal computers. As this emulator supports such a wide +variety of hardware it has been broken up into several dozen emulators +each supporting one machine lest the resulting javascript be +intractably large (60+ megabytes). EM-DosBox provides emulation for +software that runs on x86 PCs using the DOS operating systems common +to the era. -Each of these is configured by calling a constructor function and providing it with arguments formed by calling static methods on that same constructor. +Each of these is configured by calling a constructor function and +providing it with arguments formed by calling static methods on that +same constructor. ### Common ### @@ -78,11 +117,14 @@ Each of these is configured by calling a constructor function and providing it w ## Internet Archive ## -There's also a helper for loading software from [the Internet Archive](https://archive.org/v2), `IALoader`. IALoader looks at the metadata associated with their items and uses that to build the configuration for the emulator. +There's also a helper for loading software from +[the Internet Archive](https://archive.org/v2), `IALoader`. IALoader +looks at the metadata associated with an Internet Archive item and +uses that to build the configuration for the emulator. ## Examples ## - var emulator = IALoader("#canvas", "atari_2600_pitfall_1983_cce_c-813/atari_2600_pitfall_1983_cce_c-813.bin"); + var emulator = IALoader("#canvas", "Pitfall_Activision_1982/Pitfall_Activision_1982.bin"); # Runtime API # @@ -91,6 +133,7 @@ Once you have an emulator object, there are several methods you can call. * `start()` * `requestFullScreen()` * `mute()` +* `setSplashColors()` * others… # Known Bugs # From 039154dc8dc7d1befe741d3356ab2b15f554867c Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Fri, 13 Mar 2015 10:29:29 -0700 Subject: [PATCH 40/60] initialize the webaudio wrapper for mess/mame --- loader.js | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/loader.js b/loader.js index 848b48a..26101bd 100644 --- a/loader.js +++ b/loader.js @@ -304,6 +304,7 @@ var Module = null; config.emulator_arguments = build_mess_arguments(config.muted, config.mess_driver, [config.width, config.height], config.sample_rate, config.peripheral, config.extra_mess_args); + config.needs_jsmess_webaudio = true; return config; } JSMESSLoader.__proto__ = BaseLoader; @@ -325,6 +326,7 @@ var Module = null; config.emulator_arguments = build_mame_arguments(config.muted, config.mess_driver, [config.width, config.height], config.sample_rate, config.extra_mess_args); + config.needs_jsmess_webaudio = true; return config; } JSMAMELoader.__proto__ = BaseLoader; @@ -562,6 +564,8 @@ var Module = null; scale = game_data.scale || scale, css_resolution = game_data.nativeResolution || css_resolution, aspectRatio = game_data.aspectRatio || aspectRatio); + if (game_data.needs_jsmess_webaudio) + setup_jsmess_webaudio(); // Emscripten doesn't use the proper prefixed functions for fullscreen requests, // so let's map the prefixed versions to the correct function. @@ -957,3 +961,158 @@ var Module = null; // legacy var JSMESS = JSMESS || {}; JSMESS.ready = function (f) { f(); }; + +function setup_jsmess_webaudio() { + // jsmess web audio backend v0.2 + // katelyn gadd - kg at luminance dot org ; @antumbral on twitter + + var jsmess_web_audio = (function () { + + var context = null; + var gain_node = null; + var buffer_insert_point = null; + var pending_buffers = []; + + var numChannels = 2; // constant in jsmess + var sampleScale = 32766; + var prebufferDuration = 100 / 1000; + + function lazy_init () { + if (context || typeof AudioContext == 'undefined') + return; + + context = new AudioContext(); + + gain_node = context.createGain(); + gain_node.gain.value = 1.0; + gain_node.connect(context.destination); + }; + + function set_mastervolume ( + // even though it's 'attenuation' the value is negative, so... + attenuation_in_decibels + ) { + lazy_init(); + if (!context) return; + + // http://stackoverflow.com/questions/22604500/web-audio-api-working-with-decibels + // seemingly incorrect/broken. figures. welcome to Web Audio + // var gain_web_audio = 1.0 - Math.pow(10, 10 / attenuation_in_decibels); + + // HACK: Max attenuation in JSMESS appears to be 32. + // Hit ' then left/right arrow to test. + // FIXME: This is linear instead of log10 scale. + var gain_web_audio = 1.0 + (+attenuation_in_decibels / +32); + if (gain_web_audio < +0) + gain_web_audio = +0; + else if (gain_web_audio > +1) + gain_web_audio = +1; + + gain_node.gain.value = gain_web_audio; + }; + + function update_audio_stream ( + pBuffer, // pointer into emscripten heap. int16 samples + samples_this_frame // int. number of samples at pBuffer address. + ) { + lazy_init(); + if (!context) return; + + var buffer = context.createBuffer( + numChannels, samples_this_frame, + // JSMESS already initializes its mixer to use the context sampling rate. + context.sampleRate + ); + + for ( + var channel_left = buffer.getChannelData(0), + channel_right = buffer.getChannelData(1), + i = 0, + l = samples_this_frame | 0; + i < l; + i++ + ) { + var offset = + // divide by sizeof(INT16) since pBuffer is offset + // in bytes + ((pBuffer / 2) | 0) + + ((i * 2) | 0); + + var left_sample = HEAP16[offset]; + var right_sample = HEAP16[(offset + 1) | 0]; + + // normalize from signed int16 to signed float + var left_sample_float = left_sample / sampleScale; + var right_sample_float = right_sample / sampleScale; + + channel_left[i] = left_sample_float; + channel_right[i] = right_sample_float; + } + + pending_buffers.push(buffer); + + tick(); + }; + + function tick () { + // Note: this is the time the web audio mixer has mixed up to, + // not the actual current time. + var now = context.currentTime; + + // prebuffering + if (buffer_insert_point === null) { + var total_buffered_seconds = 0; + + for (var i = 0, l = pending_buffers.length; i < l; i++) { + var buffer = pending_buffers[i]; + total_buffered_seconds += buffer.duration; + } + + // Buffer not full enough? abort + if (total_buffered_seconds < prebufferDuration) + return; + } + + // FIXME/TODO: It's possible for us to burn through the whole + // chunk of prebuffered audio. At that point it seems like + // JSMESS never catches up and our sound glitches forever. + + var insert_point = (buffer_insert_point === null) + ? now + : buffer_insert_point; + + if (pending_buffers.length) { + for (var i = 0, l = pending_buffers.length; i < l; i++) { + var buffer = pending_buffers[i]; + + var source_node = context.createBufferSource(); + source_node.buffer = buffer; + source_node.connect(gain_node); + source_node.start(insert_point); + + insert_point += buffer.duration; + } + + pending_buffers.length = 0; + buffer_insert_point = insert_point; + + if (buffer_insert_point <= now) + buffer_insert_point = now; + } + }; + function get_context() { + return context; + }; + + return { + set_mastervolume: set_mastervolume, + update_audio_stream: update_audio_stream, + get_context: get_context + }; + + })(); + + window.jsmess_set_mastervolume = jsmess_web_audio.set_mastervolume; + window.jsmess_update_audio_stream = jsmess_web_audio.update_audio_stream; + window.jsmess_web_audio = jsmess_web_audio; +} From 67652ad976307d31e0821ac8dd015630aa5d4eca Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Fri, 13 Mar 2015 10:31:09 -0700 Subject: [PATCH 41/60] fix filename when saving --- loader.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/loader.js b/loader.js index 26101bd..305cb3e 100644 --- a/loader.js +++ b/loader.js @@ -490,7 +490,7 @@ var Module = null; var k, c, game_data; drawsplash(); - var loading + var loading; if (typeof loadFiles === 'function') { loading = loadFiles(fetch_file, splash); @@ -522,7 +522,7 @@ var Module = null; function saveat(filename) { return function (data) { if (data !== null) { - game_data.fs.writeFileSync(filename, new Buffer(data), null, flag_w, 0x1a4); + game_data.fs.writeFileSync('/'+ filename, new Buffer(data), null, flag_w, 0x1a4); } }; } From c655415da74d05e12f4eea197597e0b480d0e6a3 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Fri, 13 Mar 2015 10:45:11 -0700 Subject: [PATCH 42/60] bare-bones html/js example files to get you started --- README.md | 50 ++++++++++++++++++++++---------------------- example_arcade.html | 22 +++++++++++++++++++ example_console.html | 26 +++++++++++++++++++++++ example_dosbox.html | 22 +++++++++++++++++++ 4 files changed, 95 insertions(+), 25 deletions(-) create mode 100644 example_arcade.html create mode 100644 example_console.html create mode 100644 example_dosbox.html diff --git a/README.md b/README.md index 96649da..c28b504 100644 --- a/README.md +++ b/README.md @@ -35,12 +35,13 @@ of a config. Loads the emulator for the arcade game 1943, and gives it a compressed copy of the rom (which it loads from examples/1943.zip). - var emulator = new Emulator(document.querySelector("#canvas"), - null, - new JSMAMELoader(JSMAMELoader.driver("1943"), - JSMAMELoader.emulatorJS("emulators/mess1943.js.gz"), - JSMAMELoader.mountFile("1943.zip", - JSMAMELoader.fetchFile("Game File", "examples/1943.zip")))) + var emulator = new Emulator(document.querySelector("#canvas"), + null, + new JSMAMELoader(JSMAMELoader.driver("1943"), + JSMAMELoader.emulatorJS("emulators/mess1943.js"), + JSMAMELoader.mountFile("1943.zip", + JSMAMELoader.fetchFile("Game File", + "examples/1943.zip")))) ### Console game for Atari 2600 ### @@ -50,17 +51,17 @@ a file, then set up a "cart" peripheral so that the emulator can find it. We also load a configuration file that preconfigures some keybindings needed to use the 2600. - var emulator = new Emulator(document.querySelector("#canvas"), - null, - new JSMESSLoader(JSMESSLoader.driver("a2600"), - JSMESSLoader.emulatorJS("emulators/messa2600.js.gz"), - JSMESSLoader.mountFile("Pitfall_Activision_1982.bin", - JSMESSLoader.fetchFile("Game File", - "examples/Pitfall_Activision_1982.bin")), - JSMESSLoader.mountFile("a2600.cfg", - JSMESSLoader.fetchFile("Config File", - "emulators/a2600.cfg")), - JSMESSLoader.peripheral("cart", "Pitfall_Activision_1982.bin"))) + var emulator = new Emulator(document.querySelector("#canvas"), + null, + new JSMESSLoader(JSMESSLoader.driver("a2600"), + JSMESSLoader.emulatorJS("emulators/messa2600.js"), + JSMESSLoader.mountFile("Pitfall_Activision_1982.bin", + JSMESSLoader.fetchFile("Game File", + "examples/Pitfall_Activision_1982.bin")), + JSMESSLoader.mountFile("a2600.cfg", + JSMESSLoader.fetchFile("Config File", + "emulators/a2600.cfg")), + JSMESSLoader.peripheral("cart", "Pitfall_Activision_1982.bin"))) ### DOS game ### @@ -68,14 +69,13 @@ Here we load the dosbox emulator, and a zip file containing the game ZZT which we decompress and then mount as the C drive. We also tell DosBox to immediately start running zzt.exe, which is inside the zip. - var emulator = new Emulator(document.querySelector("#canvas"), - null, - new DosBoxLoader(DosBoxLoader.emulatorJS("emulators/dosbox.js.gz"), - DosBoxLoader.mountZip("c", - DosBoxLoader.fetchFile("Game File", - "examples/Zzt_1991_Epic_Megagames_Inc.zip")), - DosBoxLoader.startExe("zzt.exe"))) - + var emulator = new Emulator(document.querySelector("#canvas"), + null, + new DosBoxLoader(DosBoxLoader.emulatorJS("emulators/dosbox.js"), + DosBoxLoader.mountZip("c", + DosBoxLoader.fetchFile("Game File", + "examples/Zzt_1991_Epic_Megagames_Inc.zip")), + DosBoxLoader.startExe("zzt.exe"))) ## Configuration API ## Currently there are two supported emulators, JSMESS and diff --git a/example_arcade.html b/example_arcade.html new file mode 100644 index 0000000..d395cb8 --- /dev/null +++ b/example_arcade.html @@ -0,0 +1,22 @@ + + + + example arcade game + + + + + + + + + diff --git a/example_console.html b/example_console.html new file mode 100644 index 0000000..86b48cf --- /dev/null +++ b/example_console.html @@ -0,0 +1,26 @@ + + + + example console game + + + + + + + + + diff --git a/example_dosbox.html b/example_dosbox.html new file mode 100644 index 0000000..b96e3ca --- /dev/null +++ b/example_dosbox.html @@ -0,0 +1,22 @@ + + + + example dos game + + + + + + + + + From 7f1e9ce7c8ba5a93691e30fc02b598dc472ea9f5 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Fri, 13 Mar 2015 10:50:17 -0700 Subject: [PATCH 43/60] set the loading progress' foreground color when updating the splash screen in case the colors have been inverted --- loader.js | 1 + 1 file changed, 1 insertion(+) diff --git a/loader.js b/loader.js index 305cb3e..91d375c 100644 --- a/loader.js +++ b/loader.js @@ -713,6 +713,7 @@ var Module = null; var table = document.getElementById("dosbox-progress-indicator"); if (table) { table.style.top = (canvas.offsetTop + (canvas.height / 2 + splashimg.height / 2) + 16 - (64/2)) +'px'; + table.style.color = "foreground" in splash.colors ? splash.colors.foreground : 'black'; } if (splash.finished_loading && table) { From 1dda3d145cd3fb3e3ed876bbb27c61fa18630f27 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Fri, 13 Mar 2015 15:41:01 -0700 Subject: [PATCH 44/60] "fix" an error when loading in IA v2 interface Honestly this is a hack. IA is kindly providing us with an object that has split out the item name, file name, and a few other things. We treat it as a path (by calling its special toString() method) and then later split that path into path components to find the item name. Yay. --- loader.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/loader.js b/loader.js index 91d375c..419fb6a 100644 --- a/loader.js +++ b/loader.js @@ -2,6 +2,10 @@ var Module = null; (function (Promise) { function IALoader(canvas, game, callback, scale, splashimg) { + if (typeof game !== 'string') { + game = game.toString(); + } + var SAMPLE_RATE = (function () { var audio_ctx = window.AudioContext || window.webkitAudioContext || false; if (!audio_ctx) { @@ -713,6 +717,7 @@ var Module = null; var table = document.getElementById("dosbox-progress-indicator"); if (table) { table.style.top = (canvas.offsetTop + (canvas.height / 2 + splashimg.height / 2) + 16 - (64/2)) +'px'; + table.style.left = canvas.offsetLeft + (64 + 32) +'px'; table.style.color = "foreground" in splash.colors ? splash.colors.foreground : 'black'; } From 1aea283378e8f04d91630a6b8cc61e3e6af55cbe Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Fri, 13 Mar 2015 15:42:58 -0700 Subject: [PATCH 45/60] swap foreground and background colors on the splash screen to match IA v2 interface --- loader.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/loader.js b/loader.js index 419fb6a..14fdcec 100644 --- a/loader.js +++ b/loader.js @@ -424,8 +424,8 @@ var Module = null; spinning: true, spinner_rotation: 0, finished_loading: false, - colors: { foreground: 'black', - background: 'white' } }; + colors: { foreground: 'white', + background: 'black' } }; var SDL_PauseAudio; this.mute = function (state) { From 78b76eca53f25baada307151248b402a9f380a6a Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Fri, 13 Mar 2015 15:43:19 -0700 Subject: [PATCH 46/60] tweak IALoader example in README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c28b504..fea8a40 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,8 @@ uses that to build the configuration for the emulator. ## Examples ## - var emulator = IALoader("#canvas", "Pitfall_Activision_1982/Pitfall_Activision_1982.bin"); + var emulator = new IALoader(document.querySelector("#canvas"), + "Pitfall_Activision_1982/Pitfall_Activision_1982.bin"); # Runtime API # From 43e7b02fa0f5f758203befdf79e5d2a9d7464dc3 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Sat, 14 Mar 2015 18:40:33 -0700 Subject: [PATCH 47/60] fix the permissions problem when decompressing a zip file --- loader.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/loader.js b/loader.js index 14fdcec..551cf5a 100644 --- a/loader.js +++ b/loader.js @@ -840,7 +840,7 @@ var Module = null; copyDirectory(oldDir, newDir); function copyDirectory(oldDir, newDir) { if (!fs.existsSync(newDir)) { - fs.mkdirSync(newDir); + fs.mkdirSync(newDir, 0777); } fs.readdirSync(oldDir).forEach(function(item) { var p = path.resolve(oldDir, item), @@ -855,7 +855,7 @@ var Module = null; function copyFile(oldFile, newFile) { fs.writeFileSync(newFile, fs.readFileSync(oldFile, null, flag_r), - null, flag_w, 0x1a4); + null, flag_w, 0644); } }; From 812f14cf869be8350062e2de3ebdc86fe7b9e22c Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 17 Mar 2015 04:28:30 -0700 Subject: [PATCH 48/60] git rid of 'raw' param to fetch_file It didn't really do what it sounded like it did; it just converted the response to an Int8Array. This would only work if the result type was either null or 'arraybuffer', and we then have to immediately convert that into a BrowserFS Buffer. Since we can do that directly from an ArrayBuffer, this is just extra work. Additionally, for rediculuous reasons a 0-length Int8Array can't be converted into a Buffer, so we would have a spurious failure if we got a 0-byte response. This then is the very definition of an anti-feature. --- loader.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/loader.js b/loader.js index 551cf5a..f8a369b 100644 --- a/loader.js +++ b/loader.js @@ -26,7 +26,7 @@ var Module = null; return new Promise(function (resolve, reject) { var loading = fetch_file('Game Metadata', get_meta_url(game), - 'document', true); + 'document'); loading.then(function (data) { metadata = data; splash.loading_text = 'Downloading emulator metadata...'; @@ -35,7 +35,7 @@ var Module = null; .textContent; return fetch_file('Emulator Metadata', get_emulator_config_url(module), - 'text', true, true); + 'text', true); }, function () { splash.loading_text = 'Failed to download metadata!'; @@ -510,7 +510,7 @@ var Module = null; if ('data' in file && file.data !== null && typeof file.data !== 'undefined') { return Promise.resolve(file.data); } - return fetch_file(file.title, file.url, null, null, file.optional); + return fetch_file(file.title, file.url, 'arraybuffer', file.optional); } function mountat(drive) { @@ -616,7 +616,7 @@ var Module = null; }; }; - var fetch_file = function(title, url, rt, raw, optional) { + var fetch_file = function(title, url, rt, optional) { var table = document.getElementById("dosbox-progress-indicator"); var row, cell; if (!table) { @@ -640,8 +640,7 @@ var Module = null; xhr.onload = function(e) { if (xhr.status === 200) { cell.textContent = '✔'; - resolve(raw ? xhr.response - : new Int8Array(xhr.response)); + resolve(xhr.response); } }; xhr.onerror = function (e) { From a62d19cdfc8baacb92741de31847595c693b276c Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 17 Mar 2015 04:34:40 -0700 Subject: [PATCH 49/60] correct the positioning of the loading status by appending it not to the end of the document, but into the parent element of the canvas --- loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader.js b/loader.js index f8a369b..632ca72 100644 --- a/loader.js +++ b/loader.js @@ -626,7 +626,7 @@ var Module = null; table.style.top = (canvas.offsetTop + (canvas.height / 2 + splashimg.height / 2) + 16 - (64/2)) +'px'; table.style.left = canvas.offsetLeft + (64 + 32) +'px'; table.style.color = 'foreground' in splash.colors ? splash.colors.foreground : 'black'; - document.documentElement.appendChild(table); + canvas.parentElement.appendChild(table); } row = table.insertRow(-1); cell = row.insertCell(-1); From 671404478098f4cb5ad88108e7c8907985368c75 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 17 Mar 2015 04:48:06 -0700 Subject: [PATCH 50/60] make the 'press any key' pause opt-in --- loader.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/loader.js b/loader.js index 632ca72..86e4aa9 100644 --- a/loader.js +++ b/loader.js @@ -486,10 +486,13 @@ var Module = null; return this; }; - var start = function () { + var start = function (options) { if (has_started) return false; has_started = true; + if (typeof options !== 'object') { + options = {}; + } var k, c, game_data; drawsplash(); @@ -542,14 +545,17 @@ var Module = null; })); }) .then(function (game_files) { - return new Promise(function (resolve, reject) { - splash.loading_text = 'Press any key to continue...'; - splash.spinning = false; + if (options.waitAfterDownloading) { + return new Promise(function (resolve, reject) { + splash.loading_text = '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); - }); + // stashes these event listeners so that we can remove them after + window.addEventListener('keypress', k = keyevent(resolve)); + canvas.addEventListener('click', c = resolve); + }); + } + return Promise.resolve(); }, function () { splash.loading_text = 'Failed to download game data!'; From 61cc734dbc73cf8c18739eb0e6b59adb8af29c57 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 17 Mar 2015 08:09:19 -0700 Subject: [PATCH 51/60] remove launcher.js, now that it's no longer needed --- launcher.js | 115 ---------------------------------------------------- 1 file changed, 115 deletions(-) delete mode 100644 launcher.js diff --git a/launcher.js b/launcher.js deleted file mode 100644 index fb3e153..0000000 --- a/launcher.js +++ /dev/null @@ -1,115 +0,0 @@ -(function () { - function get(name) { - if (typeof(loader_game)=='object') - return loader_game[name]; //alternate case where dont have CGI args to parse... - if ((name = (new RegExp('[?&]'+encodeURIComponent(name)+'=([^&]*)')).exec(location.search))) { - return decodeURIComponent(name[1]); - } - } - - var games; - var emulator; - - function ready() { - var game = loader_game === 'NONE' ? null : loader_game, - scale = get('scale') ? parseFloat(get('scale')) : 1, - canvas = document.getElementById('canvas'), - module = get('module'); - - emulator = new IALoader(canvas, game, null, scale, - (module.indexOf('dosbox') == 0 ? '/images/dosbox.png' : '/images/mame.png')).start(); - - var fullscreenbutton = document.getElementById('gofullscreen'); - if (fullscreenbutton) { - if (emulator.isfullscreensupported()) { - fullscreenbutton.addEventListener('click', function () { emulator.requestFullScreen(); }); - } else { - fullscreenbutton.disabled = true; - } - } - - // Gamepad text - if (detectgamepadsupport()) { - var gamepadDiv = document.getElementById('gamepadtext'); - gamepadDiv.innerHTML = "No gamepads detected. Press a button on a gamepad to use it."; - listenforgamepads(function(gamepads, newgamepad) { - var s = (gamepads.length === 1 ? '' : 's'); - gamepadDiv.innerHTML = gamepads.length + ' gamepad'+s+' detected. If the game does not ' + - 'respond to your gamepad'+s+', refresh the browser and try again.'; - if (emulator.hasStarted) { - gamepadDiv.innerHTML += "
Restart MESS to use new gamepads."; - } - }); - } - } - - - // Firefox will not give us Joystick data unless we register this NOP - // callback. - // https://bugzilla.mozilla.org/show_bug.cgi?id=936104 - addEventListener("gamepadconnected", function() {}); - var getgamepads = navigator.getGamepads || navigator.webkitGamepads || - navigator.mozGamepads || navigator.gamepads || navigator.webkitGetGamepads; - /** - * Does the current browser support the Gamepad API? - * Returns a boolean. - */ - function detectgamepadsupport() { - return typeof getgamepads === 'function'; - } - // The timer that listens for gamepads, in case we ever want to stop it. - var gamepadlistener; - /** - * Listens for new gamepads, and triggers the callback when it detects a - * change. - * The callback is passed an array of active gamepads. - */ - function listenforgamepads(cb, freq) { - // NOP if the browser doesn't support gamepads. - if (!detectgamepadsupport()) return; - // Map from gamepad id to gamepad information. - var prevgamepads = {}; - // DEFAULT: Check gamepads every second. - if (typeof freq === 'undefined') freq = 1000; - gamepadlistener = setInterval(function() { - // Browsers get cranky when you don't apply this on the navigator object. - var gamepads = getgamepads.apply(navigator); - var currentgamepads = {}; - var i; - var hasChanged = false; - for (i = 0; i < gamepads.length; i++) { - var gamepad = gamepads[i]; - if (gamepad != null) { - currentgamepads[gamepad.id] = gamepad; - if (!prevgamepads.hasOwnProperty(gamepad.id)) { - // Gamepad has been added. - hasChanged = true; - } - } - } - - // Has a gamepad been removed? - if (!hasChanged) { - for (var gamepadid in prevgamepads) { - if (!currentgamepads.hasOwnProperty(gamepadid)) { - hasChanged = true; - } - } - } - - prevgamepads = currentgamepads; - - if (hasChanged) { - // Actual gamepads, filtered from gamepads. Chrome puts empty items into - // its gamepadlist. - var actualgamepads = []; - for (i = 0; i < gamepads.length; i++) { - if (gamepads[i] != null) actualgamepads.push(gamepads[i]); - } - cb(actualgamepads); - } - }, freq); - } - - window.addEventListener('load', ready); -})(); From fd8ef99c546b013cbb45b02529e65e88ced06d35 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 24 Mar 2015 15:13:45 -0700 Subject: [PATCH 52/60] add a -conf option to point dosbox to the config file This doesn't seem to cause any problems if the file is missing, which is good. --- loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader.js b/loader.js index 86e4aa9..3252429 100644 --- a/loader.js +++ b/loader.js @@ -391,7 +391,7 @@ var Module = null; }; var build_dosbox_arguments = function (emulator_start, files) { - var args = []; + var args = ['-conf', '/emulator/dosbox.conf']; var len = files.length; for (var i = 0; i < len; i++) { From 57df095229c4590ebbadf80906a9841d6574851f Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Wed, 25 Mar 2015 16:51:24 -0700 Subject: [PATCH 53/60] show download progress on splash screen --- loader.js | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/loader.js b/loader.js index 3252429..be54cb2 100644 --- a/loader.js +++ b/loader.js @@ -622,9 +622,24 @@ var Module = null; }; }; - var fetch_file = function(title, url, rt, optional) { + var formatSize = function (event) { + if (event.lengthComputable) + return "("+ formatBytes(event.loaded) +" of "+ formatBytes(event.total) +")"; + return "("+ formatBytes(event.loaded) +")"; + }; + + var formatBytes = function (bytes, base10) { + 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 table = document.getElementById("dosbox-progress-indicator"); - var row, cell; + var row, statusCell, titleCell, sizeCell; if (!table) { table = document.createElement('table'); table.setAttribute('id', "dosbox-progress-indicator"); @@ -635,26 +650,37 @@ var Module = null; canvas.parentElement.appendChild(table); } row = table.insertRow(-1); - cell = row.insertCell(-1); - cell.textContent = '—'; - row.insertCell(-1).textContent = title; + statusCell = row.insertCell(-1); + statusCell.textContent = '—'; + statusCell.style.width = "1.5em"; + titleCell = row.insertCell(-1); + titleCell.textContent = title; + titleCell.style.paddingRight = "1em"; + sizeCell = row.insertCell(-1); + sizeCell.textContent = '—'; + sizeCell.style.fontSize = "smaller"; return new Promise(function (resolve, reject) { var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = rt ? rt : 'arraybuffer'; - xhr.onload = function(e) { + xhr.onprogress = function (e) { + sizeCell.textContent = formatSize(e); + }; + xhr.onload = function (e) { + sizeCell.textContent = formatSize(e); if (xhr.status === 200) { - cell.textContent = '✔'; + statusCell.textContent = '✔'; resolve(xhr.response); } }; xhr.onerror = function (e) { + sizeCell.textContent = formatSize(e); if (optional) { - cell.textContent = '?'; + statusCell.textContent = '?'; resolve(null); } else { - cell.textContent = '✘'; + statusCell.textContent = '✘'; reject(); } }; From f997324eeceaac2182bc8387d14188c462834eaf Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Wed, 25 Mar 2015 16:53:37 -0700 Subject: [PATCH 54/60] convenient place to list the default option values --- loader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader.js b/loader.js index be54cb2..130da61 100644 --- a/loader.js +++ b/loader.js @@ -491,7 +491,7 @@ var Module = null; return false; has_started = true; if (typeof options !== 'object') { - options = {}; + options = { waitAfterDownloading: false }; } var k, c, game_data; From b5076a16e5c7df98e8254e410d5bf37ecc8f39e3 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Wed, 25 Mar 2015 19:07:58 -0700 Subject: [PATCH 55/60] log of 0 is -Inf, so handle that case separately --- loader.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/loader.js b/loader.js index 130da61..acf8d80 100644 --- a/loader.js +++ b/loader.js @@ -629,6 +629,8 @@ var Module = null; }; 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"], From ea79f25fdb724aa8745997a19375923e1eb3bd2b Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Wed, 25 Mar 2015 19:08:14 -0700 Subject: [PATCH 56/60] show a percentage in the splash screen if we can --- loader.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/loader.js b/loader.js index acf8d80..c2f2162 100644 --- a/loader.js +++ b/loader.js @@ -624,7 +624,10 @@ var Module = null; var formatSize = function (event) { if (event.lengthComputable) - return "("+ formatBytes(event.loaded) +" of "+ formatBytes(event.total) +")"; + return "("+ (event.total ? (event.loaded / event.total * 100).toFixed(0) + : "100") + + "%; "+ formatBytes(event.loaded) + + " of "+ formatBytes(event.total) +")"; return "("+ formatBytes(event.loaded) +")"; }; From 25b3e7b7d56ed723265a4debbc4bf44a3ff984c7 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Thu, 2 Apr 2015 19:15:18 -0700 Subject: [PATCH 57/60] fix scaling behavior --- example_arcade.html | 4 +++- loader.js | 39 ++++++++++++++++++++++++++------------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/example_arcade.html b/example_arcade.html index d395cb8..f171036 100644 --- a/example_arcade.html +++ b/example_arcade.html @@ -4,7 +4,7 @@ example arcade game - + @@ -12,10 +12,12 @@ var emulator = new Emulator(document.querySelector("#canvas"), null, new JSMAMELoader(JSMAMELoader.driver("1943"), + JSMAMELoader.nativeResolution(224, 256), JSMAMELoader.emulatorJS("emulators/mess1943.js"), JSMAMELoader.mountFile("1943.zip", JSMAMELoader.fetchFile("Game File", "examples/1943.zip")))) + emulator.setScale(3); emulator.start(); diff --git a/loader.js b/loader.js index c2f2162..1a8932c 100644 --- a/loader.js +++ b/loader.js @@ -250,7 +250,7 @@ var Module = null; BaseLoader.nativeResolution = function (width, height) { if (typeof width !== 'number' || typeof height !== 'number') throw new Error("Width and height must be numbers"); - return { width: Math.floor(width), height: Math.floor(height) }; + return { nativeResolution: { width: Math.floor(width), height: Math.floor(height) } }; }; BaseLoader.aspectRatio = function (ratio) { @@ -306,8 +306,8 @@ var Module = null; function JSMESSLoader() { var config = Array.prototype.reduce.call(arguments, extend); config.emulator_arguments = build_mess_arguments(config.muted, config.mess_driver, - [config.width, config.height], config.sample_rate, - config.peripheral, config.extra_mess_args); + [config.nativeResolution.width, config.nativeResolution.height], + config.sample_rate, config.peripheral, config.extra_mess_args); config.needs_jsmess_webaudio = true; return config; } @@ -328,8 +328,8 @@ var Module = null; function JSMAMELoader() { var config = Array.prototype.reduce.call(arguments, extend); config.emulator_arguments = build_mame_arguments(config.muted, config.mess_driver, - [config.width, config.height], config.sample_rate, - config.extra_mess_args); + [config.nativeResolution.width, config.nativeResolution.height], + config.sample_rate, config.extra_mess_args); config.needs_jsmess_webaudio = true; return config; } @@ -570,10 +570,6 @@ var Module = null; blockSomeKeys(); setupFullScreen(); disableRightClickContextMenu(canvas); - resizeCanvas(canvas, - scale = game_data.scale || scale, - css_resolution = game_data.nativeResolution || css_resolution, - aspectRatio = game_data.aspectRatio || aspectRatio); if (game_data.needs_jsmess_webaudio) setup_jsmess_webaudio(); @@ -582,7 +578,8 @@ var Module = null; canvas.requestPointerLock = getpointerlockenabler(); moveConfigToRoot(game_data.fs); - Module = init_module(game_data.emulator_arguments, game_data.fs, game_data.locateAdditionalJS); + Module = init_module(game_data.emulator_arguments, game_data.fs, game_data.locateAdditionalJS, + game_data.nativeResolution, game_data.aspectRatio); if (game_data.emulatorJS) { splash.loading_text = 'Launching Emulator'; @@ -599,7 +596,7 @@ var Module = null; }; this.start = start; - var init_module = function(args, fs, locateAdditionalJS) { + var init_module = function(args, fs, locateAdditionalJS, nativeResolution, aspectRatio) { return { arguments: args, screenIsReadOnly: true, print: function (text) { console.log(text); }, @@ -615,6 +612,12 @@ var Module = null; FS.mkdir('/emulator'); FS.mount(BFS, {root: '/'}, '/emulator'); splash.finished_loading = true; + setTimeout(function () { + resizeCanvas(canvas, + scale = scale || scale, + css_resolution = nativeResolution || css_resolution, + aspectRatio = aspectRatio || aspectRatio); + }); if (callback) { window.setTimeout(function() { callback(this); }, 0); } @@ -706,8 +709,18 @@ var Module = null; var resizeCanvas = function (canvas, scale, resolution, aspectRatio) { if (scale && resolution) { - canvas.style.width = resolution.css_width * scale +'px'; - canvas.style.height = resolution.css_height * scale +'px'; + canvas.style.imageRendering = 'optimizeSpeed'; + 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.width = resolution.width * scale +'px'; + canvas.style.height = resolution.height * scale +'px'; + canvas.width = resolution.width; + canvas.height = resolution.height; } }; From 0ec9153d06bc8824f8d85bc42ec9c0819cc2ecf3 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Thu, 2 Apr 2015 19:29:47 -0700 Subject: [PATCH 58/60] handle an unknown native resolution without errors, at least --- loader.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/loader.js b/loader.js index 1a8932c..f35caa7 100644 --- a/loader.js +++ b/loader.js @@ -306,8 +306,8 @@ var Module = null; function JSMESSLoader() { var config = Array.prototype.reduce.call(arguments, extend); config.emulator_arguments = build_mess_arguments(config.muted, config.mess_driver, - [config.nativeResolution.width, config.nativeResolution.height], - config.sample_rate, config.peripheral, config.extra_mess_args); + config.nativeResolution, config.sample_rate, + config.peripheral, config.extra_mess_args); config.needs_jsmess_webaudio = true; return config; } @@ -328,8 +328,8 @@ var Module = null; function JSMAMELoader() { var config = Array.prototype.reduce.call(arguments, extend); config.emulator_arguments = build_mame_arguments(config.muted, config.mess_driver, - [config.nativeResolution.width, config.nativeResolution.height], - config.sample_rate, config.extra_mess_args); + config.nativeResolution, config.sample_rate, + config.extra_mess_args); config.needs_jsmess_webaudio = true; return config; } @@ -348,9 +348,12 @@ var Module = null; '-verbose', '-rompath', 'emulator', '-window', - '-resolution', native_resolution.join('x'), '-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) { @@ -374,9 +377,12 @@ var Module = null; '-verbose', '-rompath', 'emulator', '-window', - '-resolution', native_resolution.join('x'), '-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) { From d73eea21ff28b03bf677aebbeda488159e83f3b3 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Thu, 2 Apr 2015 19:39:17 -0700 Subject: [PATCH 59/60] add native resolutions and waitAfterDownloading option to the examples --- README.md | 58 +++++++++++++++++++++++++------------------- example_arcade.html | 2 +- example_console.html | 5 ++-- example_dosbox.html | 3 ++- 4 files changed, 39 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index fea8a40..4652136 100644 --- a/README.md +++ b/README.md @@ -35,13 +35,16 @@ of a config. Loads the emulator for the arcade game 1943, and gives it a compressed copy of the rom (which it loads from examples/1943.zip). - var emulator = new Emulator(document.querySelector("#canvas"), - null, - new JSMAMELoader(JSMAMELoader.driver("1943"), - JSMAMELoader.emulatorJS("emulators/mess1943.js"), - JSMAMELoader.mountFile("1943.zip", - JSMAMELoader.fetchFile("Game File", - "examples/1943.zip")))) + var emulator = new Emulator(document.querySelector("#canvas"), + null, + new JSMAMELoader(JSMAMELoader.driver("1943"), + JSMAMELoader.nativeResolution(224, 256), + JSMAMELoader.emulatorJS("emulators/mess1943.js"), + JSMAMELoader.mountFile("1943.zip", + JSMAMELoader.fetchFile("Game File", + "examples/1943.zip")))) + emulator.setScale(3); + emulator.start({ waitAfterDownloading: true }); ### Console game for Atari 2600 ### @@ -51,17 +54,19 @@ a file, then set up a "cart" peripheral so that the emulator can find it. We also load a configuration file that preconfigures some keybindings needed to use the 2600. - var emulator = new Emulator(document.querySelector("#canvas"), - null, - new JSMESSLoader(JSMESSLoader.driver("a2600"), - JSMESSLoader.emulatorJS("emulators/messa2600.js"), - JSMESSLoader.mountFile("Pitfall_Activision_1982.bin", - JSMESSLoader.fetchFile("Game File", - "examples/Pitfall_Activision_1982.bin")), - JSMESSLoader.mountFile("a2600.cfg", - JSMESSLoader.fetchFile("Config File", - "emulators/a2600.cfg")), - JSMESSLoader.peripheral("cart", "Pitfall_Activision_1982.bin"))) + var emulator = new Emulator(document.querySelector("#canvas"), + null, + new JSMESSLoader(JSMESSLoader.driver("a2600"), + JSMESSLoader.nativeResolution(352, 223), + JSMESSLoader.emulatorJS("emulators/messa2600.js"), + JSMESSLoader.mountFile("Pitfall_Activision_1982.bin", + JSMESSLoader.fetchFile("Game File", + "examples/Pitfall_Activision_1982.bin")), + JSMESSLoader.mountFile("a2600.cfg", + JSMESSLoader.fetchFile("Config File", + "examples/a2600.cfg")), + JSMESSLoader.peripheral("cart", "Pitfall_Activision_1982.bin"))) + emulator.setScale(3).start({ waitAfterDownloading: true }); ### DOS game ### @@ -69,13 +74,16 @@ Here we load the dosbox emulator, and a zip file containing the game ZZT which we decompress and then mount as the C drive. We also tell DosBox to immediately start running zzt.exe, which is inside the zip. - var emulator = new Emulator(document.querySelector("#canvas"), - null, - new DosBoxLoader(DosBoxLoader.emulatorJS("emulators/dosbox.js"), - DosBoxLoader.mountZip("c", - DosBoxLoader.fetchFile("Game File", - "examples/Zzt_1991_Epic_Megagames_Inc.zip")), - DosBoxLoader.startExe("zzt.exe"))) + var emulator = new Emulator(document.querySelector("#canvas"), + null, + new DosBoxLoader(DosBoxLoader.emulatorJS("emulators/dosbox.js"), + DosBoxLoader.nativeResolution(640, 400), + DosBoxLoader.mountZip("c", + DosBoxLoader.fetchFile("Game File", + "examples/Zzt_1991_Epic_Megagames_Inc.zip")), + DosBoxLoader.startExe("zzt.exe"))) + emulator.start({ waitAfterDownloading: true }); + ## Configuration API ## Currently there are two supported emulators, JSMESS and diff --git a/example_arcade.html b/example_arcade.html index f171036..078eb5c 100644 --- a/example_arcade.html +++ b/example_arcade.html @@ -18,7 +18,7 @@ JSMAMELoader.fetchFile("Game File", "examples/1943.zip")))) emulator.setScale(3); - emulator.start(); + emulator.start({ waitAfterDownloading: true }); diff --git a/example_console.html b/example_console.html index 86b48cf..5f03ceb 100644 --- a/example_console.html +++ b/example_console.html @@ -12,15 +12,16 @@ var emulator = new Emulator(document.querySelector("#canvas"), null, new JSMESSLoader(JSMESSLoader.driver("a2600"), + JSMESSLoader.nativeResolution(352, 223), JSMESSLoader.emulatorJS("emulators/messa2600.js"), JSMESSLoader.mountFile("Pitfall_Activision_1982.bin", JSMESSLoader.fetchFile("Game File", "examples/Pitfall_Activision_1982.bin")), JSMESSLoader.mountFile("a2600.cfg", JSMESSLoader.fetchFile("Config File", - "emulators/a2600.cfg")), + "examples/a2600.cfg")), JSMESSLoader.peripheral("cart", "Pitfall_Activision_1982.bin"))) - emulator.start(); + emulator.setScale(3).start({ waitAfterDownloading: true }); diff --git a/example_dosbox.html b/example_dosbox.html index b96e3ca..8eeb261 100644 --- a/example_dosbox.html +++ b/example_dosbox.html @@ -12,11 +12,12 @@ var emulator = new Emulator(document.querySelector("#canvas"), null, new DosBoxLoader(DosBoxLoader.emulatorJS("emulators/dosbox.js"), + DosBoxLoader.nativeResolution(640, 400), DosBoxLoader.mountZip("c", DosBoxLoader.fetchFile("Game File", "examples/Zzt_1991_Epic_Megagames_Inc.zip")), DosBoxLoader.startExe("zzt.exe"))) - emulator.start(); + emulator.start({ waitAfterDownloading: true }); From 78bdb08eff71e879f56d7440e301c82af13063fa Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Thu, 2 Apr 2015 21:32:14 -0700 Subject: [PATCH 60/60] quasi-document our testcases --- testcases.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 testcases.md diff --git a/testcases.md b/testcases.md new file mode 100644 index 0000000..d0815e8 --- /dev/null +++ b/testcases.md @@ -0,0 +1,20 @@ + [19:14] if you've got a list then I'll be happy to run through it + [19:15] Yeah, I think that's smart going forward. + https://archive.org/details/arcade_astrob + [19:16] Tests: Works with arcade machine, presentation + [19:17] https://archive.org/details/sg_Wiz_n_Liz_1993_Psygnosis_US + Tests: Game Console (Sega Genesis), Very intense processing needs + [19:18] https://archive.org/details/a2_Castle_Smurfenstein_1981_Dead_Smurf_cr + Tests: Apple II performance (computer), sound + [19:19] https://archive.org/details/msdos_Wolfenstein_3D_1992 + Tests: EM-DOSBOX Side, sound, etc. + also parrallel file loads in Smurfenstein + So, at the VERY LEAST + These all shouldwork + If something blows up, there's something wrong. + [19:20] That's a solid test set. + Obviously, Dragon's Lair is our go-to for "holy fuck, large ROM" + [20:31] I'm going to add snack attack to that list, since it's easy to tell when it's running too fast + [01:51] Yes! + +https://archive.org/details/msdos_Snack_Attack_II_1982&external_js=1