diff --git a/www/index.html b/www/index.html
index 2e39476..5df99b2 100644
--- a/www/index.html
+++ b/www/index.html
@@ -7,10 +7,20 @@
OMORI
diff --git a/www/modloader/_oneloader_configuration.js b/www/modloader/_oneloader_configuration.js
index 7fd7275..26831a8 100644
--- a/www/modloader/_oneloader_configuration.js
+++ b/www/modloader/_oneloader_configuration.js
@@ -1,18 +1,39 @@
-if (window.nw.App.argv[0] !== "test") {
- console.log("Loading basic oneloader configuration.");
- const MAX_MANIFEST_VERSION = 1;
- const ID_BLACKLIST = ["gomori"];
- const EXTENSION_RULES = {
- "png": { "encrypt": "rpgmaker", "target_extension": "rpgmvp", "raw": ["img/system/window.png", "img/system/loading.png"] },
- "ogg": { "encrypt": "rpgmaker", "target_extension": "rpgmvo" }
- };
+/*
+ * Which injection targets and which encryption layer are correct depends on how the copy of
+ * the game was distributed:
+ *
+ * steam - assets are AES encrypted and renamed (data/System.KEL, js/plugins/*.OMORI).
+ * plaintext - any non-Steam build: stock RPG Maker MV layout, nothing encrypted.
+ * test - RPG Maker playtest. Also plaintext, but changes more than this file.
+ *
+ * "test" is detected from argv because $modLoader.isInTestMode alters URL handling in the web
+ * VFS too. "steam" is detected from the presence of the encrypted System file rather than from
+ * argv, so a non-Steam copy is not required to be launched with a decryption key it lacks.
+ */
+var $ONELOADER_INSTALL_TYPE = (function () {
+ const fs = require("fs");
+ const path = require("path");
+ const base = path.dirname(process.mainModule.filename);
+
+ if (window.nw.App.argv[0] === "test") return "test";
+ if (fs.existsSync(path.join(base, "data", "System.KEL"))) return "steam";
+ return "plaintext";
+})();
+
+/*
+ * Language folder that text mods are mounted into, read from the game's own
+ * Text_Language_Processor parameters so mods land where the game will look for them.
+ */
+var $ONELOADER_LANGUAGE = (function () {
+ const fs = require("fs");
+ const path = require("path");
+ const base = path.dirname(process.mainModule.filename);
- let LANGUAGE = "en";
+ let language = "en";
try {
- const fs = require("fs");
- const js = fs.readFileSync("www/js/plugins.js", "utf8")
- eval(js.replace("var $plugins", "window.___tmp_plugins"))
- LANGUAGE = window.___tmp_plugins.filter(
+ const js = fs.readFileSync(path.join(base, "js", "plugins.js"), "utf8");
+ eval(js.replace("var $plugins", "window.___tmp_plugins"));
+ language = window.___tmp_plugins.filter(
a => a.name.toLowerCase() === "text_language_processor"
)[0].parameters["Default Language"];
} catch (error) {
@@ -21,6 +42,18 @@ if (window.nw.App.argv[0] !== "test") {
} finally {
window.___tmp_plugins = undefined;
}
+ return language;
+})();
+
+if ($ONELOADER_INSTALL_TYPE === "steam") {
+ console.log("Loading basic oneloader configuration.");
+ const MAX_MANIFEST_VERSION = 1;
+ const ID_BLACKLIST = ["gomori"];
+ const IMAGE_EXTENSION = "rpgmvp";
+ const EXTENSION_RULES = {
+ "png": { "encrypt": "rpgmaker", "target_extension": "rpgmvp", "raw": ["img/system/window.png", "img/system/loading.png"] },
+ "ogg": { "encrypt": "rpgmaker", "target_extension": "rpgmvo" }
+ };
const DATA_RULES = [
{
@@ -50,7 +83,7 @@ if (window.nw.App.argv[0] !== "test") {
"yamld": { target: "HERO", delta: true, delta_method: "yaml", encrypt: true },
"hero": { target: "HERO", delta: false, encrypt: false }
},
- mountPoint: "languages/" + LANGUAGE
+ mountPoint: "languages/" + $ONELOADER_LANGUAGE
},
{
jsonKeys: [
@@ -79,12 +112,14 @@ if (window.nw.App.argv[0] !== "test") {
];
window.$ONELOADER_CONFIG = {
- MAX_MANIFEST_VERSION, ID_BLACKLIST, EXTENSION_RULES, DATA_RULES
+ MAX_MANIFEST_VERSION, ID_BLACKLIST, EXTENSION_RULES, DATA_RULES, IMAGE_EXTENSION,
+ INSTALL_TYPE: $ONELOADER_INSTALL_TYPE
};
} else {
- console.log("Loading playtest configuration");
+ console.log("Loading plaintext configuration");
const MAX_MANIFEST_VERSION = 1;
const ID_BLACKLIST = ["gomori"];
+ const IMAGE_EXTENSION = "png";
const EXTENSION_RULES = {
"png": { "encrypt": "rpgmaker", "target_extension": "png" },
"ogg": { "encrypt": "rpgmaker", "target_extension": "ogg" }
@@ -118,7 +153,7 @@ if (window.nw.App.argv[0] !== "test") {
"yamld": { target: "yaml", delta: true, delta_method: "yaml", encrypt: true },
"hero": { target: "HERO", delta: false, encrypt: false } // Mods that ship encrypted files WILL fail. Don't do that. Not cool.
},
- mountPoint: "languages/en"
+ mountPoint: "languages/" + $ONELOADER_LANGUAGE
},
{
jsonKeys: [
@@ -147,6 +182,7 @@ if (window.nw.App.argv[0] !== "test") {
];
window.$ONELOADER_CONFIG = {
- MAX_MANIFEST_VERSION, ID_BLACKLIST, EXTENSION_RULES, DATA_RULES
+ MAX_MANIFEST_VERSION, ID_BLACKLIST, EXTENSION_RULES, DATA_RULES, IMAGE_EXTENSION,
+ INSTALL_TYPE: $ONELOADER_INSTALL_TYPE
};
-}
\ No newline at end of file
+}
diff --git a/www/modloader/early_loader.js b/www/modloader/early_loader.js
index a6f0fad..49beae3 100644
--- a/www/modloader/early_loader.js
+++ b/www/modloader/early_loader.js
@@ -77,10 +77,18 @@
/* Install the argv handler and shadow the true argv object to allow the base game to work normally */ {
let key = window.nw.App.argv[0];
$modLoader.realArgv = window.nw.App.argv;
+ /*
+ * A non-Steam copy is launched with no arguments at all, so there is no key to keep.
+ * Shadowing with [undefined] would still report a length of 1, and every caller of
+ * Utils.isOptionValid does `nw.App.argv[0].split('&')` once the length check passes -
+ * which throws before Graphics exists and takes the whole boot down. Hide an empty
+ * argv as an empty array instead.
+ */
+ let shadowedArgv = (key === undefined) ? [] : [key];
window.nw.App = new Proxy(window.nw.App, {
get(t, p, r) {
if (p === "argv") {
- return [key];
+ return shadowedArgv;
} else {
return Reflect.get(...arguments);
}
diff --git a/www/modloader/encryption.js b/www/modloader/encryption.js
index 28e3a54..5239131 100644
--- a/www/modloader/encryption.js
+++ b/www/modloader/encryption.js
@@ -1,5 +1,8 @@
const crypto = require('crypto');
-if (window.nw.App.argv[0] !== "test")
+// Only the Steam build is encrypted. Playtest and non-Steam builds keep plaintext assets,
+// so they get the pass-through implementation below instead of an AES layer keyed on a
+// Steam launch argument they were never given.
+if (window.$ONELOADER_CONFIG.INSTALL_TYPE === "steam")
{
window._modloader_encryption = new (class Encryption {
constructor() {
@@ -85,7 +88,7 @@ if (window.nw.App.argv[0] !== "test")
}
})();
} else {
- console.log("Loading fallback encryption system for test mode");
+ console.log("Loading pass-through encryption system for a plaintext install");
window._modloader_encryption = new (class BSEncryption {
decrypt(buffer) { return buffer; }
encrypt(buffer) { return buffer; }
diff --git a/www/modloader/imagedelta_applier.js b/www/modloader/imagedelta_applier.js
index 15f99ac..974186d 100644
--- a/www/modloader/imagedelta_applier.js
+++ b/www/modloader/imagedelta_applier.js
@@ -31,6 +31,10 @@ async function __modloader_image_delta(knownMods) {
const cache_base = path.join(base, '..', '.oneloader-image-cache');
+ // Pictures live under a different extension depending on how the game was distributed
+ // (rpgmvp when encrypted, png otherwise), so deltas have to target whichever is in use.
+ const imageTarget = i => i.replace(/\.png$/g, "." + $ONELOADER_CONFIG.IMAGE_EXTENSION);
+
if (!native_fs.existsSync(cache_base)) {
native_fs.mkdirSync(cache_base);
}
@@ -86,7 +90,7 @@ async function __modloader_image_delta(knownMods) {
if (deltas.length < 1) continue;
- const image_data = _modloader_encryption.decryptAsset(await _vfs_resolve_file(image.replace(/\.png$/g, '.rpgmvp')));
+ const image_data = _modloader_encryption.decryptAsset(await _vfs_resolve_file(imageTarget(image)));
job_id = `${job_id}+${shaDigest(image_data)}`;
job_id = `${job_id}:${deltas.sort((a, b) => {return a.salt > b.salt?1:-1}).map(a => a.salt).join(":")}`;
@@ -163,8 +167,8 @@ async function __modloader_image_delta(knownMods) {
$oneLoaderGui.inc();
- let oDeep = _ensure_overlay_path($modLoader.overlayFS, image.replace(/\.png$/g, '.rpgmvp').toLowerCase());
- let bruh = _overlay_fs_split_path(image.replace(/\.png$/g, '.rpgmvp').toLowerCase());
+ let oDeep = _ensure_overlay_path($modLoader.overlayFS, imageTarget(image).toLowerCase());
+ let bruh = _overlay_fs_split_path(imageTarget(image).toLowerCase());
let lastFile = bruh[bruh.length - 1];
oDeep[lastFile] = {
@@ -173,9 +177,9 @@ async function __modloader_image_delta(knownMods) {
type: "filesystem"
},
delta: false,
- injectionPoint: image.replace(/\.png$/g, '.rpgmvp').toLowerCase(),
+ injectionPoint: imageTarget(image).toLowerCase(),
mode: "rpgmaker",
- ogName: path.basename(image.replace(/\.png$/g, '.rpgmvp'))
+ ogName: path.basename(imageTarget(image))
};
}
}
\ No newline at end of file
diff --git a/www/modloader/start_game.js b/www/modloader/start_game.js
index a92662d..177c115 100644
--- a/www/modloader/start_game.js
+++ b/www/modloader/start_game.js
@@ -5,6 +5,9 @@ async function _start_game() {
"js/libs/pixi.js",
"js/libs/pixi-tilemap.js",
"js/libs/pixi-picture.js",
+ // Part of the stock RPG Maker MV library set and shipped with the game, but absent
+ // from this list. Graphics._createFPSMeter uses the FPSMeter global it defines.
+ "js/libs/fpsmeter.js",
"js/libs/lz-string.js",
"js/libs/iphone-inline-video.browser.js",
"js/rpg_core.js",
@@ -48,10 +51,35 @@ async function _start_game() {
await p;
}
}
+ let pluginScriptLoads = [];
+
for (let script of scripts) {
$oneLoaderGui.inc();
$oneLoaderGui.setHt("This may take up to 25 seconds", true);
await rafResolve();
+
+ /*
+ * main.js calls PluginManager.setup, which appends one