968 lines
32 KiB
JavaScript
968 lines
32 KiB
JavaScript
/*
|
|
* Copyright (c) 2024 lax1dude. All Rights Reserved.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
const eagruntimeImpl = {
|
|
WASMGCBufferAllocator: {},
|
|
platformApplication: {},
|
|
platformAssets: {},
|
|
platformAudio: {},
|
|
platformFilesystem: {},
|
|
platformInput: {},
|
|
platformNetworking: {},
|
|
platformOpenGL: {},
|
|
platformRuntime: {},
|
|
platformScreenRecord: {},
|
|
platformVoiceClient: {},
|
|
platformWebRTC: {},
|
|
platformWebView: {},
|
|
clientPlatformSingleplayer: {},
|
|
serverPlatformSingleplayer: {}
|
|
};
|
|
|
|
/** @type {WebAssembly.Module} */
|
|
var classesWASMModule = null;
|
|
/** @type {WebAssembly.Module} */
|
|
var classesDeobfWASMModule = null;
|
|
/** @type {Int8Array} */
|
|
var classesTEADBG = null;
|
|
/** @type {function(Array<number>):Array<Object>|null} */
|
|
var deobfuscatorFunc = null;
|
|
/** @type {Array} */
|
|
var epkFileList = null;
|
|
/** @type {string|null} */
|
|
var splashURL = null;
|
|
/** @type {string|null} */
|
|
var pressAnyKeyURL = null;
|
|
/** @type {string|null} */
|
|
var crashURL = null;
|
|
/** @type {string|null} */
|
|
var faviconURL = null;
|
|
/** @type {Object} */
|
|
var eaglercraftXOpts = null;
|
|
/** @type {string|null} */
|
|
var eagRuntimeJSURL = null;
|
|
/** @type {HTMLElement} */
|
|
var rootElement = null;
|
|
/** @type {HTMLElement} */
|
|
var parentElement = null;
|
|
/** @type {HTMLCanvasElement} */
|
|
var canvasElement = null;
|
|
/** @type {WebGL2RenderingContext} */
|
|
var webglContext = null;
|
|
/** @type {boolean} */
|
|
var webglExperimental = false;
|
|
/** @type {number} */
|
|
var webglGLESVer = 0;
|
|
/** @type {AudioContext} */
|
|
var audioContext = null;
|
|
/** @type {WebAssembly.Memory} */
|
|
var heapMemory = null;
|
|
/** @type {ArrayBuffer} */
|
|
var heapArrayBuffer = null;
|
|
/** @type {Uint8Array} */
|
|
var heapU8Array = null;
|
|
/** @type {Int8Array} */
|
|
var heapI8Array = null;
|
|
/** @type {Uint16Array} */
|
|
var heapU16Array = null;
|
|
/** @type {Int16Array} */
|
|
var heapI16Array = null;
|
|
/** @type {Int32Array} */
|
|
var heapI32Array = null;
|
|
/** @type {Uint32Array} */
|
|
var heapU32Array = null;
|
|
/** @type {Float32Array} */
|
|
var heapF32Array = null;
|
|
/** @type {boolean} */
|
|
var isLikelyMobileBrowser = false;
|
|
/** @type {function(string, !ArrayBuffer)|null} */
|
|
var serverLANPeerPassIPCFunc = null;
|
|
/** @type {function(string, !ArrayBuffer)|null} */
|
|
var sendIPCPacketFunc = null;
|
|
/** @type {boolean} */
|
|
var isCrashed = false;
|
|
/** @type {Array<string>} */
|
|
const crashReportStrings = [];
|
|
/** @type {function()|null} */
|
|
var removeEventHandlers = null;
|
|
|
|
const runtimeOpts = {
|
|
localStorageNamespace: "_eaglercraftX",
|
|
openDebugConsoleOnLaunch: false,
|
|
fixDebugConsoleUnloadListener: false,
|
|
forceWebViewSupport: false,
|
|
enableWebViewCSP: true,
|
|
forceWebGL1: false,
|
|
forceWebGL2: false,
|
|
allowExperimentalWebGL1: true,
|
|
useWebGLExt: true,
|
|
useDelayOnSwap: false
|
|
};
|
|
|
|
function setupRuntimeOpts() {
|
|
if(typeof eaglercraftXOpts["localStorageNamespace"] === "string") runtimeOpts.localStorageNamespace = eaglercraftXOpts["localStorageNamespace"];
|
|
if(typeof eaglercraftXOpts["openDebugConsoleOnLaunch"] === "boolean") runtimeOpts.openDebugConsoleOnLaunch = eaglercraftXOpts["openDebugConsoleOnLaunch"];
|
|
if(typeof eaglercraftXOpts["fixDebugConsoleUnloadListener"] === "boolean") runtimeOpts.fixDebugConsoleUnloadListener = eaglercraftXOpts["fixDebugConsoleUnloadListener"];
|
|
if(typeof eaglercraftXOpts["forceWebViewSupport"] === "boolean") runtimeOpts.forceWebViewSupport = eaglercraftXOpts["forceWebViewSupport"];
|
|
if(typeof eaglercraftXOpts["enableWebViewCSP"] === "boolean") runtimeOpts.enableWebViewCSP = eaglercraftXOpts["enableWebViewCSP"];
|
|
if(typeof eaglercraftXOpts["forceWebGL1"] === "boolean") runtimeOpts.forceWebGL1 = eaglercraftXOpts["forceWebGL1"];
|
|
if(typeof eaglercraftXOpts["forceWebGL2"] === "boolean") runtimeOpts.forceWebGL2 = eaglercraftXOpts["forceWebGL2"];
|
|
if(typeof eaglercraftXOpts["allowExperimentalWebGL1"] === "boolean") runtimeOpts.allowExperimentalWebGL1 = eaglercraftXOpts["allowExperimentalWebGL1"];
|
|
if(typeof eaglercraftXOpts["useWebGLExt"] === "boolean") runtimeOpts.useWebGLExt = eaglercraftXOpts["useWebGLExt"];
|
|
if(typeof eaglercraftXOpts["useDelayOnSwap"] === "boolean") runtimeOpts.useDelayOnSwap = eaglercraftXOpts["useDelayOnSwap"];
|
|
}
|
|
|
|
/**
|
|
* @return {!Promise<boolean>}
|
|
*/
|
|
async function initializeContext() {
|
|
setupRuntimeOpts();
|
|
|
|
currentRedirectorFunc = addLogMessageImpl;
|
|
|
|
if(window.__isEaglerX188UnloadListenerSet !== "yes") {
|
|
window.onbeforeunload = function(evt) {
|
|
if(window.__curEaglerX188UnloadListenerCB) {
|
|
window.__curEaglerX188UnloadListenerCB();
|
|
}
|
|
return false;
|
|
};
|
|
window.__isEaglerX188UnloadListenerSet = "yes";
|
|
}
|
|
|
|
eagInfo("Initializing EagRuntime JS context...");
|
|
|
|
await initializePlatfRuntime();
|
|
initializePlatfApplication(eagruntimeImpl.platformApplication);
|
|
initializePlatfScreenRecord(eagruntimeImpl.platformScreenRecord);
|
|
initializePlatfVoiceClient(eagruntimeImpl.platformVoiceClient);
|
|
initializePlatfWebRTC(eagruntimeImpl.platformWebRTC);
|
|
initializePlatfWebView(eagruntimeImpl.platformWebView);
|
|
initializeClientPlatfSP(eagruntimeImpl.clientPlatformSingleplayer);
|
|
initializeNoServerPlatfSP(eagruntimeImpl.serverPlatformSingleplayer);
|
|
|
|
rootElement.classList.add("_eaglercraftX_root_element");
|
|
rootElement.style.overflow = "hidden";
|
|
|
|
/** @type {HTMLElement} */
|
|
var oldSplash = null;
|
|
|
|
var node;
|
|
while(node = rootElement.lastChild) {
|
|
if(!oldSplash) {
|
|
oldSplash = /** @type {HTMLElement} */ (node);
|
|
}
|
|
rootElement.removeChild(node);
|
|
}
|
|
|
|
parentElement = /** @type {HTMLElement} */ (document.createElement("div"));
|
|
parentElement.classList.add("_eaglercraftX_wrapper_element");
|
|
parentElement.style.position = "relative";
|
|
parentElement.style.width = "100%";
|
|
parentElement.style.height = "100%";
|
|
parentElement.style.overflow = "hidden";
|
|
parentElement.style.backgroundColor = "black";
|
|
rootElement.appendChild(parentElement);
|
|
|
|
if(oldSplash) {
|
|
oldSplash.style.position = "absolute";
|
|
oldSplash.style.top = "0px";
|
|
oldSplash.style.left = "0px";
|
|
oldSplash.style.right = "0px";
|
|
oldSplash.style.bottom = "0px";
|
|
oldSplash.style.zIndex = "2";
|
|
oldSplash.classList.add("_eaglercraftX_early_splash_element");
|
|
parentElement.appendChild(oldSplash);
|
|
}
|
|
|
|
await promiseTimeout(10);
|
|
|
|
const d = window.devicePixelRatio;
|
|
const iw = parentElement.clientWidth;
|
|
const ih = parentElement.clientHeight;
|
|
const sw = (d * iw) | 0;
|
|
const sh = (d * ih) | 0;
|
|
const canvasW = sw;
|
|
const canvasH = sh;
|
|
|
|
eagInfo("Initializing audio context");
|
|
|
|
if(typeof document.exitPointerLock === "function") {
|
|
var ua = navigator.userAgent;
|
|
if(ua !== null) {
|
|
ua = ua.toLowerCase();
|
|
isLikelyMobileBrowser = ua.indexOf("mobi") !== -1 || ua.indexOf("tablet") !== -1;
|
|
}else {
|
|
isLikelyMobileBrowser = false;
|
|
}
|
|
}else {
|
|
isLikelyMobileBrowser = true;
|
|
}
|
|
|
|
var audioCtx = null;
|
|
|
|
const createAudioContext = function() {
|
|
try {
|
|
audioCtx = new AudioContext();
|
|
}catch(ex) {
|
|
eagStackTrace(ERROR, "Could not initialize audio context", ex);
|
|
}
|
|
};
|
|
|
|
if(isLikelyMobileBrowser || !navigator.userActivation || !navigator.userActivation.hasBeenActive) {
|
|
const pressAnyKeyImage = /** @type {HTMLElement} */ (document.createElement("div"));
|
|
pressAnyKeyImage.classList.add("_eaglercraftX_press_any_key_image");
|
|
pressAnyKeyImage.style.position = "absolute";
|
|
pressAnyKeyImage.style.top = "0px";
|
|
pressAnyKeyImage.style.left = "0px";
|
|
pressAnyKeyImage.style.right = "0px";
|
|
pressAnyKeyImage.style.bottom = "0px";
|
|
pressAnyKeyImage.style.width = "100%";
|
|
pressAnyKeyImage.style.height = "100%";
|
|
pressAnyKeyImage.style.zIndex = "3";
|
|
pressAnyKeyImage.style.touchAction = "pan-x pan-y";
|
|
pressAnyKeyImage.style.background = "center / contain no-repeat url(\"" + pressAnyKeyURL + "\"), left / 1000000% 100% no-repeat url(\"" + pressAnyKeyURL + "\") white";
|
|
pressAnyKeyImage.style.setProperty("image-rendering", "pixelated");
|
|
parentElement.appendChild(pressAnyKeyImage);
|
|
|
|
await new Promise(function(resolve, reject) {
|
|
var resolved = false;
|
|
var mobilePressAnyKeyScreen;
|
|
var createAudioContextHandler = function() {
|
|
if(!resolved) {
|
|
resolved = true;
|
|
if(isLikelyMobileBrowser) {
|
|
parentElement.removeChild(mobilePressAnyKeyScreen);
|
|
}else {
|
|
window.removeEventListener("keydown", /** @type {function(Event)} */ (createAudioContextHandler));
|
|
parentElement.removeEventListener("mousedown", /** @type {function(Event)} */ (createAudioContextHandler));
|
|
parentElement.removeEventListener("touchstart", /** @type {function(Event)} */ (createAudioContextHandler));
|
|
}
|
|
try {
|
|
createAudioContext();
|
|
}catch(ex) {
|
|
reject(ex);
|
|
return;
|
|
}
|
|
resolve();
|
|
}
|
|
};
|
|
if(isLikelyMobileBrowser) {
|
|
mobilePressAnyKeyScreen = /** @type {HTMLElement} */ (document.createElement("div"));
|
|
mobilePressAnyKeyScreen.classList.add("_eaglercraftX_mobile_press_any_key");
|
|
mobilePressAnyKeyScreen.setAttribute("style", "position:absolute;background-color:white;font-family:sans-serif;top:10%;left:10%;right:10%;bottom:10%;border:5px double black;padding:calc(5px + 7vh) 15px;text-align:center;font-size:20px;user-select:none;z-index:10;");
|
|
mobilePressAnyKeyScreen.innerHTML = "<h3 style=\"margin-block-start:0px;margin-block-end:0px;margin:20px 5px;\">Mobile Browser Detected</h3>"
|
|
+ "<p style=\"margin-block-start:0px;margin-block-end:0px;margin:20px 5px;\">Warning: EaglercraftX WASM-GC requires a lot of memory and may not be stable on most mobile devices!</p>"
|
|
+ "<p style=\"margin-block-start:0px;margin-block-end:0px;margin:20px 2px;\"><button style=\"font: 24px sans-serif;font-weight:bold;\" class=\"_eaglercraftX_mobile_launch_client\">Launch EaglercraftX</button></p>"
|
|
/*+ (allowBootMenu ? "<p style=\"margin-block-start:0px;margin-block-end:0px;margin:20px 2px;\"><button style=\"font: 24px sans-serif;\" class=\"_eaglercraftX_mobile_enter_boot_menu\">Enter Boot Menu</button></p>" : "")*/
|
|
+ "<p style=\"margin-block-start:0px;margin-block-end:0px;margin:25px 5px;\">(Tablets and phones with large screens work best)</p>";
|
|
mobilePressAnyKeyScreen.querySelector("._eaglercraftX_mobile_launch_client").addEventListener("click", /** @type {function(Event)} */ (createAudioContextHandler));
|
|
parentElement.appendChild(mobilePressAnyKeyScreen);
|
|
}else {
|
|
window.addEventListener("keydown", /** @type {function(Event)} */ (createAudioContextHandler));
|
|
parentElement.addEventListener("mousedown", /** @type {function(Event)} */ (createAudioContextHandler));
|
|
parentElement.addEventListener("touchstart", /** @type {function(Event)} */ (createAudioContextHandler));
|
|
}
|
|
});
|
|
|
|
parentElement.removeChild(pressAnyKeyImage);
|
|
}else {
|
|
createAudioContext();
|
|
}
|
|
|
|
if(audioCtx) {
|
|
setCurrentAudioContext(audioCtx, eagruntimeImpl.platformAudio);
|
|
}else {
|
|
setNoAudioContext(eagruntimeImpl.platformAudio);
|
|
}
|
|
|
|
eagInfo("Creating main canvas");
|
|
|
|
canvasElement = /** @type {HTMLCanvasElement} */ (document.createElement("canvas"));
|
|
canvasElement.classList.add("_eaglercraftX_canvas_element");
|
|
canvasElement.style.width = "100%";
|
|
canvasElement.style.height = "100%";
|
|
canvasElement.style.zIndex = "1";
|
|
canvasElement.style.touchAction = "pan-x pan-y";
|
|
canvasElement.style.setProperty("-webkit-touch-callout", "none");
|
|
canvasElement.style.setProperty("-webkit-tap-highlight-color", "rgba(255, 255, 255, 0)");
|
|
canvasElement.style.setProperty("image-rendering", "pixelated");
|
|
|
|
canvasElement.width = canvasW;
|
|
canvasElement.height = canvasH;
|
|
|
|
parentElement.appendChild(canvasElement);
|
|
|
|
await initPlatformInput(eagruntimeImpl.platformInput);
|
|
|
|
eagInfo("Creating WebGL context");
|
|
|
|
parentElement.addEventListener("webglcontextcreationerror", function(evt) {
|
|
eagError("[WebGL Error]: {}", evt.statusMessage);
|
|
});
|
|
|
|
/** @type {Object} */
|
|
const contextCreationHints = {
|
|
"antialias": false,
|
|
"depth": false,
|
|
"powerPreference": "high-performance",
|
|
"desynchronized": true,
|
|
"preserveDrawingBuffer": false,
|
|
"premultipliedAlpha": false,
|
|
"alpha": false
|
|
};
|
|
|
|
/** @type {number} */
|
|
var glesVer;
|
|
/** @type {boolean} */
|
|
var experimental = false;
|
|
/** @type {WebGL2RenderingContext|null} */
|
|
var webgl_;
|
|
if(runtimeOpts.forceWebGL2) {
|
|
eagInfo("Note: Forcing WebGL 2.0 context");
|
|
glesVer = 300;
|
|
webgl_ = /** @type {WebGL2RenderingContext} */ (canvasElement.getContext("webgl2", contextCreationHints));
|
|
if(!webgl_) {
|
|
showIncompatibleScreen("WebGL 2.0 is not supported on this device!");
|
|
return false;
|
|
}
|
|
}else {
|
|
if(runtimeOpts.forceWebGL1) {
|
|
eagInfo("Note: Forcing WebGL 1.0 context");
|
|
glesVer = 200;
|
|
webgl_ = /** @type {WebGL2RenderingContext} */ (canvasElement.getContext("webgl", contextCreationHints));
|
|
if(!webgl_) {
|
|
if(runtimeOpts.allowExperimentalWebGL1) {
|
|
experimental = true;
|
|
webgl_ = /** @type {WebGL2RenderingContext} */ (canvasElement.getContext("experimental-webgl", contextCreationHints));
|
|
if(!webgl_) {
|
|
showIncompatibleScreen("WebGL is not supported on this device!");
|
|
return false;
|
|
}
|
|
}else {
|
|
showIncompatibleScreen("WebGL is not supported on this device!");
|
|
return false;
|
|
}
|
|
}
|
|
}else {
|
|
glesVer = 300;
|
|
webgl_ = /** @type {WebGL2RenderingContext} */ (canvasElement.getContext("webgl2", contextCreationHints));
|
|
if(!webgl_) {
|
|
glesVer = 200;
|
|
webgl_ = /** @type {WebGL2RenderingContext} */ (canvasElement.getContext("webgl", contextCreationHints));
|
|
if(!webgl_) {
|
|
if(runtimeOpts.allowExperimentalWebGL1) {
|
|
experimental = true;
|
|
webgl_ = /** @type {WebGL2RenderingContext} */ (canvasElement.getContext("experimental-webgl", contextCreationHints));
|
|
if(!webgl_) {
|
|
showIncompatibleScreen("WebGL is not supported on this device!");
|
|
return false;
|
|
}
|
|
}else {
|
|
showIncompatibleScreen("WebGL is not supported on this device!");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(experimental) {
|
|
alert("WARNING: Detected \"experimental\" WebGL 1.0 support, certain graphics API features may be missing, and therefore EaglercraftX may malfunction and crash!");
|
|
}
|
|
|
|
webglGLESVer = glesVer;
|
|
webglContext = webgl_;
|
|
webglExperimental = experimental;
|
|
|
|
setCurrentGLContext(webgl_, glesVer, runtimeOpts.useWebGLExt, eagruntimeImpl.platformOpenGL);
|
|
|
|
eagInfo("OpenGL Version: {}", eagruntimeImpl.platformOpenGL["glGetString"](0x1F02));
|
|
eagInfo("OpenGL Renderer: {}", eagruntimeImpl.platformOpenGL["glGetString"](0x1F01));
|
|
|
|
/** @type {Array<string>} */
|
|
const exts = eagruntimeImpl.platformOpenGL["dumpActiveExtensions"]();
|
|
if(exts.length === 0) {
|
|
eagInfo("Unlocked the following OpenGL ES extensions: (NONE)");
|
|
}else {
|
|
exts.sort();
|
|
eagInfo("Unlocked the following OpenGL ES extensions:");
|
|
for(var i = 0; i < exts.length; ++i) {
|
|
eagInfo(" - {}", exts[i]);
|
|
}
|
|
}
|
|
|
|
eagruntimeImpl.platformOpenGL["glClearColor"](0.0, 0.0, 0.0, 1.0);
|
|
eagruntimeImpl.platformOpenGL["glClear"](0x4000);
|
|
|
|
await promiseTimeout(20);
|
|
|
|
eagInfo("EagRuntime JS context initialization complete");
|
|
return true;
|
|
}
|
|
|
|
async function initializeContextWorker() {
|
|
setupRuntimeOpts();
|
|
|
|
/**
|
|
* @param {string} txt
|
|
* @param {boolean} err
|
|
*/
|
|
currentRedirectorFunc = function(txt, err) {
|
|
postMessage({
|
|
"ch": "~!LOGGER",
|
|
"txt": txt,
|
|
"err": err
|
|
});
|
|
};
|
|
|
|
eagInfo("Initializing EagRuntime worker JS context...");
|
|
|
|
await initializePlatfRuntime();
|
|
initializeNoPlatfApplication(eagruntimeImpl.platformApplication);
|
|
setNoAudioContext(eagruntimeImpl.platformAudio);
|
|
initNoPlatformInput(eagruntimeImpl.platformInput);
|
|
setNoGLContext(eagruntimeImpl.platformOpenGL);
|
|
initializeNoPlatfScreenRecord(eagruntimeImpl.platformScreenRecord);
|
|
initializeNoPlatfVoiceClient(eagruntimeImpl.platformVoiceClient);
|
|
initializeNoPlatfWebRTC(eagruntimeImpl.platformWebRTC);
|
|
initializeNoPlatfWebView(eagruntimeImpl.platformWebView);
|
|
initializeNoClientPlatfSP(eagruntimeImpl.clientPlatformSingleplayer);
|
|
initializeServerPlatfSP(eagruntimeImpl.serverPlatformSingleplayer);
|
|
|
|
eagInfo("EagRuntime worker JS context initialization complete");
|
|
}
|
|
|
|
/**
|
|
* @param {WebAssembly.Memory} mem
|
|
*/
|
|
function handleMemoryResized(mem) {
|
|
heapMemory = mem;
|
|
heapArrayBuffer = mem.buffer;
|
|
eagInfo("WebAssembly direct memory resized to {} MiB", ((heapArrayBuffer.byteLength / 1024.0 / 10.24) | 0) * 0.01);
|
|
heapU8Array = new Uint8Array(heapArrayBuffer);
|
|
heapI8Array = new Int8Array(heapArrayBuffer);
|
|
heapU16Array = new Uint16Array(heapArrayBuffer);
|
|
heapI16Array = new Int16Array(heapArrayBuffer);
|
|
heapU32Array = new Uint32Array(heapArrayBuffer);
|
|
heapI32Array = new Int32Array(heapArrayBuffer);
|
|
heapF32Array = new Float32Array(heapArrayBuffer);
|
|
}
|
|
|
|
const EVENT_TYPE_INPUT = 0;
|
|
const EVENT_TYPE_RUNTIME = 1;
|
|
const EVENT_TYPE_VOICE = 2;
|
|
const EVENT_TYPE_WEBVIEW = 3;
|
|
|
|
const mainEventQueue = new EaglerLinkedQueue();
|
|
|
|
/**
|
|
* @param {number} eventType
|
|
* @param {number} eventId
|
|
* @param {*} eventObj
|
|
*/
|
|
function pushEvent(eventType, eventId, eventObj) {
|
|
mainEventQueue.push({
|
|
"eventType": ((eventType << 5) | eventId),
|
|
"eventObj": eventObj,
|
|
"_next": null
|
|
});
|
|
}
|
|
|
|
let exceptionFrameRegex2 = /.+:wasm-function\[[0-9]+]:0x([0-9a-f]+).*/;
|
|
|
|
/**
|
|
* @param {string|null} stack
|
|
* @return {Array<string>}
|
|
*/
|
|
function deobfuscateStack(stack) {
|
|
if(!stack) return null;
|
|
/** @type {!Array<string>} */
|
|
const stackFrames = [];
|
|
for(let line of stack.split("\n")) {
|
|
if(deobfuscatorFunc) {
|
|
const match = exceptionFrameRegex2.exec(line);
|
|
if(match !== null && match.length >= 2) {
|
|
const val = parseInt(match[1], 16);
|
|
if(!isNaN(val)) {
|
|
try {
|
|
/** @type {Array<Object>} */
|
|
const resultList = deobfuscatorFunc([val]);
|
|
if(resultList.length > 0) {
|
|
for(let obj of resultList) {
|
|
stackFrames.push("" + obj["className"] + "." + obj["method"] + "(" + obj["file"] + ":" + obj["line"] + ")");
|
|
}
|
|
continue;
|
|
}
|
|
}catch(ex) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
line = line.trim();
|
|
if(line.startsWith("at ")) {
|
|
line = line.substring(3);
|
|
}
|
|
stackFrames.push(line);
|
|
}
|
|
return stackFrames;
|
|
}
|
|
|
|
function displayUncaughtCrashReport(error) {
|
|
const stack = error ? deobfuscateStack(error.stack) : null;
|
|
const crashContent = "Native Browser Exception\n" +
|
|
"----------------------------------\n" +
|
|
" Line: " + ((error && (typeof error.fileName === "string")) ? error.fileName : "unknown") +
|
|
":" + ((error && (typeof error.lineNumber === "number")) ? error.lineNumber : "unknown") +
|
|
":" + ((error && (typeof error.columnNumber === "number")) ? error.columnNumber : "unknown") +
|
|
"\n Type: " + ((error && (typeof error.name === "string")) ? error.name : "unknown") +
|
|
"\n Desc: " + ((error && (typeof error.message === "string")) ? error.message : "null") +
|
|
"\n----------------------------------\n\n" +
|
|
"Deobfuscated stack trace:\n at " + (stack ? stack.join("\n at ") : "null") +
|
|
"\n\nThis exception was not handled by the WASM binary\n";
|
|
if(typeof window !== "undefined") {
|
|
displayCrashReport(crashContent, true);
|
|
}else if(sendIntegratedServerCrash) {
|
|
eagError("\n{}", crashContent);
|
|
try {
|
|
sendIntegratedServerCrash(crashContent, true);
|
|
}catch(ex) {
|
|
console.log(ex);
|
|
}
|
|
}else {
|
|
eagError("\n{}", crashContent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} crashReport
|
|
* @param {boolean} enablePrint
|
|
*/
|
|
function displayCrashReport(crashReport, enablePrint) {
|
|
eagError("Game crashed!");
|
|
|
|
var strBefore = "Game Crashed! I have fallen and I can't get up!\n\n"
|
|
+ crashReport
|
|
+ "\n\n";
|
|
|
|
var strAfter = "eaglercraft.version = \""
|
|
+ crashReportStrings[0]
|
|
+ "\"\neaglercraft.minecraft = \""
|
|
+ crashReportStrings[2]
|
|
+ "\"\neaglercraft.brand = \""
|
|
+ crashReportStrings[1]
|
|
+ "\"\n\n"
|
|
+ addWebGLToCrash()
|
|
+ "\nwindow.eaglercraftXOpts = "
|
|
+ JSON.stringify(eaglercraftXOpts)
|
|
+ "\n\ncurrentTime = "
|
|
+ (new Date()).toLocaleString()
|
|
+ "\n\n"
|
|
+ addDebugNav("userAgent")
|
|
+ addDebugNav("vendor")
|
|
+ addDebugNav("language")
|
|
+ addDebugNav("hardwareConcurrency")
|
|
+ addDebugNav("deviceMemory")
|
|
+ addDebugNav("platform")
|
|
+ addDebugNav("product")
|
|
+ addDebugNavPlugins()
|
|
+ "\n"
|
|
+ addDebug("localStorage")
|
|
+ addDebug("sessionStorage")
|
|
+ addDebug("indexedDB")
|
|
+ "\n"
|
|
+ "rootElement.clientWidth = "
|
|
+ (parentElement ? parentElement.clientWidth : "undefined")
|
|
+ "\nrootElement.clientHeight = "
|
|
+ (parentElement ? parentElement.clientHeight : "undefined")
|
|
+ "\n"
|
|
+ addDebug("innerWidth")
|
|
+ addDebug("innerHeight")
|
|
+ addDebug("outerWidth")
|
|
+ addDebug("outerHeight")
|
|
+ addDebug("devicePixelRatio")
|
|
+ addDebugScreen("availWidth")
|
|
+ addDebugScreen("availHeight")
|
|
+ addDebugScreen("colorDepth")
|
|
+ addDebugScreen("pixelDepth")
|
|
+ "\n"
|
|
+ addDebugLocation("href")
|
|
+ "\n";
|
|
|
|
var strFinal = strBefore + strAfter;
|
|
const additionalInfo = [];
|
|
try {
|
|
if((typeof eaglercraftXOpts === "object") && (typeof eaglercraftXOpts["hooks"] === "object")
|
|
&& (typeof eaglercraftXOpts["hooks"]["crashReportShow"] === "function")) {
|
|
eaglercraftXOpts["hooks"]["crashReportShow"](strFinal, function(str) {
|
|
additionalInfo.push(str);
|
|
});
|
|
}
|
|
}catch(ex) {
|
|
eagStackTrace(ERROR, "Uncaught exception invoking crash report hook", ex);
|
|
}
|
|
|
|
if(!isCrashed) {
|
|
isCrashed = true;
|
|
|
|
if(additionalInfo.length > 0) {
|
|
strFinal = strBefore + "Got the following messages from the crash report hook registered in eaglercraftXOpts:\n\n";
|
|
for(var i = 0; i < additionalInfo.length; ++i) {
|
|
strFinal += "----------[ CRASH HOOK ]----------\n"
|
|
+ additionalInfo[i]
|
|
+ "\n----------------------------------\n\n";
|
|
}
|
|
strFinal += strAfter;
|
|
}
|
|
|
|
var parentEl = parentElement || rootElement;
|
|
|
|
if(!parentEl) {
|
|
alert("Root element not found, crash report was printed to console");
|
|
eagError("\n{}", strFinal);
|
|
return;
|
|
}
|
|
|
|
if(enablePrint) {
|
|
eagError("\n{}", strFinal);
|
|
}
|
|
|
|
const img = document.createElement("img");
|
|
const div = document.createElement("div");
|
|
img.setAttribute("style", "z-index:100;position:absolute;top:10px;left:calc(50% - 151px);");
|
|
img.src = crashURL;
|
|
div.setAttribute("style", "z-index:100;position:absolute;top:135px;left:10%;right:10%;bottom:50px;background-color:white;border:1px solid #cccccc;overflow-x:hidden;overflow-y:scroll;overflow-wrap:break-word;white-space:pre-wrap;font: 14px monospace;padding:10px;");
|
|
div.classList.add("_eaglercraftX_crash_element");
|
|
parentEl.appendChild(img);
|
|
parentEl.appendChild(div);
|
|
div.appendChild(document.createTextNode(strFinal));
|
|
|
|
if(removeEventHandlers) removeEventHandlers();
|
|
window.__curEaglerX188UnloadListenerCB = null;
|
|
}else {
|
|
eagError("");
|
|
eagError("An additional crash report was supressed:");
|
|
var s = crashReport.split(/[\r\n]+/);
|
|
for(var i = 0; i < s.length; ++i) {
|
|
eagError(" {}", s[i]);
|
|
}
|
|
if(additionalInfo.length > 0) {
|
|
for(var i = 0; i < additionalInfo.length; ++i) {
|
|
var str2 = additionalInfo[i];
|
|
if(str2) {
|
|
eagError("");
|
|
eagError(" ----------[ CRASH HOOK ]----------");
|
|
s = str2.split(/[\r\n]+/);
|
|
for(var i = 0; i < s.length; ++i) {
|
|
eagError(" {}", s[i]);
|
|
}
|
|
eagError(" ----------------------------------");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} msg
|
|
*/
|
|
function showIncompatibleScreen(msg) {
|
|
if(!isCrashed) {
|
|
isCrashed = true;
|
|
|
|
var parentEl = parentElement || rootElement;
|
|
|
|
eagError("Compatibility error: {}", msg);
|
|
|
|
if(!parentEl) {
|
|
alert("Compatibility error: " + msg);
|
|
return;
|
|
}
|
|
|
|
const img = document.createElement("img");
|
|
const div = document.createElement("div");
|
|
img.setAttribute("style", "z-index:100;position:absolute;top:10px;left:calc(50% - 151px);");
|
|
img.src = crashURL;
|
|
div.setAttribute("style", "z-index:100;position:absolute;top:135px;left:10%;right:10%;bottom:50px;background-color:white;border:1px solid #cccccc;overflow-x:hidden;overflow-y:scroll;font:18px sans-serif;padding:40px;");
|
|
div.classList.add("_eaglercraftX_incompatible_element");
|
|
parentEl.appendChild(img);
|
|
parentEl.appendChild(div);
|
|
div.innerHTML = "<h2><svg style=\"vertical-align:middle;margin:0px 16px 8px 8px;\" xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"48\" viewBox=\"0 0 48 48\" fill=\"none\"><path stroke=\"#000000\" stroke-width=\"3\" stroke-linecap=\"square\" d=\"M1.5 8.5v34h45v-28m-3-3h-10v-3m-3-3h-10m15 6h-18v-3m-3-3h-10\"/><path stroke=\"#000000\" stroke-width=\"2\" stroke-linecap=\"square\" d=\"M12 21h0m0 4h0m4 0h0m0-4h0m-2 2h0m20-2h0m0 4h0m4 0h0m0-4h0m-2 2h0\"/><path stroke=\"#000000\" stroke-width=\"2\" stroke-linecap=\"square\" d=\"M20 30h0 m2 2h0 m2 2h0 m2 2h0 m2 -2h0 m2 -2h0 m2 -2h0\"/></svg>+ This device is incompatible with Eaglercraft :(</h2>"
|
|
+ "<div style=\"margin-left:40px;\">"
|
|
+ "<p style=\"font-size:1.2em;\"><b style=\"font-size:1.1em;\">Issue:</b> <span style=\"color:#BB0000;\" id=\"_eaglercraftX_crashReason\"></span><br /></p>"
|
|
+ "<p style=\"margin-left:10px;font:0.9em monospace;\" id=\"_eaglercraftX_crashUserAgent\"></p>"
|
|
+ "<p style=\"margin-left:10px;font:0.9em monospace;\" id=\"_eaglercraftX_crashWebGL\"></p>"
|
|
+ "<p style=\"margin-left:10px;font:0.9em monospace;\">Current Date: " + (new Date()).toLocaleString() + "</p>"
|
|
+ "<p><br /><span style=\"font-size:1.1em;border-bottom:1px dashed #AAAAAA;padding-bottom:5px;\">Things you can try:</span></p>"
|
|
+ "<ol>"
|
|
+ "<li><span style=\"font-weight:bold;\">Just try using Eaglercraft on a different device</span>, it isn't a bug it's common sense</li>"
|
|
+ "<li style=\"margin-top:7px;\">If this screen just appeared randomly, try restarting your browser or device</li>"
|
|
+ "<li style=\"margin-top:7px;\">If you are not using Chrome/Edge, try installing the latest Google Chrome</li>"
|
|
+ "<li style=\"margin-top:7px;\">If your browser is out of date, please update it to the latest version</li>"
|
|
+ "</ol>"
|
|
+ "</div>";
|
|
|
|
div.querySelector("#_eaglercraftX_crashReason").appendChild(document.createTextNode(msg));
|
|
div.querySelector("#_eaglercraftX_crashUserAgent").appendChild(document.createTextNode(getStringNav("userAgent")));
|
|
|
|
if(removeEventHandlers) removeEventHandlers();
|
|
window.__curEaglerX188UnloadListenerCB = null;
|
|
|
|
var webGLRenderer = "No GL_RENDERER string could be queried";
|
|
|
|
try {
|
|
const cvs = /** @type {HTMLCanvasElement} */ (document.createElement("canvas"));
|
|
|
|
cvs.width = 64;
|
|
cvs.height = 64;
|
|
|
|
const ctx = /** @type {WebGLRenderingContext} */ (cvs.getContext("webgl"));
|
|
|
|
if(ctx) {
|
|
/** @type {string|null} */
|
|
var r;
|
|
if(ctx.getExtension("WEBGL_debug_renderer_info")) {
|
|
r = /** @type {string|null} */ (ctx.getParameter(/* UNMASKED_RENDERER_WEBGL */ 0x9246));
|
|
}else {
|
|
r = /** @type {string|null} */ (ctx.getParameter(WebGLRenderingContext.RENDERER));
|
|
if(r) {
|
|
r += " [masked]";
|
|
}
|
|
}
|
|
if(r) {
|
|
webGLRenderer = r;
|
|
}
|
|
}
|
|
}catch(tt) {
|
|
}
|
|
|
|
div.querySelector("#_eaglercraftX_crashWebGL").appendChild(document.createTextNode(webGLRenderer));
|
|
}
|
|
}
|
|
|
|
/** @type {string|null} */
|
|
var webGLCrashStringCache = null;
|
|
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
function addWebGLToCrash() {
|
|
if(webGLCrashStringCache) {
|
|
return webGLCrashStringCache;
|
|
}
|
|
|
|
try {
|
|
/** @type {WebGL2RenderingContext} */
|
|
var ctx = webglContext;
|
|
var experimental = webglExperimental;
|
|
|
|
if(!ctx) {
|
|
experimental = false;
|
|
var cvs = document.createElement("canvas");
|
|
cvs.width = 64;
|
|
cvs.height = 64;
|
|
ctx = /** @type {WebGL2RenderingContext} */ (cvs.getContext("webgl2"));
|
|
if(!ctx) {
|
|
ctx = /** @type {WebGL2RenderingContext} */ (cvs.getContext("webgl"));
|
|
if(!ctx) {
|
|
experimental = true;
|
|
ctx = /** @type {WebGL2RenderingContext} */ (cvs.getContext("experimental-webgl"));
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ctx) {
|
|
var ret = "";
|
|
|
|
if(webglGLESVer > 0) {
|
|
ret += "webgl.version = "
|
|
+ ctx.getParameter(/* VERSION */ 0x1F02)
|
|
+ "\n";
|
|
}
|
|
|
|
if(ctx.getExtension("WEBGL_debug_renderer_info")) {
|
|
ret += "webgl.renderer = "
|
|
+ ctx.getParameter(/* UNMASKED_RENDERER_WEBGL */ 0x9246)
|
|
+ "\nwebgl.vendor = "
|
|
+ ctx.getParameter(/* UNMASKED_VENDOR_WEBGL */ 0x9245)
|
|
+ "\n";
|
|
}else {
|
|
ret += "webgl.renderer = "
|
|
+ ctx.getParameter(/* RENDERER */ 0x1F01)
|
|
+ " [masked]\nwebgl.vendor = "
|
|
+ ctx.getParameter(/* VENDOR */ 0x1F00)
|
|
+ " [masked]\n";
|
|
}
|
|
|
|
if(webglGLESVer > 0) {
|
|
ret += "\nwebgl.version.id = "
|
|
+ webglGLESVer
|
|
+ "\nwebgl.experimental = "
|
|
+ experimental;
|
|
if(webglGLESVer === 200) {
|
|
ret += "\nwebgl.ext.ANGLE_instanced_arrays = "
|
|
+ !!ctx.getExtension("ANGLE_instanced_arrays")
|
|
+ "\nwebgl.ext.EXT_color_buffer_half_float = "
|
|
+ !!ctx.getExtension("EXT_color_buffer_half_float")
|
|
+ "\nwebgl.ext.EXT_shader_texture_lod = "
|
|
+ !!ctx.getExtension("EXT_shader_texture_lod")
|
|
+ "\nwebgl.ext.OES_fbo_render_mipmap = "
|
|
+ !!ctx.getExtension("OES_fbo_render_mipmap")
|
|
+ "\nwebgl.ext.OES_texture_float = "
|
|
+ !!ctx.getExtension("OES_texture_float")
|
|
+ "\nwebgl.ext.OES_texture_half_float = "
|
|
+ !!ctx.getExtension("OES_texture_half_float")
|
|
+ "\nwebgl.ext.OES_texture_half_float_linear = "
|
|
+ !!ctx.getExtension("OES_texture_half_float_linear");
|
|
}else if(webglGLESVer >= 300) {
|
|
ret += "\nwebgl.ext.EXT_color_buffer_float = "
|
|
+ !!ctx.getExtension("EXT_color_buffer_float")
|
|
+ "\nwebgl.ext.EXT_color_buffer_half_float = "
|
|
+ !!ctx.getExtension("EXT_color_buffer_half_float")
|
|
+ "\nwebgl.ext.OES_texture_float_linear = "
|
|
+ !!ctx.getExtension("OES_texture_float_linear");
|
|
}
|
|
ret += "\nwebgl.ext.EXT_texture_filter_anisotropic = "
|
|
+ !!ctx.getExtension("EXT_texture_filter_anisotropic")
|
|
+ "\n";
|
|
}else {
|
|
ret += "webgl.ext.ANGLE_instanced_arrays = "
|
|
+ !!ctx.getExtension("ANGLE_instanced_arrays")
|
|
+ "\nwebgl.ext.EXT_color_buffer_float = "
|
|
+ !!ctx.getExtension("EXT_color_buffer_float")
|
|
+ "\nwebgl.ext.EXT_color_buffer_half_float = "
|
|
+ !!ctx.getExtension("EXT_color_buffer_half_float")
|
|
+ "\nwebgl.ext.EXT_shader_texture_lod = "
|
|
+ !!ctx.getExtension("EXT_shader_texture_lod")
|
|
+ "\nwebgl.ext.OES_fbo_render_mipmap = "
|
|
+ !!ctx.getExtension("OES_fbo_render_mipmap")
|
|
+ "\nwebgl.ext.OES_texture_float = "
|
|
+ !!ctx.getExtension("OES_texture_float")
|
|
+ "\nwebgl.ext.OES_texture_float_linear = "
|
|
+ !!ctx.getExtension("OES_texture_float_linear")
|
|
+ "\nwebgl.ext.OES_texture_half_float = "
|
|
+ !!ctx.getExtension("OES_texture_half_float")
|
|
+ "\nwebgl.ext.OES_texture_half_float_linear = "
|
|
+ !!ctx.getExtension("OES_texture_half_float_linear")
|
|
+ "\nwebgl.ext.EXT_texture_filter_anisotropic = "
|
|
+ !!ctx.getExtension("EXT_texture_filter_anisotropic")
|
|
+ "\n";
|
|
}
|
|
|
|
return webGLCrashStringCache = ret;
|
|
}else {
|
|
return webGLCrashStringCache = "Failed to query GPU info!\n";
|
|
}
|
|
}catch(ex) {
|
|
return webGLCrashStringCache = "ERROR: could not query webgl info - " + ex + "\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} k
|
|
* @return {string}
|
|
*/
|
|
function addDebugNav(k) {
|
|
var val;
|
|
try {
|
|
val = window.navigator[k];
|
|
} catch(e) {
|
|
val = "<error>";
|
|
}
|
|
return "window.navigator." + k + " = " + val + "\n";
|
|
}
|
|
|
|
/**
|
|
* @param {string} k
|
|
* @return {string}
|
|
*/
|
|
function getStringNav(k) {
|
|
try {
|
|
return window.navigator[k];
|
|
} catch(e) {
|
|
return "<error>";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
function addDebugNavPlugins() {
|
|
var val;
|
|
try {
|
|
var retObj = new Array();
|
|
if(typeof navigator.plugins === "object") {
|
|
var len = navigator.plugins.length;
|
|
if(len > 0) {
|
|
for(var idx = 0; idx < len; ++idx) {
|
|
var thePlugin = navigator.plugins[idx];
|
|
retObj.push({
|
|
"name": thePlugin.name,
|
|
"filename": thePlugin.filename,
|
|
"desc": thePlugin.description
|
|
});
|
|
}
|
|
}
|
|
}
|
|
val = JSON.stringify(retObj);
|
|
} catch(e) {
|
|
val = "<error>";
|
|
}
|
|
return "window.navigator.plugins = " + val + "\n";
|
|
}
|
|
|
|
/**
|
|
* @param {string} k
|
|
* @return {string}
|
|
*/
|
|
function addDebugScreen(k) {
|
|
var val;
|
|
try {
|
|
val = window.screen[k];
|
|
} catch(e) {
|
|
val = "<error>";
|
|
}
|
|
return "window.screen." + k + " = " + val + "\n";
|
|
}
|
|
|
|
/**
|
|
* @param {string} k
|
|
* @return {string}
|
|
*/
|
|
function addDebugLocation(k) {
|
|
var val;
|
|
try {
|
|
val = window.location[k];
|
|
} catch(e) {
|
|
val = "<error>";
|
|
}
|
|
return "window.location." + k + " = " + val + "\n";
|
|
}
|
|
|
|
/**
|
|
* @param {string} k
|
|
* @return {string}
|
|
*/
|
|
function addDebug(k) {
|
|
var val;
|
|
try {
|
|
val = window[k];
|
|
} catch(e) {
|
|
val = "<error>";
|
|
}
|
|
return "window." + k + " = " + val + "\n";
|
|
}
|