diff --git a/HtmlRuntime/quixe/gi_load.js b/HtmlRuntime/quixe/gi_load.js new file mode 100644 index 0000000..11eecd2 --- /dev/null +++ b/HtmlRuntime/quixe/gi_load.js @@ -0,0 +1,546 @@ +/* GiLoad -- a game-file loader for Quixe + * Designed by Andrew Plotkin + * + * + * This Javascript library is copyright 2010-2012 by Andrew Plotkin. You may + * copy and distribute it freely, by any means and under any conditions, + * as long as the code and documentation is not changed. You may also + * incorporate this code into your own program and distribute that, or + * modify this code and use and distribute the modified version, as long + * as you retain a notice in your program or documentation which mentions + * my name and the URL shown above. + * + * This library loads a game image (by one of several possible methods) + * and then starts up the display layer and game engine. It also extracts + * data from a Blorb image, if that's what's provided. + * + * (This code makes use of the Prototype library, which therefore must be + * available.) + * + * When you are putting together a Quixe installation page, you call + * GiLoad.load_run() to get the game started. You should do this in the + * document's "onload" handler, or later. (If you call it before "onload" + * time, it may not work.) + * + * You can do this in a couple of different ways: + * + * GiLoad.load_run(OPTIONS) -- load and run the game using the options + * passed as the argument. If OPTIONS is null or not provided, the + * global "game_options" object is considered. (The various options are + * described below.) + * + * GiLoad.load_run(OPTIONS, IMAGE, IMAGE_FORMAT) -- run the game with the + * given options. The IMAGE argument should be the game file itself + * (a glulx or blorb file). IMAGE_FORMAT describes how the game file + * is encoded: + * "base64": a base64-encoded binary file + * "raw": a binary file stored in a string + * "array": an array of (numeric) byte values + * Again, if OPTIONS is null, the global "game_options" object is + * considered. + * + * These are the game options. Most have default values, so you only have + * to declare the ones you want to change. + * + * use_query_story: If this is true, you (or the player) can use a + * "?story=..." URL parameter to load any game file. If it is false, + * this parameter is ignored. (default: true) + * set_page_title: If true, the loader will change the document title + * to describe the game being loaded. If false, the document title + * will be left alone. (default: true) + * default_story: The URL of the game file to load, if not otherwise + * provided. + * proxy_url: The URL of the web-app service which is used to convert + * binary data to Javascript, if the browser needs that. (default: + * http://zcode.appspot.com/proxy/) + * vm: The game engine interface object. (default: Quixe) + * io: The display layer interface object. (default: Glk) + * + * You can also include any of the display options used by the GlkOte + * library, such as gameport, windowport, spacing, ... + * And also the interpreter options used by the Quixe library, such as + * rethrow_exceptions, ... + * + * GiLoad.find_data_chunk(NUM) -- this finds the Data chunk of the + * given number from the blorb file. The returned object looks like + * { data:[...], type:"..." } (where the type is TEXT or BINA). + * If there was no such chunk, or if the game was loaded from a non- + * blorb file, this returns undefined. + */ + +/* Put everything inside the GiLoad namespace. */ +GiLoad = function() { + +/* Start with the defaults. These can be modified later by the game_options + defined in the HTML file. + + Note that the "vm" and "io" entries are not filled in here, because + we don't know whether the Quixe or Glk libraries were loaded before + this one. We'll fill them in at load_run() time. +*/ +var all_options = { + vm: null, // default game engine (Quixe) + io: null, // default display layer (Glk) + spacing: 4, // default spacing between windows + use_query_story: true, // use the ?story= URL parameter (if provided) + default_story: null, // story URL to use if not otherwise set + set_page_title: true, // set the window title to the game name + proxy_url: 'http://zcode.appspot.com/proxy/' +}; + +var gameurl = null; /* The URL we are loading. */ +var metadata = {}; /* Title, author, etc -- loaded from Blorb */ +var datachunks = {}; /* Indexed by filenum -- loaded from Blorb */ + +/* Begin the loading process. This is what you call to start a game; + it takes care of starting the Glk and Quixe modules, when the game + file is available. +*/ +function load_run(optobj, image, image_format) { + + /* Set the default entries for the interface objects that come from + other libraries. (If no such libraries have been loaded, then + these do nothing, but game_options can still supply these entries.) */ + all_options.vm = window.Quixe; + all_options.io = window.Glk; + + if (!optobj) + optobj = window.game_options; + if (optobj) + Object.extend(all_options, optobj); /* Prototype-ism */ + + /* The first question is, what's the game file URL? */ + + gameurl = null; + + if (all_options.use_query_story) { + /* Use ?story= URL parameter, if present and accepted. */ + var qparams = get_query_params(); + gameurl = qparams['story']; + } + + if (!gameurl && image) { + /* The story data is already loaded -- it's not an a URL at all. + Decode it, and then fire it off. */ + GlkOte.log('### trying pre-loaded load (' + image_format + ')...'); + switch (image_format) { + case 'base64': + image = decode_base64(image); + break; + case 'raw': + image = decode_text(image); + break; + case 'array': + /* Leave image alone */ + break; + default: + all_options.io.fatal_error("Could not decode story file data: " + image_format); + return; + } + + start_game(image); + return; + } + + if (!gameurl) { + /* Go with the "default_story" option parameter, if present. */ + gameurl = all_options.default_story; + } + + if (!gameurl) { + all_options.io.fatal_error("No story file specified!"); + return; + } + + GlkOte.log('### gameurl: ' + gameurl); //### + /* The gameurl is now known. (It should not change after this point.) + The next question is, how do we load it in? */ + + /* If an image file was passed in, we didn't use it. So we might as + well free its memory at this point. */ + image = null; + image_format = null; + + /* The logic of the following code is adapted from Parchment's + file.js. */ + + var xhr = Ajax.getTransport(); + var binary_supported = (xhr.overrideMimeType !== undefined && !Prototype.Browser.Opera); + /* I'm told that Opera's overrideMimeType() doesn't work. */ + var crossorigin_supported = (xhr.withCredentials !== undefined); + xhr = null; + + var regex_urldomain = /^(file:|runtime:|(\w+:)?\/\/[^\/?#]+)/; + var page_domain = regex_urldomain.exec(location)[0]; + var data_exec = regex_urldomain.exec(gameurl); + var is_relative = data_exec ? false : true; + var data_domain = data_exec ? data_exec[0] : page_domain; + + var same_origin = (page_domain == data_domain); + if (navigator.userAgent.match(/chrome/i) && data_domain == 'file:') { + /* Chrome enforces a stricter same-origin policy for file: URLs -- + it doesn't want to trawl your hard drive for random files. + Other browsers may pick this up someday, but for now, it's + only Chrome. */ + same_origin = false; + } + var old_js_url = gameurl.toLowerCase().endsWith('.js'); + + GlkOte.log('### is_relative=' + is_relative + ', same_origin=' + same_origin + ', binary_supported=' + binary_supported + ', crossorigin_supported=' + crossorigin_supported); + + if (old_js_url && same_origin) { + /* Old-fashioned Javascript file -- the output of Parchment's + zcode2js tool. When loaded and eval'ed, this will call + a global function processBase64Zcode() with base64 data + as the argument. */ + GlkOte.log('### trying old-fashioned load...'); + window.processBase64Zcode = function(val) { + start_game(decode_base64(val)); + }; + new Ajax.Request(gameurl, { + method: 'get', + evalJS: 'force', + onFailure: function(resp) { + all_options.io.fatal_error("The story could not be loaded. (" + gameurl + "): Error " + resp.status + ": " + resp.statusText); + } + }); + return; + } + + if (old_js_url) { + /* Javascript file in a different domain. We'll insert it as a