Update #44 - WebAssembly GC support, fix more WebRTC bugs

This commit is contained in:
lax1dude
2024-12-03 23:38:28 -08:00
parent 919014b4df
commit 70b52bbf7a
216 changed files with 34358 additions and 91 deletions

View File

@ -0,0 +1,80 @@
/*
* 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 WASMGCBufferAllocatorName = "WASMGCBufferAllocator";
/**
* @param {number} addr
* @param {number} length
* @return {Int8Array}
*/
eagruntimeImpl.WASMGCBufferAllocator["getByteBufferView"] = function(addr, length) {
return new Int8Array(heapArrayBuffer, addr, length);
}
/**
* @param {number} addr
* @param {number} length
* @return {Uint8Array}
*/
eagruntimeImpl.WASMGCBufferAllocator["getUnsignedByteBufferView"] = function(addr, length) {
return new Uint8Array(heapArrayBuffer, addr, length);
}
/**
* @param {number} addr
* @param {number} length
* @return {Uint8ClampedArray}
*/
eagruntimeImpl.WASMGCBufferAllocator["getUnsignedClampedByteBufferView"] = function(addr, length) {
return new Uint8ClampedArray(heapArrayBuffer, addr, length);
}
/**
* @param {number} addr
* @param {number} length
* @return {Int16Array}
*/
eagruntimeImpl.WASMGCBufferAllocator["getShortBufferView"] = function(addr, length) {
return new Int16Array(heapArrayBuffer, addr, length);
}
/**
* @param {number} addr
* @param {number} length
* @return {Uint16Array}
*/
eagruntimeImpl.WASMGCBufferAllocator["getUnsignedShortBufferView"] = function(addr, length) {
return new Uint16Array(heapArrayBuffer, addr, length);
}
/**
* @param {number} addr
* @param {number} length
* @return {Int32Array}
*/
eagruntimeImpl.WASMGCBufferAllocator["getIntBufferView"] = function(addr, length) {
return new Int32Array(heapArrayBuffer, addr, length);
}
/**
* @param {number} addr
* @param {number} length
* @return {Float32Array}
*/
eagruntimeImpl.WASMGCBufferAllocator["getFloatBufferView"] = function(addr, length) {
return new Float32Array(heapArrayBuffer, addr, length);
}

View File

@ -0,0 +1,209 @@
/*
* 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 clientPlatfSPName = "clientPlatformSingleplayer";
function initializeClientPlatfSP(spImports) {
/** @type {Worker|null} */
var workerObj = null;
const clientMessageQueue = new EaglerLinkedQueue();
const workerBootstrapSource = "\"use strict\"; (function(ctx, globals) {" +
"globals.__eaglerXOnMessage = function(o) {" +
"globals.__eaglerXOnMessage = function(oo) { console.error(\"Dropped IPC packet that was sent too early!\"); };" +
"const eagRuntimeJSURL = URL.createObjectURL(new Blob([ o.eagruntimeJS ], { type: \"text/javascript;charset=utf-8\" }));" +
"ctx.getEaglercraftXOpts = function() { return o.eaglercraftXOpts; };" +
"ctx.getEagRuntimeJSURL = function() { return eagRuntimeJSURL; };" +
"ctx.getClassesWASMURL = function() { return o.classesWASM; };" +
"ctx.getClassesDeobfWASMURL = function() { return o.classesDeobfWASM; };" +
"ctx.getClassesTEADBGURL = function() { return o.classesTEADBG; };" +
"ctx.getEPKFiles = function() { return null; };" +
"ctx.getRootElement = function() { return null; };" +
"ctx.getMainArgs = function() { return [\"_worker_process_\"]; };" +
"ctx.getImageURL = function(idx) { return null; };" +
"ctx.runMain = function(mainFunc) { mainFunc(); };" +
"importScripts(eagRuntimeJSURL);" +
"};" +
"addEventListener(\"message\", function(evt) { globals.__eaglerXOnMessage(evt.data); });" +
"})(self.__eaglercraftXLoaderContext = {}, self);";
/** @type {string|null} */
var workerURL = null;
/**
* @return {Promise<boolean>}
*/
async function startIntegratedServerImpl() {
if(!workerURL) {
workerURL = URL.createObjectURL(new Blob([ workerBootstrapSource ], { "type": "text/javascript;charset=utf8" }));
}
try {
workerObj = new Worker(workerURL);
}catch(ex) {
eagStackTrace(ERROR, "Failed to create worker", ex);
return false;
}
workerObj.addEventListener("error", /** @type {function(Event)} */ (function(/** ErrorEvent */ evt) {
eagStackTrace(ERROR, "Worker Error", /** @type {Error} */ (evt.error));
}));
workerObj.addEventListener("message", /** @type {function(Event)} */ (function(/** MessageEvent */ evt) {
const channel = evt.data["ch"];
if(!channel) {
eagError("Recieved IPC packet with null channel");
return;
}
if(channel === "~!LOGGER") {
addLogMessageImpl(evt.data["txt"], evt.data["err"]);
return;
}
const buf = evt.data["dat"];
if(!buf) {
eagError("Recieved IPC packet with null buffer");
return;
}
if(serverLANPeerPassIPCFunc(channel, buf)) {
return;
}
clientMessageQueue.push({
"ch": channel,
"data": new Uint8Array(buf),
"_next": null
});
}));
const classesTEADBGCopy = new Int8Array(classesTEADBG.length);
classesTEADBGCopy.set(classesTEADBG, 0);
var eagRuntimeJS;
try {
eagRuntimeJS = await fetch(/** @type {string} */ (eagRuntimeJSURL), { "cache": "force-cache" })
.then((resp) => resp.arrayBuffer());
}catch(ex) {
eagStackTrace(ERROR, "Failed to fetch eagruntime.js contents", ex);
try {
workerObj.terminate();
}catch(exx) {
}
return false;
}
workerObj.postMessage({
"eaglercraftXOpts": eaglercraftXOpts,
"eagruntimeJS": eagRuntimeJS,
"classesWASM": classesWASMModule,
"classesDeobfWASM": classesDeobfWASMModule,
"classesTEADBG": classesTEADBGCopy.buffer
});
return true;
};
spImports["startIntegratedServer"] = new WebAssembly.Suspending(startIntegratedServerImpl);
/**
* @param {string} channel
* @param {Uint8Array} arr
*/
spImports["sendPacket"] = function(channel, arr) {
if(workerObj) {
const copiedArray = new Uint8Array(arr.length);
copiedArray.set(arr, 0);
workerObj.postMessage({
"ch": channel,
"dat": copiedArray.buffer
});
}
};
/**
* @param {string} channel
* @param {!ArrayBuffer} arr
*/
sendIPCPacketFunc = function(channel, arr) {
if(workerObj) {
workerObj.postMessage({
"ch": channel,
"dat": arr
});
}
};
spImports["getAvailablePackets"] = clientMessageQueue.getLength.bind(clientMessageQueue);
spImports["getNextPacket"] = clientMessageQueue.shift.bind(clientMessageQueue);
spImports["killWorker"] = function() {
if(workerObj) {
workerObj.terminate();
workerObj = null;
}
};
/** @type {HTMLElement} */
var integratedServerCrashPanel = null;
/**
* @param {string} report
* @param {number} x
* @param {number} y
* @param {number} w
* @param {number} h
*/
spImports["showCrashReportOverlay"] = function(report, x, y, w, h) {
if(!integratedServerCrashPanel) {
integratedServerCrashPanel = /** @type {HTMLElement} */ (document.createElement("div"));
integratedServerCrashPanel.setAttribute("style", "z-index:99;position:absolute;background-color:black;color:white;overflow-x:hidden;overflow-y:scroll;overflow-wrap:break-word;white-space:pre-wrap;font:18px sans-serif;padding:20px;display:none;");
integratedServerCrashPanel.classList.add("_eaglercraftX_integratedserver_crash_element");
parentElement.appendChild(integratedServerCrashPanel);
}
integratedServerCrashPanel.innerText = "";
integratedServerCrashPanel.innerText = "CURRENT DATE: " + (new Date()).toLocaleString() + "\n\n" + report;
const s = window.devicePixelRatio;
integratedServerCrashPanel.style.top = "" + (y / s) + "px";
integratedServerCrashPanel.style.left = "" + (x / s) + "px";
integratedServerCrashPanel.style.width = "" + ((w / s) - 20) + "px";
integratedServerCrashPanel.style.height = "" + ((h / s) - 20) + "px";
integratedServerCrashPanel.style.display = "block";
};
spImports["hideCrashReportOverlay"] = function() {
if(integratedServerCrashPanel) {
integratedServerCrashPanel.style.display = "none";
}
};
}
function initializeNoClientPlatfSP(spImports) {
setUnsupportedFunc(spImports, clientPlatfSPName, "startIntegratedServer");
setUnsupportedFunc(spImports, clientPlatfSPName, "sendPacket");
setUnsupportedFunc(spImports, clientPlatfSPName, "getAvailablePackets");
setUnsupportedFunc(spImports, clientPlatfSPName, "getNextPacket");
setUnsupportedFunc(spImports, clientPlatfSPName, "killWorker");
setUnsupportedFunc(spImports, clientPlatfSPName, "showCrashReportOverlay");
setUnsupportedFunc(spImports, clientPlatfSPName, "hideCrashReportOverlay");
}

View File

@ -0,0 +1,98 @@
/*
* 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.
*
*/
async function entryPoint() {
try {
eaglercraftXOpts = self.__eaglercraftXLoaderContext.getEaglercraftXOpts();
eagRuntimeJSURL = self.__eaglercraftXLoaderContext.getEagRuntimeJSURL();
const classesWASM = self.__eaglercraftXLoaderContext.getClassesWASMURL();
const classesDeobfWASM = self.__eaglercraftXLoaderContext.getClassesDeobfWASMURL();
const classesTEADBGURL = self.__eaglercraftXLoaderContext.getClassesTEADBGURL();
epkFileList = self.__eaglercraftXLoaderContext.getEPKFiles();
rootElement = self.__eaglercraftXLoaderContext.getRootElement();
splashURL = self.__eaglercraftXLoaderContext.getImageURL(0);
pressAnyKeyURL = self.__eaglercraftXLoaderContext.getImageURL(1);
crashURL = self.__eaglercraftXLoaderContext.getImageURL(2);
faviconURL = self.__eaglercraftXLoaderContext.getImageURL(3);
const args = self.__eaglercraftXLoaderContext.getMainArgs();
const isWorker = args[0] === "_worker_process_";
if(!isWorker) {
if(!await initializeContext()) {
return;
}
}else {
setLoggerContextName("worker");
await initializeContextWorker();
}
eagInfo("Loading EaglercraftX WASM GC binary...");
const teavm = await wasmGC.load(classesWASM, {
stackDeobfuscator: {
enabled: true,
path: classesDeobfWASM,
infoLocation: "external",
externalInfoPath: classesTEADBGURL
},
installImports: function(/** {Object} */ importObj) {
importObj[WASMGCBufferAllocatorName] = eagruntimeImpl.WASMGCBufferAllocator;
importObj[platfApplicationName] = eagruntimeImpl.platformApplication;
importObj[platfAssetsName] = eagruntimeImpl.platformAssets;
importObj[platfAudioName] = eagruntimeImpl.platformAudio;
importObj[platfFilesystemName] = eagruntimeImpl.platformFilesystem;
importObj[platfInputName] = eagruntimeImpl.platformInput;
importObj[platfNetworkingName] = eagruntimeImpl.platformNetworking;
importObj[platfOpenGLName] = eagruntimeImpl.platformOpenGL;
importObj[platfRuntimeName] = eagruntimeImpl.platformRuntime;
importObj[platfScreenRecordName] = eagruntimeImpl.platformScreenRecord;
importObj[platfVoiceClientName] = eagruntimeImpl.platformVoiceClient;
importObj[platfWebRTCName] = eagruntimeImpl.platformWebRTC;
importObj[platfWebViewName] = eagruntimeImpl.platformWebView;
importObj[clientPlatfSPName] = eagruntimeImpl.clientPlatformSingleplayer;
importObj[serverPlatfSPName] = eagruntimeImpl.serverPlatformSingleplayer;
importObj["teavm"]["notifyHeapResized"] = function() {
handleMemoryResized(teavm.exports.memory);
};
}
});
classesWASMModule = teavm.modules.classes;
classesDeobfWASMModule = teavm.modules.deobfuscator;
classesTEADBG = teavm.exports.debugInfo;
handleMemoryResized(teavm.exports.memory);
deobfuscatorFunc = /** @type {function(Array<number>):Array<Object>|null} */ (teavm.exports["deobfuscator"]);
eagInfo("Calling entry point with args: {}", JSON.stringify(args));
try {
await WebAssembly.promising(teavm.exports["main"]["__impl"])(args);
}catch(ex) {
teavm.exports["main"]["__rethrow"](ex);
}finally {
eagWarn("Main function has returned!");
}
}catch(ex) {
displayUncaughtCrashReport(ex);
}
}
if(typeof self.__eaglercraftXLoaderContext === "object") {
self.__eaglercraftXLoaderContext.runMain(entryPoint);
}else {
console.error("???");
}

View File

@ -0,0 +1,970 @@
/*
* 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;
window.__curEaglerX188UnloadListenerCB = function() {
//TODO: Autosave somehow?
};
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&ensp;:(</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";
}

View File

@ -0,0 +1,233 @@
/*
* 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 DEBUG = 0;
const INFO = 1;
const WARN = 2;
const ERROR = 3;
const OFF = 4;
const levels = [
"DEBUG",
"INFO",
"WARN",
"ERROR"
];
var contextName = "main";
var currentLogLevel = INFO;
/** @type {function(string, boolean)|null} */
var currentRedirectorFunc = null;
/**
* @param {string} msg
* @param {Array} args
*/
function formatArgs(msg, args) {
if(args.length > 0) {
var retString = [];
for(var i = 0; i < args.length; ++i) {
var idx = msg.indexOf("{}");
if(idx != -1) {
retString.push(msg.substring(0, idx));
retString.push(args[i]);
msg = msg.substring(idx + 2);
}else {
break;
}
}
if(retString.length > 0) {
retString.push(msg);
return retString.join("");
}else {
return msg;
}
}else {
return msg;
}
}
/**
* @param {number} lvl
* @param {string} msg
* @param {Array} args
*/
function logImpl(lvl, msg, args) {
if(lvl < currentLogLevel) {
return;
}
msg = "EagRuntimeJS: [" + (new Date()).toLocaleTimeString() + "][" + contextName +"/" + (levels[lvl] || "UNKNOWN") + "] " + formatArgs(msg, args);
if(lvl >= ERROR) {
console.error(msg);
}else {
console.log(msg);
}
if(currentRedirectorFunc) {
currentRedirectorFunc(msg, lvl >= ERROR);
}
}
/**
* @param {string} name
*/
function setLoggerContextName(name) {
contextName = name;
}
/**
* @param {string} msg
* @param {...*} args
*/
function eagDebug(msg, ...args) {
logImpl(DEBUG, msg, args);
}
/**
* @param {string} msg
* @param {...*} args
*/
function eagInfo(msg, ...args) {
logImpl(INFO, msg, args);
}
/**
* @param {string} msg
* @param {...*} args
*/
function eagWarn(msg, ...args) {
logImpl(WARN, msg, args);
}
/**
* @param {string} msg
* @param {...*} args
*/
function eagError(msg, ...args) {
logImpl(ERROR, msg, args);
}
/**
* @param {number} lvl
* @param {string} msg
* @param {...*} args
*/
function eagLog(lvl, msg, ...args) {
logImpl(lvl, msg, args);
}
/**
* @param {number} lvl
* @param {string} msg
* @param {Error} err
*/
function eagStackTrace(lvl, msg, err) {
if(err) {
if(err.message) {
eagLog(lvl, "{}: {} - \"{}\"", msg, err.name, err.message);
}else {
eagLog(lvl, "{}: {}", msg, err.name);
}
if(typeof err.stack === "string") {
const stackElements = deobfuscateStack(err.stack);
for(var i = 0; i < stackElements.length; ++i) {
eagLog(lvl, " at " + stackElements[i]);
}
}
}else {
eagLog(lvl, "{}: <null>", msg);
}
}
/**
* @param {string} modName
* @param {string} str
* @return {function()}
*/
function unsupportedFunc(modName, str) {
return function() {
eagError("Unsupported function called: {}.{}", str);
return 0;
};
}
/**
* @param {Object} importsObj
* @param {string} modName
* @param {string} str
*/
function setUnsupportedFunc(importsObj, modName, str) {
importsObj[str] = unsupportedFunc(modName, str);
}
/**
* @param {number} ms
*/
function promiseTimeout(ms) {
return new Promise(function(resolve) {
setTimeout(resolve, ms);
});
}
class EaglerLinkedQueue {
constructor() {
this.firstElement = null;
this.lastElement = null;
this.queueLength = 0;
}
/**
* @return {number}
*/
getLength() {
return this.queueLength;
}
/**
* @param {Object} obj
*/
push(obj) {
if(this.lastElement) {
this.lastElement["_next"] = obj;
}
this.lastElement = obj;
if(!this.firstElement) {
this.firstElement = obj;
}
++this.queueLength;
}
/**
* @return {Object}
*/
shift() {
if(this.firstElement) {
const ret = this.firstElement;
this.firstElement = ret["_next"] || null;
if(!this.firstElement) {
this.lastElement = null;
}else {
ret["_next"] = null;
}
--this.queueLength;
return ret;
}else {
return null;
}
}
}

View File

@ -0,0 +1,129 @@
/**
* @fileoverview eagruntime externs
* @externs
*/
/*
* 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.
*
*/
self.__eaglercraftXLoaderContext = {};
/**
* @return {Object}
*/
self.__eaglercraftXLoaderContext.getEaglercraftXOpts = function() {};
/**
* @return {string}
*/
self.__eaglercraftXLoaderContext.getEagRuntimeJSURL = function() {};
/**
* @return {string|WebAssembly.Module}
*/
self.__eaglercraftXLoaderContext.getClassesWASMURL = function() {};
/**
* @return {string|WebAssembly.Module}
*/
self.__eaglercraftXLoaderContext.getClassesDeobfWASMURL = function() {};
/**
* @return {string}
*/
self.__eaglercraftXLoaderContext.getClassesTEADBGURL = function() {};
/**
* @return {Array}
*/
self.__eaglercraftXLoaderContext.getEPKFiles = function() {};
/**
* @return {HTMLElement}
*/
self.__eaglercraftXLoaderContext.getRootElement = function() {};
/**
* @return {Array}
*/
self.__eaglercraftXLoaderContext.getMainArgs = function() {};
/**
* @param {number} img
* @return {string}
*/
self.__eaglercraftXLoaderContext.getImageURL = function(img) {};
/**
* @param {function(Array<string>)} fn
*/
self.__eaglercraftXLoaderContext.runMain = function(fn) {};
/**
* @param {Object} o
*/
self.__eaglerXOnMessage = function(o) {};
window.__isEaglerX188UnloadListenerSet = "";
/** @type {function()|null} */
window.__curEaglerX188UnloadListenerCB = function() {};
/**
* @return {Promise<Object>}
*/
window.navigator.keyboard.getLayoutMap = function() {};
/**
* @param {*} fn
* @return {function(...*)}
*/
WebAssembly.promising = function(fn) {};
WebAssembly.Suspending = class {
/**
* @param {*} fn
*/
constructor(fn) {
}
};
/**
* @param {*} tag
* @return {boolean}
*/
WebAssembly.Exception.prototype.is = function(tag) {}
/**
* @param {*} tag
* @param {number} idx
* @return {*}
*/
WebAssembly.Exception.prototype.getArg = function(tag, idx) {}
WebAssembly.Global = class {
/**
* @param {!Object} desc
* @param {*} initValue
*/
constructor(desc, initValue) {
/** @type {*} */
this.value = null;
}
};
/** @type {string|null} */
HTMLIFrameElement.prototype.csp = "";

View File

@ -0,0 +1,541 @@
/*
* The MIT license
*
* Copyright (c) 2018 Yury Sitnikov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
const webmSections = {
0xa45dfa3: { name: 'EBML', type: 'Container' },
0x286: { name: 'EBMLVersion', type: 'Uint' },
0x2f7: { name: 'EBMLReadVersion', type: 'Uint' },
0x2f2: { name: 'EBMLMaxIDLength', type: 'Uint' },
0x2f3: { name: 'EBMLMaxSizeLength', type: 'Uint' },
0x282: { name: 'DocType', type: 'String' },
0x287: { name: 'DocTypeVersion', type: 'Uint' },
0x285: { name: 'DocTypeReadVersion', type: 'Uint' },
0x6c: { name: 'Void', type: 'Binary' },
0x3f: { name: 'CRC-32', type: 'Binary' },
0xb538667: { name: 'SignatureSlot', type: 'Container' },
0x3e8a: { name: 'SignatureAlgo', type: 'Uint' },
0x3e9a: { name: 'SignatureHash', type: 'Uint' },
0x3ea5: { name: 'SignaturePublicKey', type: 'Binary' },
0x3eb5: { name: 'Signature', type: 'Binary' },
0x3e5b: { name: 'SignatureElements', type: 'Container' },
0x3e7b: { name: 'SignatureElementList', type: 'Container' },
0x2532: { name: 'SignedElement', type: 'Binary' },
0x8538067: { name: 'Segment', type: 'Container' },
0x14d9b74: { name: 'SeekHead', type: 'Container' },
0xdbb: { name: 'Seek', type: 'Container' },
0x13ab: { name: 'SeekID', type: 'Binary' },
0x13ac: { name: 'SeekPosition', type: 'Uint' },
0x549a966: { name: 'Info', type: 'Container' },
0x33a4: { name: 'SegmentUID', type: 'Binary' },
0x3384: { name: 'SegmentFilename', type: 'String' },
0x1cb923: { name: 'PrevUID', type: 'Binary' },
0x1c83ab: { name: 'PrevFilename', type: 'String' },
0x1eb923: { name: 'NextUID', type: 'Binary' },
0x1e83bb: { name: 'NextFilename', type: 'String' },
0x444: { name: 'SegmentFamily', type: 'Binary' },
0x2924: { name: 'ChapterTranslate', type: 'Container' },
0x29fc: { name: 'ChapterTranslateEditionUID', type: 'Uint' },
0x29bf: { name: 'ChapterTranslateCodec', type: 'Uint' },
0x29a5: { name: 'ChapterTranslateID', type: 'Binary' },
0xad7b1: { name: 'TimecodeScale', type: 'Uint' },
0x489: { name: 'Duration', type: 'Float' },
0x461: { name: 'DateUTC', type: 'Date' },
0x3ba9: { name: 'Title', type: 'String' },
0xd80: { name: 'MuxingApp', type: 'String' },
0x1741: { name: 'WritingApp', type: 'String' },
// 0xf43b675: { name: 'Cluster', type: 'Container' },
0x67: { name: 'Timecode', type: 'Uint' },
0x1854: { name: 'SilentTracks', type: 'Container' },
0x18d7: { name: 'SilentTrackNumber', type: 'Uint' },
0x27: { name: 'Position', type: 'Uint' },
0x2b: { name: 'PrevSize', type: 'Uint' },
0x23: { name: 'SimpleBlock', type: 'Binary' },
0x20: { name: 'BlockGroup', type: 'Container' },
0x21: { name: 'Block', type: 'Binary' },
0x22: { name: 'BlockVirtual', type: 'Binary' },
0x35a1: { name: 'BlockAdditions', type: 'Container' },
0x26: { name: 'BlockMore', type: 'Container' },
0x6e: { name: 'BlockAddID', type: 'Uint' },
0x25: { name: 'BlockAdditional', type: 'Binary' },
0x1b: { name: 'BlockDuration', type: 'Uint' },
0x7a: { name: 'ReferencePriority', type: 'Uint' },
0x7b: { name: 'ReferenceBlock', type: 'Int' },
0x7d: { name: 'ReferenceVirtual', type: 'Int' },
0x24: { name: 'CodecState', type: 'Binary' },
0x35a2: { name: 'DiscardPadding', type: 'Int' },
0xe: { name: 'Slices', type: 'Container' },
0x68: { name: 'TimeSlice', type: 'Container' },
0x4c: { name: 'LaceNumber', type: 'Uint' },
0x4d: { name: 'FrameNumber', type: 'Uint' },
0x4b: { name: 'BlockAdditionID', type: 'Uint' },
0x4e: { name: 'Delay', type: 'Uint' },
0x4f: { name: 'SliceDuration', type: 'Uint' },
0x48: { name: 'ReferenceFrame', type: 'Container' },
0x49: { name: 'ReferenceOffset', type: 'Uint' },
0x4a: { name: 'ReferenceTimeCode', type: 'Uint' },
0x2f: { name: 'EncryptedBlock', type: 'Binary' },
0x654ae6b: { name: 'Tracks', type: 'Container' },
0x2e: { name: 'TrackEntry', type: 'Container' },
0x57: { name: 'TrackNumber', type: 'Uint' },
0x33c5: { name: 'TrackUID', type: 'Uint' },
0x3: { name: 'TrackType', type: 'Uint' },
0x39: { name: 'FlagEnabled', type: 'Uint' },
0x8: { name: 'FlagDefault', type: 'Uint' },
0x15aa: { name: 'FlagForced', type: 'Uint' },
0x1c: { name: 'FlagLacing', type: 'Uint' },
0x2de7: { name: 'MinCache', type: 'Uint' },
0x2df8: { name: 'MaxCache', type: 'Uint' },
0x3e383: { name: 'DefaultDuration', type: 'Uint' },
0x34e7a: { name: 'DefaultDecodedFieldDuration', type: 'Uint' },
0x3314f: { name: 'TrackTimecodeScale', type: 'Float' },
0x137f: { name: 'TrackOffset', type: 'Int' },
0x15ee: { name: 'MaxBlockAdditionID', type: 'Uint' },
0x136e: { name: 'Name', type: 'String' },
0x2b59c: { name: 'Language', type: 'String' },
0x6: { name: 'CodecID', type: 'String' },
0x23a2: { name: 'CodecPrivate', type: 'Binary' },
0x58688: { name: 'CodecName', type: 'String' },
0x3446: { name: 'AttachmentLink', type: 'Uint' },
0x1a9697: { name: 'CodecSettings', type: 'String' },
0x1b4040: { name: 'CodecInfoURL', type: 'String' },
0x6b240: { name: 'CodecDownloadURL', type: 'String' },
0x2a: { name: 'CodecDecodeAll', type: 'Uint' },
0x2fab: { name: 'TrackOverlay', type: 'Uint' },
0x16aa: { name: 'CodecDelay', type: 'Uint' },
0x16bb: { name: 'SeekPreRoll', type: 'Uint' },
0x2624: { name: 'TrackTranslate', type: 'Container' },
0x26fc: { name: 'TrackTranslateEditionUID', type: 'Uint' },
0x26bf: { name: 'TrackTranslateCodec', type: 'Uint' },
0x26a5: { name: 'TrackTranslateTrackID', type: 'Binary' },
0x60: { name: 'Video', type: 'Container' },
0x1a: { name: 'FlagInterlaced', type: 'Uint' },
0x13b8: { name: 'StereoMode', type: 'Uint' },
0x13c0: { name: 'AlphaMode', type: 'Uint' },
0x13b9: { name: 'OldStereoMode', type: 'Uint' },
0x30: { name: 'PixelWidth', type: 'Uint' },
0x3a: { name: 'PixelHeight', type: 'Uint' },
0x14aa: { name: 'PixelCropBottom', type: 'Uint' },
0x14bb: { name: 'PixelCropTop', type: 'Uint' },
0x14cc: { name: 'PixelCropLeft', type: 'Uint' },
0x14dd: { name: 'PixelCropRight', type: 'Uint' },
0x14b0: { name: 'DisplayWidth', type: 'Uint' },
0x14ba: { name: 'DisplayHeight', type: 'Uint' },
0x14b2: { name: 'DisplayUnit', type: 'Uint' },
0x14b3: { name: 'AspectRatioType', type: 'Uint' },
0xeb524: { name: 'ColourSpace', type: 'Binary' },
0xfb523: { name: 'GammaValue', type: 'Float' },
0x383e3: { name: 'FrameRate', type: 'Float' },
0x61: { name: 'Audio', type: 'Container' },
0x35: { name: 'SamplingFrequency', type: 'Float' },
0x38b5: { name: 'OutputSamplingFrequency', type: 'Float' },
0x1f: { name: 'Channels', type: 'Uint' },
0x3d7b: { name: 'ChannelPositions', type: 'Binary' },
0x2264: { name: 'BitDepth', type: 'Uint' },
0x62: { name: 'TrackOperation', type: 'Container' },
0x63: { name: 'TrackCombinePlanes', type: 'Container' },
0x64: { name: 'TrackPlane', type: 'Container' },
0x65: { name: 'TrackPlaneUID', type: 'Uint' },
0x66: { name: 'TrackPlaneType', type: 'Uint' },
0x69: { name: 'TrackJoinBlocks', type: 'Container' },
0x6d: { name: 'TrackJoinUID', type: 'Uint' },
0x40: { name: 'TrickTrackUID', type: 'Uint' },
0x41: { name: 'TrickTrackSegmentUID', type: 'Binary' },
0x46: { name: 'TrickTrackFlag', type: 'Uint' },
0x47: { name: 'TrickMasterTrackUID', type: 'Uint' },
0x44: { name: 'TrickMasterTrackSegmentUID', type: 'Binary' },
0x2d80: { name: 'ContentEncodings', type: 'Container' },
0x2240: { name: 'ContentEncoding', type: 'Container' },
0x1031: { name: 'ContentEncodingOrder', type: 'Uint' },
0x1032: { name: 'ContentEncodingScope', type: 'Uint' },
0x1033: { name: 'ContentEncodingType', type: 'Uint' },
0x1034: { name: 'ContentCompression', type: 'Container' },
0x254: { name: 'ContentCompAlgo', type: 'Uint' },
0x255: { name: 'ContentCompSettings', type: 'Binary' },
0x1035: { name: 'ContentEncryption', type: 'Container' },
0x7e1: { name: 'ContentEncAlgo', type: 'Uint' },
0x7e2: { name: 'ContentEncKeyID', type: 'Binary' },
0x7e3: { name: 'ContentSignature', type: 'Binary' },
0x7e4: { name: 'ContentSigKeyID', type: 'Binary' },
0x7e5: { name: 'ContentSigAlgo', type: 'Uint' },
0x7e6: { name: 'ContentSigHashAlgo', type: 'Uint' },
0xc53bb6b: { name: 'Cues', type: 'Container' },
0x3b: { name: 'CuePoint', type: 'Container' },
0x33: { name: 'CueTime', type: 'Uint' },
0x37: { name: 'CueTrackPositions', type: 'Container' },
0x77: { name: 'CueTrack', type: 'Uint' },
0x71: { name: 'CueClusterPosition', type: 'Uint' },
0x70: { name: 'CueRelativePosition', type: 'Uint' },
0x32: { name: 'CueDuration', type: 'Uint' },
0x1378: { name: 'CueBlockNumber', type: 'Uint' },
0x6a: { name: 'CueCodecState', type: 'Uint' },
0x5b: { name: 'CueReference', type: 'Container' },
0x16: { name: 'CueRefTime', type: 'Uint' },
0x17: { name: 'CueRefCluster', type: 'Uint' },
0x135f: { name: 'CueRefNumber', type: 'Uint' },
0x6b: { name: 'CueRefCodecState', type: 'Uint' },
0x941a469: { name: 'Attachments', type: 'Container' },
0x21a7: { name: 'AttachedFile', type: 'Container' },
0x67e: { name: 'FileDescription', type: 'String' },
0x66e: { name: 'FileName', type: 'String' },
0x660: { name: 'FileMimeType', type: 'String' },
0x65c: { name: 'FileData', type: 'Binary' },
0x6ae: { name: 'FileUID', type: 'Uint' },
0x675: { name: 'FileReferral', type: 'Binary' },
0x661: { name: 'FileUsedStartTime', type: 'Uint' },
0x662: { name: 'FileUsedEndTime', type: 'Uint' },
0x43a770: { name: 'Chapters', type: 'Container' },
0x5b9: { name: 'EditionEntry', type: 'Container' },
0x5bc: { name: 'EditionUID', type: 'Uint' },
0x5bd: { name: 'EditionFlagHidden', type: 'Uint' },
0x5db: { name: 'EditionFlagDefault', type: 'Uint' },
0x5dd: { name: 'EditionFlagOrdered', type: 'Uint' },
0x36: { name: 'ChapterAtom', type: 'Container' },
0x33c4: { name: 'ChapterUID', type: 'Uint' },
0x1654: { name: 'ChapterStringUID', type: 'String' },
0x11: { name: 'ChapterTimeStart', type: 'Uint' },
0x12: { name: 'ChapterTimeEnd', type: 'Uint' },
0x18: { name: 'ChapterFlagHidden', type: 'Uint' },
0x598: { name: 'ChapterFlagEnabled', type: 'Uint' },
0x2e67: { name: 'ChapterSegmentUID', type: 'Binary' },
0x2ebc: { name: 'ChapterSegmentEditionUID', type: 'Uint' },
0x23c3: { name: 'ChapterPhysicalEquiv', type: 'Uint' },
0xf: { name: 'ChapterTrack', type: 'Container' },
0x9: { name: 'ChapterTrackNumber', type: 'Uint' },
0x0: { name: 'ChapterDisplay', type: 'Container' },
0x5: { name: 'ChapString', type: 'String' },
0x37c: { name: 'ChapLanguage', type: 'String' },
0x37e: { name: 'ChapCountry', type: 'String' },
0x2944: { name: 'ChapProcess', type: 'Container' },
0x2955: { name: 'ChapProcessCodecID', type: 'Uint' },
0x50d: { name: 'ChapProcessPrivate', type: 'Binary' },
0x2911: { name: 'ChapProcessCommand', type: 'Container' },
0x2922: { name: 'ChapProcessTime', type: 'Uint' },
0x2933: { name: 'ChapProcessData', type: 'Binary' },
0x254c367: { name: 'Tags', type: 'Container' },
0x3373: { name: 'Tag', type: 'Container' },
0x23c0: { name: 'Targets', type: 'Container' },
0x28ca: { name: 'TargetTypeValue', type: 'Uint' },
0x23ca: { name: 'TargetType', type: 'String' },
0x23c5: { name: 'TagTrackUID', type: 'Uint' },
0x23c9: { name: 'TagEditionUID', type: 'Uint' },
0x23c4: { name: 'TagChapterUID', type: 'Uint' },
0x23c6: { name: 'TagAttachmentUID', type: 'Uint' },
0x27c8: { name: 'SimpleTag', type: 'Container' },
0x5a3: { name: 'TagName', type: 'String' },
0x47a: { name: 'TagLanguage', type: 'String' },
0x484: { name: 'TagDefault', type: 'Uint' },
0x487: { name: 'TagString', type: 'String' },
0x485: { name: 'TagBinary', type: 'Binary' }
};
function doInherit(newClass, baseClass) {
newClass.prototype = Object.create(baseClass.prototype);
newClass.prototype.constructor = newClass;
}
/**
* @param {string} name
* @param {string} type
* @constructor
*/
function WebmBase(name, type) {
this.name = name || 'Unknown';
this.type = type || 'Unknown';
}
WebmBase.prototype.updateBySource = function() { };
WebmBase.prototype.setSource = function(source) {
this.source = source;
this.updateBySource();
};
WebmBase.prototype.updateByData = function() { };
WebmBase.prototype.setData = function(data) {
this.data = data;
this.updateByData();
};
/**
* @param {string} name
* @param {string} type
* @extends {WebmBase}
* @constructor
*/
function WebmUint(name, type) {
WebmBase.call(this, name, type || 'Uint');
}
doInherit(WebmUint, WebmBase);
function padHex(hex) {
return hex.length % 2 === 1 ? '0' + hex : hex;
}
WebmUint.prototype.updateBySource = function() {
// use hex representation of a number instead of number value
this.data = '';
for (var i = 0; i < this.source.length; i++) {
var hex = this.source[i].toString(16);
this.data += padHex(hex);
}
};
WebmUint.prototype.updateByData = function() {
var length = this.data.length / 2;
this.source = new Uint8Array(length);
for (var i = 0; i < length; i++) {
var hex = this.data.substr(i * 2, 2);
this.source[i] = parseInt(hex, 16);
}
};
WebmUint.prototype.getValue = function() {
return parseInt(this.data, 16);
};
WebmUint.prototype.setValue = function(value) {
this.setData(padHex(value.toString(16)));
};
/**
* @param {string} name
* @param {string} type
* @extends {WebmBase}
* @constructor
*/
function WebmFloat(name, type) {
WebmBase.call(this, name, type || 'Float');
}
doInherit(WebmFloat, WebmBase);
WebmFloat.prototype.getFloatArrayType = function() {
return this.source && this.source.length === 4 ? Float32Array : Float64Array;
};
WebmFloat.prototype.updateBySource = function() {
var byteArray = this.source.reverse();
var floatArrayType = this.getFloatArrayType();
var floatArray = new floatArrayType(byteArray.buffer);
this.data = floatArray[0];
};
WebmFloat.prototype.updateByData = function() {
var floatArrayType = this.getFloatArrayType();
var floatArray = new floatArrayType([ this.data ]);
var byteArray = new Uint8Array(floatArray.buffer);
this.source = byteArray.reverse();
};
WebmFloat.prototype.getValue = function() {
return this.data;
};
WebmFloat.prototype.setValue = function(value) {
this.setData(value);
};
/**
* @param {string} name
* @param {string} type
* @extends {WebmBase}
* @constructor
*/
function WebmContainer(name, type) {
WebmBase.call(this, name, type || 'Container');
}
doInherit(WebmContainer, WebmBase);
WebmContainer.prototype.readByte = function() {
return this.source[this.offset++];
};
WebmContainer.prototype.readUint = function() {
var firstByte = this.readByte();
var bytes = 8 - firstByte.toString(2).length;
var value = firstByte - (1 << (7 - bytes));
for (var i = 0; i < bytes; i++) {
// don't use bit operators to support x86
value *= 256;
value += this.readByte();
}
return value;
};
WebmContainer.prototype.updateBySource = function() {
this.data = [];
var end;
for (this.offset = 0; this.offset < this.source.length; this.offset = end) {
var id = this.readUint();
var len = this.readUint();
end = Math.min(this.offset + len, this.source.length);
var data = this.source.slice(this.offset, end);
var info = webmSections[id] || { name: 'Unknown', type: 'Unknown' };
var ctr = WebmBase;
switch (info.type) {
case 'Container':
ctr = WebmContainer;
break;
case 'Uint':
ctr = WebmUint;
break;
case 'Float':
ctr = WebmFloat;
break;
}
var section = new ctr(info.name, info.type);
section.setSource(data);
this.data.push({
id: id,
idHex: id.toString(16),
data: section
});
}
};
WebmContainer.prototype.writeUint = function(x, draft) {
for (var bytes = 1, flag = 0x80; x >= flag && bytes < 8; bytes++, flag *= 0x80) { }
if (!draft) {
var value = flag + x;
for (var i = bytes - 1; i >= 0; i--) {
// don't use bit operators to support x86
var c = value % 256;
this.source[this.offset + i] = c;
value = (value - c) / 256;
}
}
this.offset += bytes;
};
WebmContainer.prototype.writeSections = function(draft) {
this.offset = 0;
for (var i = 0; i < this.data.length; i++) {
var section = this.data[i],
content = section.data.source,
contentLength = content.length;
this.writeUint(section.id, draft);
this.writeUint(contentLength, draft);
if (!draft) {
this.source.set(content, this.offset);
}
this.offset += contentLength;
}
return this.offset;
};
WebmContainer.prototype.updateByData = function() {
// run without accessing this.source to determine total length - need to know it to create Uint8Array
var length = this.writeSections('draft');
this.source = new Uint8Array(length);
// now really write data
this.writeSections(null);
};
WebmContainer.prototype.getSectionById = function(id) {
for (var i = 0; i < this.data.length; i++) {
var section = this.data[i];
if (section.id === id) {
return section.data;
}
}
return null;
};
/**
* @param {Uint8Array} source
* @extends {WebmContainer}
* @constructor
*/
function WebmFile(source) {
WebmContainer.call(this, 'File', 'File');
this.setSource(source);
}
doInherit(WebmFile, WebmContainer);
WebmFile.prototype.fixDuration = function(duration, options) {
var logger = options && options.logger;
if (logger === undefined) {
logger = function(message) {
console.log(message);
};
} else if (!logger) {
logger = function() { };
}
var segmentSection = this.getSectionById(0x8538067);
if (!segmentSection) {
logger('[fix-webm-duration] Segment section is missing');
return false;
}
var infoSection = segmentSection.getSectionById(0x549a966);
if (!infoSection) {
logger('[fix-webm-duration] Info section is missing');
return false;
}
var timeScaleSection = infoSection.getSectionById(0xad7b1);
if (!timeScaleSection) {
logger('[fix-webm-duration] TimecodeScale section is missing');
return false;
}
var durationSection = infoSection.getSectionById(0x489);
if (durationSection) {
if (durationSection.getValue() <= 0) {
logger('[fix-webm-duration] Duration section is present, but the value is empty');
durationSection.setValue(duration);
} else {
logger('[fix-webm-duration] Duration section is present');
return false;
}
} else {
logger('[fix-webm-duration] Duration section is missing');
// append Duration section
durationSection = new WebmFloat('Duration', 'Float');
durationSection.setValue(duration);
infoSection.data.push({
id: 0x489,
data: durationSection
});
}
// set default time scale to 1 millisecond (1000000 nanoseconds)
timeScaleSection.setValue(1000000);
infoSection.updateByData();
segmentSection.updateByData();
this.updateByData();
return true;
};
WebmFile.prototype.toBlob = function(mimeType) {
return new Blob([ this.source.buffer ], { type: mimeType || 'video/webm' });
};
/**
* @param {!Blob} blob
* @param {number} duration
* @param {function(!Blob)} callback
* @param {Object} options
*/
function fixWebMDuration(blob, duration, callback, options) {
try {
var reader = new FileReader();
reader.onloadend = function() {
try {
var file = new WebmFile(new Uint8Array(/** @type {ArrayBuffer} */ (reader.result)));
if (file.fixDuration(duration, options)) {
blob = file.toBlob(blob.type);
}
} catch (ex) {
// ignore
}
callback(blob);
};
reader.readAsArrayBuffer(blob);
} catch (ex) {
callback(blob);
}
}

View File

@ -0,0 +1,502 @@
/*
* 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 platfApplicationName = "platformApplication";
/**
* @param {string} str
* @param {string} blobUrl
* @param {function()|null} blobRevoke
*/
function downloadFileImpl(str, blobUrl, blobRevoke) {
const el = document.createElement("a");
el.style.position = "absolute";
el.style.left = "0px";
el.style.top = "0px";
el.style.zIndex = "-100";
el.style.color = "transparent";
el.innerText = "Download File";
el.href = blobUrl;
el.target = "_blank";
el.download = str;
parentElement.appendChild(el);
setTimeout(function() {
el.click();
setTimeout(function() {
parentElement.removeChild(el);
}, 50);
if(blobRevoke) {
setTimeout(blobRevoke, 60000);
}
}, 50);
}
/** @type {number} */
const bufferSpoolSize = 256;
/** @type {number} */
const windowMaxMessages = 2048;
/** @type {number} */
var messageBufferLength = 0;
/** @type {Object|null} */
var messageBufferStart = null;
/** @type {Object|null} */
var messageBufferEnd = null;
/** @type {Window|null} */
var loggerWindow = null;
/** @type {function(string, boolean)|null} */
var loggerWindowMsgHandler = null;
/**
* @param {string} text
* @param {boolean} isErr
*/
function addLogMessageImpl(text, isErr) {
if(!loggerWindow) {
var newEl = {
"msg": text,
"err": isErr,
"next": null
};
if(messageBufferEnd) {
messageBufferEnd["next"] = newEl;
}
if(!messageBufferStart) {
messageBufferStart = newEl;
}
messageBufferEnd = newEl;
++messageBufferLength;
while(messageBufferLength > bufferSpoolSize) {
--messageBufferLength;
if(messageBufferStart) {
messageBufferStart = messageBufferStart["next"];
}
}
}else if(loggerWindowMsgHandler) {
loggerWindowMsgHandler(text, isErr);
}
}
/**
* @param {Object} applicationImports
*/
function initializePlatfApplication(applicationImports) {
/**
* @param {string} str
* @return {boolean}
*/
applicationImports["setClipboard"] = function setClipboardImpl(str) {
try {
if(window.navigator.clipboard) {
window.navigator.clipboard.writeText(str);
return true;
}
}catch(ex) {
eagError("Failed to set clipboard data!");
}
return false;
};
/**
* @return {Promise<string|null>}
*/
async function getClipboardImpl() {
var txt = null;
try {
if(window.navigator.clipboard) {
txt = await navigator.clipboard.readText();
}
}catch(ex) {
eagError("Failed to read clipboard data!");
}
return txt;
}
applicationImports["getClipboard"] = new WebAssembly.Suspending(getClipboardImpl);
/** @type {boolean} */
var fileChooserHasResult = false;
/** @type {Object|null} */
var fileChooserResultObject = null;
/** @type {HTMLInputElement|null} */
var fileChooserElement = null;
/** @type {HTMLElement|null} */
var fileChooserMobileElement = null;
/**
* @param {string} mime
* @param {string} ext
*/
applicationImports["displayFileChooser"] = function(mime, ext) {
clearFileChooserResultImpl();
if(isLikelyMobileBrowser) {
const el = fileChooserMobileElement = /** @type {HTMLElement} */ (document.createElement("div"));
el.classList.add("_eaglercraftX_mobile_file_chooser_popup");
el.style.position = "absolute";
el.style.backgroundColor = "white";
el.style.fontFamily = "sans-serif";
el.style.top = "10%";
el.style.left = "10%";
el.style.right = "10%";
el.style.border = "5px double black";
el.style.padding = "15px";
el.style.textAlign = "left";
el.style.fontSize = "20px";
el.style.userSelect = "none";
el.style.zIndex = "150";
const fileChooserTitle = document.createElement("h3");
fileChooserTitle.appendChild(document.createTextNode("File Chooser"));
el.appendChild(fileChooserTitle);
const inputElementContainer = document.createElement("p");
const inputElement = fileChooserElement = /** @type {HTMLInputElement} */ (document.createElement("input"));
inputElement.type = "file";
if(mime === null) {
inputElement.accept = "." + ext;
}else {
inputElement.accept = mime;
}
inputElement.multiple = false;
inputElementContainer.appendChild(inputElement);
el.appendChild(inputElementContainer);
const fileChooserButtons = document.createElement("p");
const fileChooserButtonCancel = document.createElement("button");
fileChooserButtonCancel.classList.add("_eaglercraftX_mobile_file_chooser_btn_cancel");
fileChooserButtonCancel.style.fontSize = "1.0em";
fileChooserButtonCancel.addEventListener("click", function(/** Event */ evt) {
if(fileChooserMobileElement === el) {
parentElement.removeChild(el);
fileChooserMobileElement = null;
fileChooserElement = null;
}
});
fileChooserButtonCancel.appendChild(document.createTextNode("Cancel"));
fileChooserButtons.appendChild(fileChooserButtonCancel);
fileChooserButtons.appendChild(document.createTextNode(" "));
const fileChooserButtonDone = document.createElement("button");
fileChooserButtonDone.classList.add("_eaglercraftX_mobile_file_chooser_btn_done");
fileChooserButtonDone.style.fontSize = "1.0em";
fileChooserButtonDone.style.fontWeight = "bold";
fileChooserButtonDone.addEventListener("click", function(/** Event */ evt) {
if(fileChooserMobileElement === el) {
if(inputElement.files.length > 0) {
const val = inputElement.files[0];
val.arrayBuffer().then(function(arr){
fileChooserHasResult = true;
fileChooserResultObject = {
"fileName": val.name,
"fileData": arr
};
}).catch(function(){
fileChooserHasResult = true;
fileChooserResultObject = null;
});
}else {
fileChooserHasResult = true;
fileChooserResultObject = null;
}
parentElement.removeChild(el);
fileChooserMobileElement = null;
fileChooserElement = null;
}
});
fileChooserButtonDone.appendChild(document.createTextNode("Done"));
fileChooserButtons.appendChild(fileChooserButtonDone);
el.appendChild(fileChooserButtons);
parentElement.appendChild(el);
}else {
const inputElement = fileChooserElement = /** @type {HTMLInputElement} */ (document.createElement("input"));
inputElement.type = "file";
inputElement.style.position = "absolute";
inputElement.style.left = "0px";
inputElement.style.top = "0px";
inputElement.style.zIndex = "-100";
if(mime === null) {
inputElement.accept = "." + ext;
}else {
inputElement.accept = mime;
}
inputElement.multiple = false;
inputElement.addEventListener("change", function(/** Event */ evt) {
if(fileChooserElement === inputElement) {
if(inputElement.files.length > 0) {
const val = inputElement.files[0];
val.arrayBuffer().then(function(arr){
fileChooserHasResult = true;
fileChooserResultObject = {
"fileName": val.name,
"fileData": arr
};
}).catch(function(){
fileChooserHasResult = true;
fileChooserResultObject = null;
});
}else {
fileChooserHasResult = true;
fileChooserResultObject = null;
}
parentElement.removeChild(inputElement);
fileChooserElement = null;
}
});
parentElement.appendChild(inputElement);
window.setTimeout(function() {
inputElement.click();
}, 50);
}
};
/**
* @return {boolean}
*/
applicationImports["fileChooserHasResult"] = function() {
return fileChooserHasResult;
};
/**
* @return {Object}
*/
applicationImports["getFileChooserResult"] = function() {
fileChooserHasResult = false;
const res = fileChooserResultObject;
fileChooserResultObject = null;
return res;
};
function clearFileChooserResultImpl() {
fileChooserHasResult = false;
fileChooserResultObject = null;
if(fileChooserMobileElement !== null) {
parentElement.removeChild(fileChooserMobileElement);
fileChooserMobileElement = null;
fileChooserElement = null;
}else if(fileChooserElement !== null) {
parentElement.removeChild(fileChooserElement);
fileChooserElement = null;
}
}
applicationImports["clearFileChooserResult"] = clearFileChooserResultImpl;
/**
* @param {string} str
* @param {Uint8Array} dat
*/
applicationImports["downloadFileWithNameU8"] = function(str, dat) {
const blobUrl = URL.createObjectURL(new Blob([dat], { "type": "application/octet-stream" }));
downloadFileImpl(str, blobUrl, function() {
URL.revokeObjectURL(blobUrl);
});
};
/**
* @param {string} str
* @param {ArrayBuffer} dat
*/
applicationImports["downloadFileWithNameA"] = function(str, dat) {
const blobUrl = URL.createObjectURL(new Blob([dat], { "type": "application/octet-stream" }));
downloadFileImpl(str, blobUrl, function() {
URL.revokeObjectURL(blobUrl);
});
};
/**
* @param {string} str
* @param {HTMLCanvasElement} cvs
*/
applicationImports["downloadScreenshot"] = function(str, cvs) {
downloadFileImpl(str, cvs.toDataURL("image/png"), null);
};
/** @type {HTMLDocument} */
var loggerDoc = null;
/** @type {HTMLElement} */
var loggerBody = null;
/** @type {HTMLElement} */
var loggerMessageContainer = null;
/** @type {string} */
const loggerLocalStorageKey = runtimeOpts.localStorageNamespace + "showDebugConsole";
/** @type {string} */
const loggerWinUnloadEvent = runtimeOpts.fixDebugConsoleUnloadListener ? "beforeunload" : "unload";
function debugConsoleLocalStorageSet(val) {
try {
if(window.localStorage) {
window.localStorage.setItem(loggerLocalStorageKey, val ? "true" : "false");
}
}catch(t) {
}
}
function debugConsoleLocalStorageGet() {
try {
if(window.localStorage) {
const val = window.localStorage.getItem(loggerLocalStorageKey);
return val && "true" === val.toLowerCase();
}else {
return false;
}
}catch(t) {
return false;
}
}
try {
window.addEventListener(loggerWinUnloadEvent, (evt) => {
destroyWindow();
});
}catch(ex) {
}
if(runtimeOpts.openDebugConsoleOnLaunch || debugConsoleLocalStorageGet()) {
showDebugConsole0();
}
function showDebugConsole() {
debugConsoleLocalStorageSet(true);
showDebugConsole0();
}
function showDebugConsole0() {
if(!loggerWindow) {
const w = Math.round(1000 * window.devicePixelRatio);
const h = Math.round(400 * window.devicePixelRatio);
const x = Math.round((window.screen.width - w) / 2.0);
const y = Math.round((window.screen.height - h) / 2.0);
loggerWindow = window.open("", "_blank", "top=" + y + ",left=" + x + ",width=" + w + ",height=" + h + ",menubar=0,status=0,titlebar=0,toolbar=0");
if(!loggerWindow) {
eagError("Logger popup was blocked!");
window.alert("ERROR: Popup blocked!\n\nPlease make sure you have popups enabled for this site!");
return;
}
loggerWindow.focus();
loggerDoc = loggerWindow.document;
loggerDoc.write("<!DOCTYPE html><html><head><meta charset=\"UTF-8\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />"
+ "<title>Debug Console</title><link type=\"image/png\" rel=\"shortcut icon\" href=\"" + faviconURL + "\" />"
+ "</head><body style=\"overflow-x:hidden;overflow-y:scroll;padding:0px;\"><p id=\"loggerMessageContainer\" style=\"overflow-wrap:break-word;white-space:pre-wrap;font:14px monospace;padding:10px;\"></p></body></html>");
loggerDoc.close();
loggerBody = loggerDoc.body;
loggerMessageContainer = /** @type {HTMLElement} */ (loggerDoc.getElementById("loggerMessageContainer"));
var linkedListEl = messageBufferStart;
while(linkedListEl) {
appendLogMessage(linkedListEl["msg"] + "\n", linkedListEl["err"] ? "#DD0000" : "#000000");
linkedListEl = linkedListEl["next"];
}
messageBufferStart = null;
messageBufferEnd = null;
messageBufferLength = 0;
scrollToEnd0();
const unloadListener = (evt) => {
if(loggerWindow != null) {
loggerWindow = null;
debugConsoleLocalStorageSet(false);
}
};
loggerWindow.addEventListener("beforeunload", unloadListener);
loggerWindow.addEventListener("unload", unloadListener);
}else {
loggerWindow.focus();
}
}
/**
* @param {string} text
* @param {boolean} isErr
*/
loggerWindowMsgHandler = function(text, isErr) {
appendLogMessageAndScroll(text + "\n", isErr ? "#DD0000" : "#000000");
}
function appendLogMessageAndScroll(text, color) {
var b = isScrollToEnd();
appendLogMessage(text, color);
if(b) {
scrollToEnd0();
}
}
function appendLogMessage(text, color) {
var el = loggerDoc.createElement("span");
el.innerText = text;
el.style.color = color;
loggerMessageContainer.appendChild(el);
var children = loggerMessageContainer.children;
while(children.length > windowMaxMessages) {
children[0].remove();
}
}
/**
* @return {boolean}
*/
function isShowingDebugConsole() {
return !!loggerWindow;
}
function destroyWindow() {
if(loggerWindow) {
var w = loggerWindow;
loggerWindow = null;
loggerDoc = null;
loggerBody = null;
loggerMessageContainer = null;
w.close();
}
}
function isScrollToEnd() {
return (loggerWindow.innerHeight + loggerWindow.pageYOffset) >= loggerBody.offsetHeight;
}
function scrollToEnd0() {
setTimeout(() => {
loggerWindow.scrollTo(0, loggerBody.scrollHeight || loggerBody.clientHeight);
}, 1);
}
applicationImports["showDebugConsole"] = showDebugConsole;
applicationImports["addLogMessage"] = addLogMessageImpl;
applicationImports["isShowingDebugConsole"] = isShowingDebugConsole;
/**
* @return {string|null}
*/
applicationImports["getFaviconURL"] = function() {
return faviconURL;
};
}
/**
* @param {Object} applicationImports
*/
function initializeNoPlatfApplication(applicationImports) {
setUnsupportedFunc(applicationImports, platfApplicationName, "setClipboard");
setUnsupportedFunc(applicationImports, platfApplicationName, "getClipboard");
setUnsupportedFunc(applicationImports, platfApplicationName, "displayFileChooser");
setUnsupportedFunc(applicationImports, platfApplicationName, "fileChooserHasResult");
setUnsupportedFunc(applicationImports, platfApplicationName, "getFileChooserResult");
setUnsupportedFunc(applicationImports, platfApplicationName, "clearFileChooserResult");
setUnsupportedFunc(applicationImports, platfApplicationName, "downloadFileWithNameU8");
setUnsupportedFunc(applicationImports, platfApplicationName, "downloadFileWithNameA");
setUnsupportedFunc(applicationImports, platfApplicationName, "downloadScreenshot");
setUnsupportedFunc(applicationImports, platfApplicationName, "showDebugConsole");
setUnsupportedFunc(applicationImports, platfApplicationName, "addLogMessage");
setUnsupportedFunc(applicationImports, platfApplicationName, "isShowingDebugConsole");
setUnsupportedFunc(applicationImports, platfApplicationName, "getFaviconURL");
}

View File

@ -0,0 +1,106 @@
/*
* 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 platfAssetsName = "platformAssets";
/**
* @param {number} idx
* @return {Object}
*/
eagruntimeImpl.platformAssets["getEPKFileData"] = function(idx) {
const tmp = epkFileList[idx];
epkFileList[idx] = null;
return tmp;
};
/**
* @return {number}
*/
eagruntimeImpl.platformAssets["getEPKFileCount"] = function() {
return epkFileList.length;
};
if(typeof window !== "undefined") {
/**
* @param {Uint8Array} bufferData
* @param {string} mime
* @return {Promise}
*/
function loadImageFile0Impl(bufferData, mime) {
return new Promise(function(resolve) {
const loadURL = URL.createObjectURL(new Blob([bufferData], {type: mime}));
if(loadURL) {
const toLoad = document.createElement("img");
toLoad.addEventListener("load", function(evt) {
URL.revokeObjectURL(loadURL);
resolve({
"width": toLoad.width,
"height": toLoad.height,
"img": toLoad
});
});
toLoad.addEventListener("error", function(evt) {
URL.revokeObjectURL(loadURL);
resolve(null);
});
toLoad.src = loadURL;
}else {
resolve(null);
}
});
}
eagruntimeImpl.platformAssets["loadImageFile0"] = new WebAssembly.Suspending(loadImageFile0Impl);
/** @type {HTMLCanvasElement} */
var imageLoadingCanvas = null;
/** @type {CanvasRenderingContext2D} */
var imageLoadingContext = null;
/**
* @param {Object} imageLoadResult
* @param {Uint8ClampedArray} dataDest
*/
eagruntimeImpl.platformAssets["loadImageFile1"] = function(imageLoadResult, dataDest) {
const width = imageLoadResult["width"];
const height = imageLoadResult["height"];
const img = imageLoadResult["img"];
if(img) {
if(!imageLoadingCanvas) {
imageLoadingCanvas = /** @type {HTMLCanvasElement} */ (document.createElement("canvas"));
}
if(imageLoadingCanvas.width < width) {
imageLoadingCanvas.width = width;
}
if(imageLoadingCanvas.height < height) {
imageLoadingCanvas.height = height;
}
if(!imageLoadingContext) {
imageLoadingContext = /** @type {CanvasRenderingContext2D} */ (imageLoadingCanvas.getContext("2d", { willReadFrequently: true }));
imageLoadingContext.imageSmoothingEnabled = false;
}
imageLoadingContext.clearRect(0, 0, width, height);
imageLoadingContext.drawImage(img, 0, 0, width, height);
dataDest.set(imageLoadingContext.getImageData(0, 0, width, height).data, 0);
}
};
}else {
setUnsupportedFunc(eagruntimeImpl.platformAssets, platfAssetsName, "loadImageFile0");
setUnsupportedFunc(eagruntimeImpl.platformAssets, platfAssetsName, "loadImageFile1");
}

View File

@ -0,0 +1,78 @@
/*
* 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 platfAudioName = "platformAudio";
function setCurrentAudioContext(audioContext, audioImports) {
/**
* @return {AudioContext}
*/
audioImports["getContext"] = function() {
return audioContext;
};
/**
* @param {AudioBufferSourceNode} sourceNode
* @param {Object} isEnded
*/
audioImports["registerIsEndedHandler"] = function(sourceNode, isEnded) {
if(!isEnded["selfEndHandler"]) {
isEnded["selfEndHandler"] = function(evt) {
isEnded["isEnded"] = true;
}
}
sourceNode.addEventListener("ended", isEnded["selfEndHandler"]);
};
/**
* @param {AudioBufferSourceNode} sourceNode
* @param {Object} isEnded
*/
audioImports["releaseIsEndedHandler"] = function(sourceNode, isEnded) {
if(isEnded["selfEndHandler"]) {
sourceNode.removeEventListener("ended", isEnded["selfEndHandler"]);
}
};
/**
* @param {Uint8Array} fileData
* @param {string} errorFileName
* @return {Promise}
*/
function decodeAudioBrowserImpl(fileData, errorFileName) {
return new Promise(function(resolve) {
const copiedData = new Uint8Array(fileData.length);
copiedData.set(fileData, 0);
audioContext.decodeAudioData(copiedData.buffer, resolve, function(err) {
eagError("Failed to load audio: {}", errorFileName);
resolve(null);
});
});
}
audioImports["decodeAudioBrowser"] = new WebAssembly.Suspending(decodeAudioBrowserImpl);
}
function setNoAudioContext(audioImports) {
audioImports["getContext"] = function() {
return null;
};
setUnsupportedFunc(audioImports, platfAudioName, "registerIsEndedHandler");
setUnsupportedFunc(audioImports, platfAudioName, "releaseIsEndedHandler");
setUnsupportedFunc(audioImports, platfAudioName, "decodeAudioBrowser");
}

View File

@ -0,0 +1,295 @@
/*
* 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 platfFilesystemName = "platformFilesystem";
/**
* @param {Object} k
* @return {string}
*/
function readDBKey(k) {
return ((typeof k) === "string") ? k : (((typeof k) === "undefined") ? null : (((typeof k[0]) === "string") ? k[0] : null));
}
/**
* @param {Object} obj
* @return {ArrayBuffer}
*/
function readDBRow(obj) {
return (typeof obj === "undefined") ? null : ((typeof obj["data"] === "undefined") ? null : obj["data"]);
}
/**
* @param {string} pat
* @param {ArrayBuffer} dat
* @return {Object}
*/
function writeDBRow(pat, dat) {
return { "path": pat, "data": dat };
}
/**
* @param {string} filesystemDB
* @return {Promise}
*/
function openDBImpl(filesystemDB) {
return new Promise(function(resolve) {
if(typeof indexedDB === "undefined") {
resolve({
"failedInit": true,
"failedLocked": false,
"failedError": "IndexedDB not supported",
"database": null
});
return;
}
let dbOpen;
try {
dbOpen = indexedDB.open(filesystemDB, 1);
}catch(err) {
resolve({
"failedInit": true,
"failedLocked": false,
"failedError": "Exception opening database",
"database": null
});
return;
}
let resultConsumer = resolve;
dbOpen.addEventListener("success", function(evt) {
if(resultConsumer) resultConsumer({
"failedInit": false,
"failedLocked": false,
"failedError": null,
"database": dbOpen.result
});
resultConsumer = null;
});
dbOpen.addEventListener("blocked", function(evt) {
if(resultConsumer) resultConsumer({
"failedInit": false,
"failedLocked": true,
"failedError": "Database is locked",
"database": null
});
resultConsumer = null;
});
dbOpen.addEventListener("error", function(evt) {
if(resultConsumer) resultConsumer({
"failedInit": true,
"failedLocked": false,
"failedError": "Opening database failed",
"database": null
});
resultConsumer = null;
});
dbOpen.addEventListener("upgradeneeded", function(evt) {
dbOpen.result.createObjectStore("filesystem", { keyPath: ["path"] });
});
});
}
eagruntimeImpl.platformFilesystem["openDB"] = new WebAssembly.Suspending(openDBImpl);
/**
* @param {IDBDatabase} database
* @param {string} pathName
* @return {Promise}
*/
function eaglerDeleteImpl(database, pathName) {
return new Promise(function(resolve) {
const tx = database.transaction("filesystem", "readwrite");
const r = tx.objectStore("filesystem").delete([pathName]);
r.addEventListener("success", function() {
resolve(true);
});
r.addEventListener("error", function() {
resolve(false);
});
});
}
eagruntimeImpl.platformFilesystem["eaglerDelete"] = new WebAssembly.Suspending(eaglerDeleteImpl);
/**
* @param {IDBDatabase} database
* @param {string} pathName
* @return {Promise}
*/
function eaglerReadImpl(database, pathName) {
return new Promise(function(resolve) {
const tx = database.transaction("filesystem", "readonly");
const r = tx.objectStore("filesystem").get([pathName]);
r.addEventListener("success", function() {
resolve(readDBRow(r.result));
});
r.addEventListener("error", function() {
resolve(null);
});
});
}
eagruntimeImpl.platformFilesystem["eaglerRead"] = new WebAssembly.Suspending(eaglerReadImpl);
/**
* @param {IDBDatabase} database
* @param {string} pathName
* @param {ArrayBuffer} arr
* @return {Promise}
*/
function eaglerWriteImpl(database, pathName, arr) {
return new Promise(function(resolve) {
const tx = database.transaction("filesystem", "readwrite");
const r = tx.objectStore("filesystem").put(writeDBRow(pathName, arr));
r.addEventListener("success", function() {
resolve(true);
});
r.addEventListener("error", function() {
resolve(false);
});
});
}
eagruntimeImpl.platformFilesystem["eaglerWrite"] = new WebAssembly.Suspending(eaglerWriteImpl);
/**
* @param {IDBDatabase} database
* @param {string} pathName
* @return {Promise}
*/
function eaglerExistsImpl(database, pathName) {
return new Promise(function(resolve) {
const tx = database.transaction("filesystem", "readonly");
const r = tx.objectStore("filesystem").count([pathName]);
r.addEventListener("success", function() {
resolve(r.result > 0);
});
r.addEventListener("error", function() {
resolve(false);
});
});
}
eagruntimeImpl.platformFilesystem["eaglerExists"] = new WebAssembly.Suspending(eaglerExistsImpl);
/**
* @param {IDBDatabase} database
* @param {string} pathNameOld
* @param {string} pathNameNew
* @return {Promise<boolean>}
*/
async function eaglerMoveImpl(database, pathNameOld, pathNameNew) {
const oldData = await eaglerReadImpl(database, pathNameOld);
if(!oldData || !(await eaglerWriteImpl(database, pathNameNew, oldData))) {
return false;
}
return await eaglerDeleteImpl(database, pathNameOld);
}
eagruntimeImpl.platformFilesystem["eaglerMove"] = new WebAssembly.Suspending(eaglerMoveImpl);
/**
* @param {IDBDatabase} database
* @param {string} pathNameOld
* @param {string} pathNameNew
* @return {Promise<boolean>}
*/
async function eaglerCopyImpl(database, pathNameOld, pathNameNew) {
const oldData = await eaglerReadImpl(database, pathNameOld);
return oldData && (await eaglerWriteImpl(database, pathNameNew, oldData));
}
eagruntimeImpl.platformFilesystem["eaglerCopy"] = new WebAssembly.Suspending(eaglerCopyImpl);
/**
* @param {IDBDatabase} database
* @param {string} pathName
* @return {Promise}
*/
function eaglerSizeImpl(database, pathName) {
return new Promise(function(resolve) {
const tx = database.transaction("filesystem", "readonly");
const r = tx.objectStore("filesystem").get([pathName]);
r.addEventListener("success", function() {
const data = readDBRow(r.result);
resolve(data ? data.byteLength : -1);
});
r.addEventListener("error", function() {
resolve(-1);
});
});
}
eagruntimeImpl.platformFilesystem["eaglerSize"] = new WebAssembly.Suspending(eaglerSizeImpl);
/**
* @param {string} str
* @return {number}
*/
function countSlashes(str) {
if(str.length === 0) return -1;
var j = 0;
for(var i = 0, l = str.length; i < l; ++i) {
if(str.charCodeAt(i) === 47) {
++j;
}
}
return j;
}
/**
* @param {IDBDatabase} database
* @param {string} pathName
* @param {number} recursive
* @return {Promise}
*/
function eaglerIterateImpl(database, pathName, recursive) {
return new Promise(function(resolve) {
const rows = [];
const tx = database.transaction("filesystem", "readonly");
const r = tx.objectStore("filesystem").openCursor();
const b = pathName.length === 0;
const pc = recursive ? -1 : countSlashes(pathName);
r.addEventListener("success", function() {
const c = r.result;
if(c === null || c.key === null) {
resolve({
"length": rows.length,
/**
* @param {number} idx
* @return {string}
*/
"getRow": function(idx) {
return rows[idx];
}
});
return;
}
const k = readDBKey(c.key);
if(k != null) {
if((b || k.startsWith(pathName)) && (recursive || countSlashes(k) === pc)) {
rows.push(k);
}
}
c.continue();
});
r.addEventListener("error", function() {
resolve(null);
});
});
}
eagruntimeImpl.platformFilesystem["eaglerIterate"] = new WebAssembly.Suspending(eaglerIterateImpl);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,365 @@
/*
* 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 platfNetworkingName = "platformNetworking";
(function() {
const WS_CLOSED = 0;
const WS_CONNECTING = 1;
const WS_CONNECTED = 2;
const WS_FAILED = 3;
function closeSocketImpl() {
this["_socket"].close();
}
/**
* @param {string} str
*/
function sendStringFrameImpl(str) {
this["_socket"].send(str);
}
/**
* @param {Uint8Array} bin
*/
function sendBinaryFrameImpl(bin) {
this["_socket"].send(bin);
}
/**
* @return {number}
*/
function availableFramesImpl() {
return this["_frameCountStr"] + this["_frameCountBin"];
}
/**
* @return {Object}
*/
function getNextFrameImpl() {
const f = this["_queue"];
if(f) {
if(f["_next"] === f && f["_prev"] === f) {
this["_queue"] = null;
}else {
this["_queue"] = f["_next"];
f["_prev"]["_next"] = f["_next"];
f["_next"]["_prev"] = f["_prev"];
}
f["_next"] = null;
f["_prev"] = null;
if(f["type"] === 0) {
--this["_frameCountStr"];
}else {
--this["_frameCountBin"];
}
return f;
}else {
return null;
}
}
/**
* @return {Array}
*/
function getAllFramesImpl() {
const len = this["_frameCountStr"] + this["_frameCountBin"];
if(len === 0) {
return null;
}
const ret = new Array(len);
var idx = 0;
var f = this["_queue"];
var g;
const ff = f;
do {
ret[idx++] = f;
g = f["_next"];
f["_next"] = null;
f["_prev"] = null;
f = g;
}while(f !== ff);
this["_queue"] = null;
this["_frameCountStr"] = 0;
this["_frameCountBin"] = 0;
return ret;
}
function clearFramesImpl() {
this["_queue"] = null;
this["_frameCountStr"] = 0;
this["_frameCountBin"] = 0;
}
/**
* @param {Object} thisObj
* @param {number} type
* @return {Object}
*/
function getNextTypedFrameImpl(thisObj, type) {
var f = thisObj["_queue"];
if(!f) {
return null;
}
var g, h;
const ff = f;
do {
g = f["_next"];
if(f["type"] === type) {
h = f["_prev"];
if(g === f && h === f) {
thisObj["_queue"] = null;
}else {
if(f === ff) {
thisObj["_queue"] = g;
}
h["_next"] = g;
g["_prev"] = h;
}
f["_next"] = null;
f["_prev"] = null;
return f;
}
f = g;
}while(f !== ff);
return null;
}
/**
* @param {Object} thisObj
* @param {number} type
* @param {Array} ret
*/
function getAllTypedFrameImpl(thisObj, type, ret) {
var idx = 0;
var f = thisObj["_queue"];
var g, h;
const ff = f;
do {
g = f["_next"];
if(f["type"] === type) {
ret[idx++] = f;
}
f = g;
}while(f !== ff);
ret.length = idx;
for(var i = 0; i < idx; ++i) {
f = ret[i];
g = f["_next"];
h = f["_prev"];
if(g === f && h === f) {
thisObj["_queue"] = null;
}else {
if(f === thisObj["_queue"]) {
thisObj["_queue"] = g;
}
h["_next"] = g;
g["_prev"] = h;
}
}
}
/**
* @return {number}
*/
function availableStringFramesImpl() {
return this["_frameCountStr"];
}
/**
* @return {Object}
*/
function getNextStringFrameImpl() {
const len = this["_frameCountStr"];
if(len === 0) {
return null;
}
const ret = getNextTypedFrameImpl(this, 0);
if(!ret) {
this["_frameCountStr"] = 0;
}else {
--this["_frameCountStr"];
}
return ret;
}
/**
* @return {Array}
*/
function getAllStringFramesImpl() {
const len = this["_frameCountStr"];
if(len === 0) {
return null;
}
const ret = new Array(len);
getAllTypedFrameImpl(this, 0, ret);
this["_frameCountStr"] = 0;
return ret;
}
function clearStringFramesImpl() {
const len = this["_frameCountStr"];
if(len === 0) {
return null;
}
const ret = new Array(len);
getAllTypedFrameImpl(this, 0, ret);
this["_frameCountStr"] = 0;
}
/**
* @return {number}
*/
function availableBinaryFramesImpl() {
return this["_frameCountBin"];
}
/**
* @return {Object}
*/
function getNextBinaryFrameImpl() {
const len = this["_frameCountBin"];
if(len === 0) {
return null;
}
const ret = getNextTypedFrameImpl(this, 1);
if(!ret) {
this["_frameCountBin"] = 0;
}else {
--this["_frameCountBin"];
}
return ret;
}
/**
* @return {Array}
*/
function getAllBinaryFramesImpl() {
const len = this["_frameCountBin"];
if(len === 0) {
return null;
}
const ret = new Array(len);
getAllTypedFrameImpl(this, 1, ret);
this["_frameCountBin"] = 0;
return ret;
}
function clearBinaryFramesImpl() {
const len = this["_frameCountBin"];
if(len === 0) {
return null;
}
const ret = new Array(len);
getAllTypedFrameImpl(this, 1, ret);
this["_frameCountBin"] = 0;
}
function addRecievedFrameImpl(dat) {
const isStr = (typeof dat === "string");
const itm = {
"type": (isStr ? 0 : 1),
"data": dat,
"timestamp": performance.now(),
"_next": null,
"_prev": null
};
const first = this["_queue"];
if(!first) {
this["_queue"] = itm;
itm["_next"] = itm;
itm["_prev"] = itm;
}else {
const last = first["_prev"];
last["_next"] = itm;
itm["_prev"] = last;
itm["_next"] = first;
first["_prev"] = itm;
}
if(isStr) {
++this["_frameCountStr"];
}else {
++this["_frameCountBin"];
}
}
/**
* @param {string} socketURI
* @return {Object}
*/
eagruntimeImpl.platformNetworking["createWebSocketHandle"] = function(socketURI) {
let sock;
try {
sock = new WebSocket(socketURI);
}catch(ex) {
eagError("Failed to create WebSocket: {}", socketURI);
eagStackTrace(ERROR, "Exception Caught", ex);
return null;
}
sock.binaryType = "arraybuffer";
const ret = {
"state": WS_CONNECTING,
"_socket": sock,
"_queue": null,
"_frameCountStr": 0,
"_frameCountBin": 0,
"_addRecievedFrame": addRecievedFrameImpl,
"closeSocket": closeSocketImpl,
"sendStringFrame": sendStringFrameImpl,
"sendBinaryFrame": sendBinaryFrameImpl,
"availableFrames": availableFramesImpl,
"getNextFrame": getNextFrameImpl,
"getAllFrames": getAllFramesImpl,
"clearFrames": clearFramesImpl,
"availableStringFrames": availableStringFramesImpl,
"getNextStringFrame": getNextStringFrameImpl,
"getAllStringFrames": getAllStringFramesImpl,
"clearStringFrames": clearStringFramesImpl,
"availableBinaryFrames": availableBinaryFramesImpl,
"getNextBinaryFrame": getNextBinaryFrameImpl,
"getAllBinaryFrames": getAllBinaryFramesImpl,
"clearBinaryFrames": clearBinaryFramesImpl
};
sock.addEventListener("open", function(evt) {
ret["state"] = WS_CONNECTED;
});
sock.addEventListener("message", function(evt) {
ret["_addRecievedFrame"](evt.data);
});
sock.addEventListener("close", function(evt) {
if(ret["state"] !== WS_FAILED) {
ret["state"] = WS_CLOSED;
}
});
sock.addEventListener("error", function(evt) {
if(ret["state"] === WS_CONNECTING) {
ret["state"] = WS_FAILED;
}
});
return ret;
};
})();

View File

@ -0,0 +1,387 @@
/*
* 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 VAO_IMPL_NONE = -1;
const VAO_IMPL_CORE = 0;
const VAO_IMPL_OES = 1;
const INSTANCE_IMPL_NONE = -1;
const INSTANCE_IMPL_CORE = 0;
const INSTANCE_IMPL_ANGLE = 1;
const CAP_A_BIT_EXT_GPU_SHADER5 = 1;
const CAP_A_BIT_OES_GPU_SHADER5 = 2;
const CAP_A_BIT_FBO_RENDER_MIPMAP = 4;
const CAP_A_BIT_TEXTURE_LOD_CAPABLE = 8;
const CAP_A_BIT_NPOT_CAPABLE = 16;
const CAP_A_BIT_HDR_FBO16F = 32;
const CAP_A_BIT_HDR_FBO32F = 64;
const CAP_A_BIT_ANISOTROPIC = 128;
const CAP_B_BIT_HDR_LINEAR16F = 1;
const CAP_B_BIT_HDR_LINEAR32F = 2;
const platfOpenGLName = "platformOpenGL";
/**
* @param {WebGL2RenderingContext} ctx
* @param {number} glesVersIn
* @param {boolean} allowExts
* @param {Object} glImports
*/
function setCurrentGLContext(ctx, glesVersIn, allowExts, glImports) {
const wglExtVAO = (allowExts && glesVersIn === 200) ? ctx.getExtension("OES_vertex_array_object") : null;
const wglExtInstancing = (allowExts && glesVersIn === 200) ? ctx.getExtension("ANGLE_instanced_arrays") : null;
const hasANGLEInstancedArrays = allowExts && glesVersIn === 200 && wglExtInstancing !== null;
const hasEXTColorBufferFloat = allowExts && (glesVersIn === 310 || glesVersIn === 300) && ctx.getExtension("EXT_color_buffer_float") !== null;
const hasEXTColorBufferHalfFloat = allowExts && !hasEXTColorBufferFloat && (glesVersIn === 310 || glesVersIn === 300 || glesVersIn === 200)
&& ctx.getExtension("EXT_color_buffer_half_float") !== null;
const hasEXTShaderTextureLOD = allowExts && glesVersIn === 200 && ctx.getExtension("EXT_shader_texture_lod") !== null;
const hasOESFBORenderMipmap = allowExts && glesVersIn === 200 && ctx.getExtension("OES_fbo_render_mipmap") !== null;
const hasOESVertexArrayObject = allowExts && glesVersIn === 200 && wglExtVAO !== null;
const hasOESTextureFloat = allowExts && glesVersIn === 200 && ctx.getExtension("OES_texture_float") !== null;
const hasOESTextureFloatLinear = allowExts && glesVersIn >= 300 && ctx.getExtension("OES_texture_float_linear") !== null;
const hasOESTextureHalfFloat = allowExts && glesVersIn === 200 && ctx.getExtension("OES_texture_half_float") !== null;
const hasOESTextureHalfFloatLinear = allowExts && glesVersIn === 200 && ctx.getExtension("OES_texture_half_float_linear") !== null;
const hasEXTTextureFilterAnisotropic = allowExts && ctx.getExtension("EXT_texture_filter_anisotropic") !== null;
const hasWEBGLDebugRendererInfo = ctx.getExtension("WEBGL_debug_renderer_info") !== null;
const hasFBO16FSupport = glesVersIn >= 320 || ((glesVersIn >= 300 || hasOESTextureFloat) && (hasEXTColorBufferFloat || hasEXTColorBufferHalfFloat));
const hasFBO32FSupport = glesVersIn >= 320 || ((glesVersIn >= 300 || hasOESTextureHalfFloat) && hasEXTColorBufferFloat);
const hasLinearHDR16FSupport = glesVersIn >= 300 || hasOESTextureHalfFloatLinear;
const hasLinearHDR32FSupport = glesVersIn >= 300 && hasOESTextureFloatLinear;
const vertexArrayImpl = glesVersIn >= 300 ? VAO_IMPL_CORE : ((glesVersIn === 200 && hasOESVertexArrayObject) ? VAO_IMPL_OES : VAO_IMPL_NONE);
const instancingImpl = glesVersIn >= 300 ? INSTANCE_IMPL_CORE : ((glesVersIn === 200 && hasANGLEInstancedArrays) ? INSTANCE_IMPL_ANGLE : INSTANCE_IMPL_NONE);
const capBits = [ glesVersIn, vertexArrayImpl, instancingImpl, 0, 0 ];
if(glesVersIn >= 300 || hasOESFBORenderMipmap) capBits[3] |= CAP_A_BIT_FBO_RENDER_MIPMAP;
if(glesVersIn >= 300 || hasEXTShaderTextureLOD) capBits[3] |= CAP_A_BIT_TEXTURE_LOD_CAPABLE;
if(glesVersIn >= 300) capBits[3] |= CAP_A_BIT_NPOT_CAPABLE;
if(hasFBO16FSupport) capBits[3] |= CAP_A_BIT_HDR_FBO16F;
if(hasFBO32FSupport) capBits[3] |= CAP_A_BIT_HDR_FBO32F;
if(hasEXTTextureFilterAnisotropic) capBits[3] |= CAP_A_BIT_ANISOTROPIC;
if(hasLinearHDR16FSupport) capBits[4] |= CAP_B_BIT_HDR_LINEAR16F;
if(hasLinearHDR32FSupport) capBits[4] |= CAP_B_BIT_HDR_LINEAR32F;
/**
* @param {number} idx
* @return {number}
*/
glImports["getCapBits"] = function(idx) {
return capBits[idx];
};
glImports["glEnable"] = ctx.enable.bind(ctx);
glImports["glDisable"] = ctx.disable.bind(ctx);
glImports["glClearColor"] = ctx.clearColor.bind(ctx);
glImports["glClearDepth"] = ctx.clearDepth.bind(ctx);
glImports["glClear"] = ctx.clear.bind(ctx);
glImports["glDepthFunc"] = ctx.depthFunc.bind(ctx);
glImports["glDepthMask"] = ctx.depthMask.bind(ctx);
glImports["glCullFace"] = ctx.cullFace.bind(ctx);
glImports["glViewport"] = ctx.viewport.bind(ctx);
glImports["glBlendFunc"] = ctx.blendFunc.bind(ctx);
glImports["glBlendFuncSeparate"] = ctx.blendFuncSeparate.bind(ctx);
glImports["glBlendEquation"] = ctx.blendEquation.bind(ctx);
glImports["glBlendColor"] = ctx.blendColor.bind(ctx);
glImports["glColorMask"] = ctx.colorMask.bind(ctx);
glImports["glDrawBuffers"] = glesVersIn >= 300 ? ctx.drawBuffers.bind(ctx) : unsupportedFunc(platfOpenGLName, "glDrawBuffers");
glImports["glReadBuffer"] = glesVersIn >= 300 ? ctx.readBuffer.bind(ctx) : unsupportedFunc(platfOpenGLName, "glReadBuffer");
glImports["glReadPixels"] = ctx.readPixels.bind(ctx);
glImports["glPolygonOffset"] = ctx.polygonOffset.bind(ctx);
glImports["glLineWidth"] = ctx.lineWidth.bind(ctx);
glImports["glGenBuffers"] = ctx.createBuffer.bind(ctx);
glImports["glGenTextures"] = ctx.createTexture.bind(ctx);
glImports["glCreateProgram"] = ctx.createProgram.bind(ctx);
glImports["glCreateShader"] = ctx.createShader.bind(ctx);
glImports["glCreateFramebuffer"] = ctx.createFramebuffer.bind(ctx);
glImports["glCreateRenderbuffer"] = ctx.createRenderbuffer.bind(ctx);
glImports["glGenQueries"] = glesVersIn >= 300 ? ctx.createQuery.bind(ctx) : unsupportedFunc(platfOpenGLName, "glGenQueries");
glImports["glDeleteBuffers"] = ctx.deleteBuffer.bind(ctx);
glImports["glDeleteTextures"] = ctx.deleteTexture.bind(ctx);
glImports["glDeleteProgram"] = ctx.deleteProgram.bind(ctx);
glImports["glDeleteShader"] = ctx.deleteShader.bind(ctx);
glImports["glDeleteFramebuffer"] = ctx.deleteFramebuffer.bind(ctx);
glImports["glDeleteRenderbuffer"] = ctx.deleteRenderbuffer.bind(ctx);
glImports["glDeleteQueries"] = glesVersIn >= 300 ? ctx.deleteQuery.bind(ctx) : unsupportedFunc(platfOpenGLName, "glDeleteQueries");
glImports["glBindBuffer"] = ctx.bindBuffer.bind(ctx);
glImports["glBufferData"] = ctx.bufferData.bind(ctx);
glImports["glBufferSubData"] = ctx.bufferSubData.bind(ctx);
glImports["glEnableVertexAttribArray"] = ctx.enableVertexAttribArray.bind(ctx);
glImports["glDisableVertexAttribArray"] = ctx.disableVertexAttribArray.bind(ctx);
glImports["glVertexAttribPointer"] = ctx.vertexAttribPointer.bind(ctx);
glImports["glActiveTexture"] = ctx.activeTexture.bind(ctx);
glImports["glBindTexture"] = ctx.bindTexture.bind(ctx);
glImports["glTexParameterf"] = ctx.texParameterf.bind(ctx);
glImports["glTexParameteri"] = ctx.texParameteri.bind(ctx);
glImports["glTexImage3D"] = glesVersIn >= 300 ? ctx.texImage3D.bind(ctx) : unsupportedFunc(platfOpenGLName, "glTexImage3D");
glImports["glTexImage2D"] = ctx.texImage2D.bind(ctx);
glImports["glTexSubImage2D"] = ctx.texSubImage2D.bind(ctx);
glImports["glCopyTexSubImage2D"] = ctx.copyTexSubImage2D.bind(ctx);
glImports["glTexStorage2D"] = glesVersIn >= 300 ? ctx.texStorage2D.bind(ctx) : unsupportedFunc(platfOpenGLName, "glTexStorage2D");
glImports["glPixelStorei"] = ctx.pixelStorei.bind(ctx);
glImports["glGenerateMipmap"] = ctx.generateMipmap.bind(ctx);
glImports["glShaderSource"] = ctx.shaderSource.bind(ctx);
glImports["glCompileShader"] = ctx.compileShader.bind(ctx);
glImports["glGetShaderi"] = ctx.getShaderParameter.bind(ctx);
glImports["glGetShaderInfoLog"] = ctx.getShaderInfoLog.bind(ctx);
glImports["glUseProgram"] = ctx.useProgram.bind(ctx);
glImports["glAttachShader"] = ctx.attachShader.bind(ctx);
glImports["glDetachShader"] = ctx.detachShader.bind(ctx);
glImports["glLinkProgram"] = ctx.linkProgram.bind(ctx);
glImports["glGetProgrami"] = ctx.getProgramParameter.bind(ctx);
glImports["glGetProgramInfoLog"] = ctx.getProgramInfoLog.bind(ctx);
glImports["glDrawArrays"] = ctx.drawArrays.bind(ctx);
glImports["glDrawElements"] = ctx.drawElements.bind(ctx);
glImports["glBindAttribLocation"] = ctx.bindAttribLocation.bind(ctx);
glImports["glGetAttribLocation"] = ctx.getAttribLocation.bind(ctx);
glImports["glGetUniformLocation"] = ctx.getUniformLocation.bind(ctx);
glImports["glGetUniformBlockIndex"] = glesVersIn >= 300 ? ctx.getUniformBlockIndex.bind(ctx) : unsupportedFunc(platfOpenGLName, "glGetUniformBlockIndex");
glImports["glBindBufferRange"] = glesVersIn >= 300 ? ctx.bindBufferRange.bind(ctx) : unsupportedFunc(platfOpenGLName, "glBindBufferRange");
glImports["glUniformBlockBinding"] = glesVersIn >= 300 ? ctx.uniformBlockBinding.bind(ctx) : unsupportedFunc(platfOpenGLName, "glUniformBlockBinding");
glImports["glUniform1f"] = ctx.uniform1f.bind(ctx);
glImports["glUniform2f"] = ctx.uniform2f.bind(ctx);
glImports["glUniform3f"] = ctx.uniform3f.bind(ctx);
glImports["glUniform4f"] = ctx.uniform4f.bind(ctx);
glImports["glUniform1i"] = ctx.uniform1i.bind(ctx);
glImports["glUniform2i"] = ctx.uniform2i.bind(ctx);
glImports["glUniform3i"] = ctx.uniform3i.bind(ctx);
glImports["glUniform4i"] = ctx.uniform4i.bind(ctx);
glImports["glUniformMatrix2fv"] = ctx.uniformMatrix2fv.bind(ctx);
glImports["glUniformMatrix3fv"] = ctx.uniformMatrix3fv.bind(ctx);
glImports["glUniformMatrix4fv"] = ctx.uniformMatrix4fv.bind(ctx);
glImports["glUniformMatrix3x2fv"] = glesVersIn >= 300 ? ctx.uniformMatrix3x2fv.bind(ctx) : unsupportedFunc(platfOpenGLName, "glUniformMatrix3x2fv");
glImports["glUniformMatrix4x2fv"] = glesVersIn >= 300 ? ctx.uniformMatrix4x2fv.bind(ctx) : unsupportedFunc(platfOpenGLName, "glUniformMatrix4x2fv");
glImports["glUniformMatrix4x3fv"] = glesVersIn >= 300 ? ctx.uniformMatrix4x3fv.bind(ctx) : unsupportedFunc(platfOpenGLName, "glUniformMatrix4x3fv");
glImports["glBindFramebuffer"] = ctx.bindFramebuffer.bind(ctx);
glImports["glCheckFramebufferStatus"] = ctx.checkFramebufferStatus.bind(ctx);
glImports["glBlitFramebuffer"] = glesVersIn >= 300 ? ctx.blitFramebuffer.bind(ctx) : unsupportedFunc(platfOpenGLName, "glBlitFramebuffer");
glImports["glRenderbufferStorage"] = ctx.renderbufferStorage.bind(ctx);
glImports["glFramebufferTexture2D"] = ctx.framebufferTexture2D.bind(ctx);
glImports["glFramebufferTextureLayer"] = glesVersIn >= 300 ? ctx.framebufferTextureLayer.bind(ctx) : unsupportedFunc(platfOpenGLName, "glFramebufferTextureLayer");
glImports["glBindRenderbuffer"] = ctx.bindRenderbuffer.bind(ctx);
glImports["glFramebufferRenderbuffer"] = ctx.framebufferRenderbuffer.bind(ctx);
glImports["glGetError"] = ctx.getError.bind(ctx);
glImports["getAllExtensions"] = ctx.getSupportedExtensions.bind(ctx);
glImports["isContextLost"] = ctx.isContextLost.bind(ctx);
const exts = [];
if(hasANGLEInstancedArrays) exts.push("ANGLE_instanced_arrays");
if(hasEXTColorBufferFloat) exts.push("EXT_color_buffer_float");
if(hasEXTColorBufferHalfFloat) exts.push("EXT_color_buffer_half_float");
if(hasEXTShaderTextureLOD) exts.push("EXT_shader_texture_lod");
if(hasOESFBORenderMipmap) exts.push("OES_fbo_render_mipmap");
if(hasOESVertexArrayObject) exts.push("OES_vertex_array_object");
if(hasOESTextureFloat) exts.push("OES_texture_float");
if(hasOESTextureFloatLinear) exts.push("OES_texture_float_linear");
if(hasOESTextureHalfFloat) exts.push("OES_texture_half_float");
if(hasOESTextureHalfFloatLinear) exts.push("OES_texture_half_float_linear");
if(hasEXTTextureFilterAnisotropic) exts.push("EXT_texture_filter_anisotropic");
if(hasWEBGLDebugRendererInfo) exts.push("WEBGL_debug_renderer_info");
/**
* @return {Array}
*/
glImports["dumpActiveExtensions"] = function() {
return exts;
};
/**
* @param {number} p
* @return {number}
*/
glImports["glGetInteger"] = function(p) {
const ret = /** @type {*} */ (ctx.getParameter(p));
return (typeof ret === "number") ? (/** @type {number} */ (ret)) : 0;
};
/**
* @param {number} p
* @return {string|null}
*/
glImports["glGetString"] = function(p) {
var s;
if(hasWEBGLDebugRendererInfo) {
switch(p) {
case 0x1f00: // VENDOR
s = ctx.getParameter(0x9245); // UNMASKED_VENDOR_WEBGL
if(s == null) {
s = ctx.getParameter(0x1f00); // VENDOR
}
break;
case 0x1f01: // RENDERER
s = ctx.getParameter(0x9246); // UNMASKED_RENDERER_WEBGL
if(s == null) {
s = ctx.getParameter(0x1f01); // RENDERER
}
break;
default:
s = ctx.getParameter(p);
break;
}
}else {
s = ctx.getParameter(p);
}
if(typeof s === "string") {
return s;
}else {
return null;
}
};
switch(vertexArrayImpl) {
case VAO_IMPL_CORE:
glImports["glGenVertexArrays"] = ctx.createVertexArray.bind(ctx);
glImports["glDeleteVertexArrays"] = ctx.deleteVertexArray.bind(ctx);
glImports["glBindVertexArray"] = ctx.bindVertexArray.bind(ctx);
break;
case VAO_IMPL_OES:
glImports["glGenVertexArrays"] = wglExtVAO.createVertexArrayOES.bind(wglExtVAO);
glImports["glDeleteVertexArrays"] = wglExtVAO.deleteVertexArrayOES.bind(wglExtVAO);
glImports["glBindVertexArray"] = wglExtVAO.bindVertexArrayOES.bind(wglExtVAO);
break;
case VAO_IMPL_NONE:
default:
setUnsupportedFunc(glImports, platfOpenGLName, "glGenVertexArrays");
setUnsupportedFunc(glImports, platfOpenGLName, "glDeleteVertexArrays");
setUnsupportedFunc(glImports, platfOpenGLName, "glBindVertexArray");
break;
}
switch(instancingImpl) {
case INSTANCE_IMPL_CORE:
glImports["glVertexAttribDivisor"] = ctx.vertexAttribDivisor.bind(ctx);
glImports["glDrawArraysInstanced"] = ctx.drawArraysInstanced.bind(ctx);
glImports["glDrawElementsInstanced"] = ctx.drawElementsInstanced.bind(ctx);
break;
case INSTANCE_IMPL_ANGLE:
glImports["glVertexAttribDivisor"] = wglExtInstancing.vertexAttribDivisorANGLE.bind(wglExtInstancing);
glImports["glDrawArraysInstanced"] = wglExtInstancing.drawArraysInstancedANGLE.bind(wglExtInstancing);
glImports["glDrawElementsInstanced"] = wglExtInstancing.drawElementsInstancedANGLE.bind(wglExtInstancing);
break;
case INSTANCE_IMPL_NONE:
default:
setUnsupportedFunc(glImports, platfOpenGLName, "glVertexAttribDivisor");
setUnsupportedFunc(glImports, platfOpenGLName, "glDrawArraysInstanced");
setUnsupportedFunc(glImports, platfOpenGLName, "glDrawElementsInstanced");
break;
}
}
function setNoGLContext(glImports) {
setUnsupportedFunc(glImports, platfOpenGLName, "getCapBits");
setUnsupportedFunc(glImports, platfOpenGLName, "glEnable");
setUnsupportedFunc(glImports, platfOpenGLName, "glDisable");
setUnsupportedFunc(glImports, platfOpenGLName, "glClearColor");
setUnsupportedFunc(glImports, platfOpenGLName, "glClearDepth");
setUnsupportedFunc(glImports, platfOpenGLName, "glClear");
setUnsupportedFunc(glImports, platfOpenGLName, "glDepthFunc");
setUnsupportedFunc(glImports, platfOpenGLName, "glDepthMask");
setUnsupportedFunc(glImports, platfOpenGLName, "glCullFace");
setUnsupportedFunc(glImports, platfOpenGLName, "glViewport");
setUnsupportedFunc(glImports, platfOpenGLName, "glBlendFunc");
setUnsupportedFunc(glImports, platfOpenGLName, "glBlendFuncSeparate");
setUnsupportedFunc(glImports, platfOpenGLName, "glBlendEquation");
setUnsupportedFunc(glImports, platfOpenGLName, "glBlendColor");
setUnsupportedFunc(glImports, platfOpenGLName, "glColorMask");
setUnsupportedFunc(glImports, platfOpenGLName, "glDrawBuffers");
setUnsupportedFunc(glImports, platfOpenGLName, "glReadBuffer");
setUnsupportedFunc(glImports, platfOpenGLName, "glReadPixels");
setUnsupportedFunc(glImports, platfOpenGLName, "glPolygonOffset");
setUnsupportedFunc(glImports, platfOpenGLName, "glLineWidth");
setUnsupportedFunc(glImports, platfOpenGLName, "glGenBuffers");
setUnsupportedFunc(glImports, platfOpenGLName, "glGenTextures");
setUnsupportedFunc(glImports, platfOpenGLName, "glCreateProgram");
setUnsupportedFunc(glImports, platfOpenGLName, "glCreateShader");
setUnsupportedFunc(glImports, platfOpenGLName, "glCreateFramebuffer");
setUnsupportedFunc(glImports, platfOpenGLName, "glCreateRenderbuffer");
setUnsupportedFunc(glImports, platfOpenGLName, "glGenQueries");
setUnsupportedFunc(glImports, platfOpenGLName, "glDeleteBuffers");
setUnsupportedFunc(glImports, platfOpenGLName, "glDeleteTextures");
setUnsupportedFunc(glImports, platfOpenGLName, "glDeleteProgram");
setUnsupportedFunc(glImports, platfOpenGLName, "glDeleteShader");
setUnsupportedFunc(glImports, platfOpenGLName, "glDeleteFramebuffer");
setUnsupportedFunc(glImports, platfOpenGLName, "glDeleteRenderbuffer");
setUnsupportedFunc(glImports, platfOpenGLName, "glDeleteQueries");
setUnsupportedFunc(glImports, platfOpenGLName, "glBindBuffer");
setUnsupportedFunc(glImports, platfOpenGLName, "glBufferData");
setUnsupportedFunc(glImports, platfOpenGLName, "glBufferSubData");
setUnsupportedFunc(glImports, platfOpenGLName, "glEnableVertexAttribArray");
setUnsupportedFunc(glImports, platfOpenGLName, "glDisableVertexAttribArray");
setUnsupportedFunc(glImports, platfOpenGLName, "glVertexAttribPointer");
setUnsupportedFunc(glImports, platfOpenGLName, "glActiveTexture");
setUnsupportedFunc(glImports, platfOpenGLName, "glBindTexture");
setUnsupportedFunc(glImports, platfOpenGLName, "glTexParameterf");
setUnsupportedFunc(glImports, platfOpenGLName, "glTexParameteri");
setUnsupportedFunc(glImports, platfOpenGLName, "glTexImage3D");
setUnsupportedFunc(glImports, platfOpenGLName, "glTexImage2D");
setUnsupportedFunc(glImports, platfOpenGLName, "glTexSubImage2D");
setUnsupportedFunc(glImports, platfOpenGLName, "glCopyTexSubImage2D");
setUnsupportedFunc(glImports, platfOpenGLName, "glTexStorage2D");
setUnsupportedFunc(glImports, platfOpenGLName, "glPixelStorei");
setUnsupportedFunc(glImports, platfOpenGLName, "glGenerateMipmap");
setUnsupportedFunc(glImports, platfOpenGLName, "glShaderSource");
setUnsupportedFunc(glImports, platfOpenGLName, "glCompileShader");
setUnsupportedFunc(glImports, platfOpenGLName, "glGetShaderi");
setUnsupportedFunc(glImports, platfOpenGLName, "glGetShaderInfoLog");
setUnsupportedFunc(glImports, platfOpenGLName, "glUseProgram");
setUnsupportedFunc(glImports, platfOpenGLName, "glAttachShader");
setUnsupportedFunc(glImports, platfOpenGLName, "glDetachShader");
setUnsupportedFunc(glImports, platfOpenGLName, "glLinkProgram");
setUnsupportedFunc(glImports, platfOpenGLName, "glGetProgrami");
setUnsupportedFunc(glImports, platfOpenGLName, "glGetProgramInfoLog");
setUnsupportedFunc(glImports, platfOpenGLName, "glDrawArrays");
setUnsupportedFunc(glImports, platfOpenGLName, "glDrawElements");
setUnsupportedFunc(glImports, platfOpenGLName, "glBindAttribLocation");
setUnsupportedFunc(glImports, platfOpenGLName, "glGetAttribLocation");
setUnsupportedFunc(glImports, platfOpenGLName, "glGetUniformLocation");
setUnsupportedFunc(glImports, platfOpenGLName, "glGetUniformBlockIndex");
setUnsupportedFunc(glImports, platfOpenGLName, "glBindBufferRange");
setUnsupportedFunc(glImports, platfOpenGLName, "glUniformBlockBinding");
setUnsupportedFunc(glImports, platfOpenGLName, "glUniform1f");
setUnsupportedFunc(glImports, platfOpenGLName, "glUniform2f");
setUnsupportedFunc(glImports, platfOpenGLName, "glUniform3f");
setUnsupportedFunc(glImports, platfOpenGLName, "glUniform4f");
setUnsupportedFunc(glImports, platfOpenGLName, "glUniform1i");
setUnsupportedFunc(glImports, platfOpenGLName, "glUniform2i");
setUnsupportedFunc(glImports, platfOpenGLName, "glUniform3i");
setUnsupportedFunc(glImports, platfOpenGLName, "glUniform4i");
setUnsupportedFunc(glImports, platfOpenGLName, "glUniformMatrix2fv");
setUnsupportedFunc(glImports, platfOpenGLName, "glUniformMatrix3fv");
setUnsupportedFunc(glImports, platfOpenGLName, "glUniformMatrix4fv");
setUnsupportedFunc(glImports, platfOpenGLName, "glUniformMatrix3x2fv");
setUnsupportedFunc(glImports, platfOpenGLName, "glUniformMatrix4x2fv");
setUnsupportedFunc(glImports, platfOpenGLName, "glUniformMatrix4x3fv")
setUnsupportedFunc(glImports, platfOpenGLName, "glBindFramebuffer");
setUnsupportedFunc(glImports, platfOpenGLName, "glCheckFramebufferStatus");
setUnsupportedFunc(glImports, platfOpenGLName, "glBlitFramebuffer");
setUnsupportedFunc(glImports, platfOpenGLName, "glRenderbufferStorage");
setUnsupportedFunc(glImports, platfOpenGLName, "glFramebufferTexture2D");
setUnsupportedFunc(glImports, platfOpenGLName, "glFramebufferTextureLayer");
setUnsupportedFunc(glImports, platfOpenGLName, "glBindRenderbuffer");
setUnsupportedFunc(glImports, platfOpenGLName, "glFramebufferRenderbuffer");
setUnsupportedFunc(glImports, platfOpenGLName, "glGetInteger");
setUnsupportedFunc(glImports, platfOpenGLName, "glGetError");
setUnsupportedFunc(glImports, platfOpenGLName, "getAllExtensions");
setUnsupportedFunc(glImports, platfOpenGLName, "dumpActiveExtensions");
setUnsupportedFunc(glImports, platfOpenGLName, "glGetString");
setUnsupportedFunc(glImports, platfOpenGLName, "glGenVertexArrays");
setUnsupportedFunc(glImports, platfOpenGLName, "glDeleteVertexArrays");
setUnsupportedFunc(glImports, platfOpenGLName, "glBindVertexArray");
setUnsupportedFunc(glImports, platfOpenGLName, "glVertexAttribDivisor");
setUnsupportedFunc(glImports, platfOpenGLName, "glDrawArraysInstanced");
setUnsupportedFunc(glImports, platfOpenGLName, "glDrawElementsInstanced");
setUnsupportedFunc(glImports, platfOpenGLName, "isContextLost");
}

View File

@ -0,0 +1,208 @@
/*
* 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 platfRuntimeName = "platformRuntime";
var allowImmediateContinue = false;
const immediateContinueChannel = new MessageChannel();
var immediateContinueHandler = null;
immediateContinueChannel.port2.addEventListener("message", function(evt) {
immediateContinueHandler();
});
async function initializePlatfRuntime() {
immediateContinueChannel.port1.start();
immediateContinueChannel.port2.start();
immediateContinueHandler = function() {
immediateContinueHandler = null;
};
immediateContinueChannel.port1.postMessage(0);
if(immediateContinueHandler) {
await new Promise(function(resolve) {
setTimeout(function() {
if(!immediateContinueHandler) {
allowImmediateContinue = true;
}else {
eagError("Immediate continue hack is not supported");
}
resolve();
}, 25);
});
}else {
eagError("Immediate continue hack is not supported");
}
}
/**
* @return {HTMLElement}
*/
eagruntimeImpl.platformRuntime["getRootElement"] = function() {
return rootElement;
};
/**
* @return {HTMLElement}
*/
eagruntimeImpl.platformRuntime["getParentElement"] = function() {
return parentElement;
};
/**
* @return {HTMLCanvasElement}
*/
eagruntimeImpl.platformRuntime["getCanvasElement"] = function() {
return canvasElement;
};
/**
* @return {Object}
*/
eagruntimeImpl.platformRuntime["getEaglercraftXOpts"] = function() {
return eaglercraftXOpts;
};
eagruntimeImpl.platformRuntime["getEventCount"] = mainEventQueue.getLength.bind(mainEventQueue);
eagruntimeImpl.platformRuntime["getNextEvent"] = mainEventQueue.shift.bind(mainEventQueue);
const EVENT_RUNTIME_ASYNC_DOWNLOAD = 0;
/**
* @param {string} uri
* @param {number} forceCache
* @param {number} id
*/
eagruntimeImpl.platformRuntime["queueAsyncDownload"] = function(uri, forceCache, id) {
try {
fetch(uri, /** @type {!RequestInit} */ ({
"cache": forceCache ? "force-cache" : "no-store",
"mode": "cors"
})).then(function(res) {
return res.arrayBuffer();
}).then(function(arr) {
pushEvent(EVENT_TYPE_RUNTIME, EVENT_RUNTIME_ASYNC_DOWNLOAD, {
"requestId": id,
"arrayBuffer": arr
});
}).catch(function(err) {
eagError("Failed to complete async download: {}", uri);
eagStackTrace(ERROR, "Exception Caught", /** @type {Error} */ (err));
pushEvent(EVENT_TYPE_RUNTIME, EVENT_RUNTIME_ASYNC_DOWNLOAD, {
"requestId": id,
"arrayBuffer": null
});
});
}catch(/** Error */ ex) {
eagError("Failed to fetch: {}", uri);
eagStackTrace(ERROR, "Exception Caught", ex);
pushEvent(EVENT_TYPE_RUNTIME, EVENT_RUNTIME_ASYNC_DOWNLOAD, {
"requestId": id,
"arrayBuffer": null
});
}
};
/**
* @param {string} uri
* @param {number} forceCache
* @return {Promise}
*/
function downloadImpl(uri, forceCache) {
return new Promise(function(resolve) {
try {
fetch(uri, /** @type {!RequestInit} */ ({
"cache": forceCache ? "force-cache" : "no-store",
"mode": "cors"
})).then(function(res) {
return res.arrayBuffer();
}).then(function(arr) {
resolve(arr);
}).catch(function(err) {
eagError("Failed to complete download: {}", uri);
eagStackTrace(ERROR, "Exception Caught", /** @type {Error} */ (err));
resolve(null);
});
}catch(/** Error */ ex) {
eagError("Failed to fetch: {}", uri);
eagStackTrace(ERROR, "Exception Caught", ex);
resolve(null);
}
});
}
eagruntimeImpl.platformRuntime["download"] = new WebAssembly.Suspending(downloadImpl);
/**
* @param {string} crashDump
*/
eagruntimeImpl.platformRuntime["writeCrashReport"] = function(crashDump) {
displayCrashReport(crashDump, false);
};
eagruntimeImpl.platformRuntime["steadyTimeMillis"] = performance.now.bind(performance);
/**
* @param {number} millis
* @return {Promise}
*/
function sleepImpl(millis) {
return new Promise(function(resolve) {
setTimeout(resolve, millis);
});
}
eagruntimeImpl.platformRuntime["sleep"] = new WebAssembly.Suspending(sleepImpl);
function immediateContinueResolver(resolve) {
if(allowImmediateContinue) {
immediateContinueHandler = resolve;
immediateContinueChannel.port1.postMessage(0);
}else {
setTimeout(resolve, 0);
}
}
/**
* @return {Promise}
*/
function immediateContinueImpl() {
return new Promise(immediateContinueResolver);
}
/**
* @return {Promise}
*/
function swapDelayImpl() {
if(!runtimeOpts.useDelayOnSwap) {
return immediateContinueImpl();
}else {
return sleepImpl(0);
}
}
eagruntimeImpl.platformRuntime["immediateContinue"] = new WebAssembly.Suspending(immediateContinueImpl);
/**
* @param {number} id
* @param {string} str
*/
eagruntimeImpl.platformRuntime["setCrashReportString"] = function(id, str) {
crashReportStrings[id] = str;
};

View File

@ -0,0 +1,123 @@
/*
* 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 platfScreenRecordName = "platformScreenRecord";
var canMic = (typeof window !== "undefined");
var mic = null;
/**
* @return {Promise<MediaStream|null>}
*/
function getMic0() {
return new Promise(function(resolve) {
if ("navigator" in window && "mediaDevices" in window.navigator && "getUserMedia" in window.navigator.mediaDevices) {
try {
window.navigator.mediaDevices.getUserMedia({
audio: true,
video: false
}).then(function(stream) {
resolve(stream);
}).catch(function(err) {
eagError("getUserMedia Error! (async)");
eagStackTrace(ERROR, "Exception Caught", /** @type {Error} */ (err));
resolve(null);
});
} catch(e) {
eagError("getUserMedia Error!");
resolve(null);
}
} else {
eagError("No getUserMedia!");
resolve(null);
}
});
}
/**
* @return {Promise<MediaStream|null>}
*/
async function getMicImpl() {
if (canMic) {
if (mic === null) {
mic = await getMic0();
if (mic === null) {
canMic = false;
return null;
}
return mic;
}
return mic;
}
return null;
}
function initializePlatfScreenRecord(screenRecImports) {
eagruntimeImpl.platformScreenRecord["getMic"] = new WebAssembly.Suspending(getMicImpl);
/**
* @param {string} nameStr
* @return {string}
*/
function formatScreenRecDate(nameStr) {
const d = new Date();
const fmt = d.getFullYear()
+ "-" + ("0" + (d.getMonth() + 1)).slice(-2)
+ "-" + ("0" + d.getDate()).slice(-2)
+ " " + ("0" + d.getHours()).slice(-2)
+ "-" + ("0" + d.getMinutes()).slice(-2)
+ "-" + ("0" + d.getSeconds()).slice(-2);
return nameStr.replace("${date}", fmt);
}
/**
* @param {MediaRecorder} mediaRec
* @param {boolean} isWebM
* @param {string} nameStr
*/
eagruntimeImpl.platformScreenRecord["setDataAvailableHandler"] = function(mediaRec, isWebM, nameStr) {
const startTime = performance.now();
mediaRec.addEventListener("dataavailable", function(evt) {
if(isWebM) {
fixWebMDuration(/** @type {!Blob} */ (evt.data), (performance.now() - startTime) | 0, function(/** !Blob */ b) {
const blobUrl = URL.createObjectURL(b);
downloadFileImpl(formatScreenRecDate(nameStr), blobUrl, function() {
URL.revokeObjectURL(blobUrl);
});
}, {
/**
* @param {string} str
*/
logger: function(str) {
eagInfo(str);
}
});
}else {
const blobUrl = URL.createObjectURL(/** @type {!Blob} */ (evt.data));
downloadFileImpl(formatScreenRecDate(nameStr), blobUrl, function() {
URL.revokeObjectURL(blobUrl);
});
}
});
};
}
function initializeNoPlatfScreenRecord(screenRecImports) {
setUnsupportedFunc(screenRecImports, platfScreenRecordName, "getMic");
setUnsupportedFunc(screenRecImports, platfScreenRecordName, "setDataAvailableHandler");
}

View File

@ -0,0 +1,197 @@
/*
* Copyright (c) 2024 lax1dude, ayunami2000. 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 EVENT_VOICE_ICE = 0;
const EVENT_VOICE_DESC = 1;
const EVENT_VOICE_OPEN = 2;
const EVENT_VOICE_CLOSE = 3;
const platfVoiceClientName = "platformVoiceClient";
function initializePlatfVoiceClient(voiceClientImports) {
/**
* @return {boolean}
*/
voiceClientImports["isSupported"] = function() {
return typeof navigator.mediaDevices !== "undefined" && typeof navigator.mediaDevices.getUserMedia !== "undefined" && "srcObject" in HTMLAudioElement.prototype;
};
/**
* @param {string} desc
* @suppress {globalThis}
*/
function setRemoteDescriptionImpl(desc) {
try {
const remoteDesc = JSON.parse(desc);
this["_peerConnection"].setRemoteDescription(remoteDesc).then(() => {
if (remoteDesc.hasOwnProperty("type") && "offer" === remoteDesc["type"]) {
this["_peerConnection"].createAnswer().then((desc) => {
this["_peerConnection"].setLocalDescription(desc).then(() => {
pushEvent(EVENT_TYPE_VOICE, EVENT_VOICE_DESC, {
"objId": this["objId"],
"data": JSON.stringify(desc)
});
}).catch((err) => {
eagError("Failed to set local description for \"{}\"! {}", this["objId"], err.message);
pushEvent(EVENT_TYPE_VOICE, EVENT_VOICE_CLOSE, {
"objId": this["objId"]
});
});
}).catch((err) => {
eagError("Failed to create answer for \"{}\"! {}", this["objId"], err.message);
pushEvent(EVENT_TYPE_VOICE, EVENT_VOICE_CLOSE, {
"objId": this["objId"]
});
});
}
}).catch((err) => {
eagError("Failed to set remote description for \"{}\"! {}", this["objId"], err.message);
pushEvent(EVENT_TYPE_VOICE, EVENT_VOICE_CLOSE, {
"objId": this["objId"]
});
});
} catch (e) {
eagError(e.message);
pushEvent(EVENT_TYPE_VOICE, EVENT_VOICE_CLOSE, {
"objId": this["objId"]
});
}
}
/**
* @param {string} ice
* @suppress {globalThis}
*/
function addRemoteICECandidateImpl(ice) {
try {
this["_peerConnection"].addIceCandidate(new RTCIceCandidate(/** @type {!RTCIceCandidateInit} */ (JSON.parse(ice)))).catch((err) => {
eagError("Failed to parse ice candidate for \"{}\"! {}", this["objId"], err.message);
pushEvent(EVENT_TYPE_VOICE, EVENT_VOICE_CLOSE, {
"objId": this["objId"]
});
});
} catch (e) {
eagError(e.message);
pushEvent(EVENT_TYPE_VOICE, EVENT_VOICE_CLOSE, {
"objId": this["objId"]
});
}
}
/**
* @suppress {globalThis}
*/
function closeImpl() {
this["_peerConnection"].close();
}
let idCounter = 0;
/**
* @param {string} iceServers
* @param {number} offer
* @param {!MediaStream} localStream
* @return {Object}
*/
voiceClientImports["createRTCPeerConnection"] = function(iceServers, offer, localStream) {
try {
const peerId = idCounter++;
var ret;
const peerConnection = new RTCPeerConnection(/** @type {!RTCConfiguration} */ ({
"iceServers": JSON.parse(iceServers),
"optional": [
{
"DtlsSrtpKeyAgreement": true
}
]
}));
peerConnection.addEventListener("icecandidate", /** @type {function(Event)} */ ((/** RTCPeerConnectionIceEvent */ evt) => {
if (evt.candidate) {
pushEvent(EVENT_TYPE_VOICE, EVENT_VOICE_ICE, {
"objId": peerId,
"data": JSON.stringify({
"sdpMLineIndex": "" + evt.candidate.sdpMLineIndex,
"candidate": evt.candidate.candidate
})
});
}
}));
peerConnection.addEventListener("track", /** @type {function(Event)} */ ((/** RTCTrackEvent */ evt) => {
const rawStream = evt.streams[0];
ret["_aud"] = document.createElement("audio");
ret["_aud"]["autoplay"] = true;
ret["_aud"]["muted"] = true;
ret["_aud"]["srcObject"] = rawStream;
pushEvent(EVENT_TYPE_VOICE, EVENT_VOICE_OPEN, {
"objId": peerId,
"stream": rawStream
});
}));
localStream.getTracks().forEach(function(track) {
peerConnection.addTrack(track, localStream);
});
if (offer) {
peerConnection.createOffer().then((desc) => {
peerConnection.setLocalDescription(desc).then(() => {
pushEvent(EVENT_TYPE_VOICE, EVENT_VOICE_DESC, {
"objId": peerId,
"data": JSON.stringify(desc)
});
}).catch((err) => {
eagError("Failed to set local description for \"{}\"! {}", peerId, err.message);
pushEvent(EVENT_TYPE_VOICE, EVENT_VOICE_CLOSE, {
"objId": peerId
});
});
}).catch((err) => {
eagError("Failed to set create offer for \"{}\"! {}", peerId, err.message);
pushEvent(EVENT_TYPE_VOICE, EVENT_VOICE_CLOSE, {
"objId": peerId
});
});
}
peerConnection.addEventListener("connectionstatechange", () => {
const cs = peerConnection.connectionState;
if ("disconnected" === cs || "failed" === cs) {
pushEvent(EVENT_TYPE_VOICE, EVENT_VOICE_CLOSE, {
"objId": peerId
});
}
});
return ret = {
"objId": peerId,
"_peerConnection": peerConnection,
"setRemoteDescription": setRemoteDescriptionImpl,
"addRemoteICECandidate": addRemoteICECandidateImpl,
"closeHandle": closeImpl
};
} catch (e) {
eagError(e.message);
return null;
}
};
}
function initializeNoPlatfVoiceClient(voiceClientImports) {
setUnsupportedFunc(voiceClientImports, platfVoiceClientName, "isSupported");
setUnsupportedFunc(voiceClientImports, platfVoiceClientName, "createRTCPeerConnection");
}

View File

@ -0,0 +1,651 @@
/*
* Copyright (c) 2024 lax1dude, ayunami2000. 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 READYSTATE_INIT_FAILED = -2;
const READYSTATE_FAILED = -1;
const READYSTATE_DISCONNECTED = 0;
const READYSTATE_CONNECTING = 1;
const READYSTATE_CONNECTED = 2;
const EVENT_WEBRTC_ICE = 0;
const EVENT_WEBRTC_DESC = 1;
const EVENT_WEBRTC_OPEN = 2;
const EVENT_WEBRTC_PACKET = 3;
const EVENT_WEBRTC_CLOSE = 4;
const platfWebRTCName = "platformWebRTC";
/**
* @typedef {{
* peerId:string,
* peerConnection:!RTCPeerConnection,
* dataChannel:RTCDataChannel,
* ipcChannel:(string|null),
* pushEvent:function(number,*),
* disconnect:function()
* }}
*/
let LANPeerInternal;
function initializePlatfWebRTC(webrtcImports) {
const clientLANPacketBuffer = new EaglerLinkedQueue();
let lanClient;
lanClient = {
iceServers: [],
/** @type {RTCPeerConnection|null} */
peerConnection: null,
/** @type {RTCDataChannel|null} */
dataChannel: null,
readyState: READYSTATE_CONNECTING,
/** @type {string|null} */
iceCandidate: null,
/** @type {string|null} */
description: null,
dataChannelOpen: false,
dataChannelClosed: true,
disconnect: function(quiet) {
if (lanClient.dataChannel) {
try {
lanClient.dataChannel.close();
} catch (t) {
}
lanClient.dataChannel = null;
}
if (lanClient.peerConnection) {
try {
lanClient.peerConnection.close();
} catch (t) {
}
lanClient.peerConnection = null;
}
if (!quiet) lanClient.dataChannelClosed = true;
lanClient.readyState = READYSTATE_DISCONNECTED;
}
};
/**
* @return {boolean}
*/
webrtcImports["supported"] = function() {
return typeof RTCPeerConnection !== "undefined";
};
/**
* @return {number}
*/
webrtcImports["clientLANReadyState"] = function() {
return lanClient.readyState;
};
webrtcImports["clientLANCloseConnection"] = function() {
lanClient.disconnect(false);
};
/**
* @param {!Uint8Array} pkt
*/
webrtcImports["clientLANSendPacket"] = function(pkt) {
if (lanClient.dataChannel !== null && "open" === lanClient.dataChannel.readyState) {
try {
lanClient.dataChannel.send(pkt);
} catch (e) {
lanClient.disconnect(false);
}
}else {
lanClient.disconnect(false);
}
};
/**
* @return {Uint8Array}
*/
webrtcImports["clientLANReadPacket"] = function() {
const ret = clientLANPacketBuffer.shift();
return ret ? new Uint8Array(ret["data"]) : null;
};
/**
* @return {number}
*/
webrtcImports["clientLANAvailable"] = function() {
return clientLANPacketBuffer.getLength();
};
/**
* @param {!Array<string>} servers
*/
webrtcImports["clientLANSetICEServersAndConnect"] = function(servers) {
lanClient.iceServers.length = 0;
for (let url of servers) {
let etr = url.split(";");
if(etr.length === 1) {
lanClient.iceServers.push({
urls: etr[0]
});
}else if(etr.length === 3) {
lanClient.iceServers.push({
urls: etr[0],
username: etr[1],
credential: etr[2]
});
}
}
if(lanClient.readyState === READYSTATE_CONNECTED || lanClient.readyState === READYSTATE_CONNECTING) {
lanClient.disconnect(true);
}
try {
if (lanClient.dataChannel) {
try {
lanClient.dataChannel.close();
} catch (t) {
}
lanClient.dataChannel = null;
}
if (lanClient.peerConnection) {
try {
lanClient.peerConnection.close();
} catch (t) {
}
}
lanClient.peerConnection = new RTCPeerConnection({
iceServers: lanClient.iceServers,
optional: [
{
DtlsSrtpKeyAgreement: true
}
]
});
lanClient.readyState = READYSTATE_CONNECTING;
} catch (/** Error */ t) {
eagStackTrace(ERROR, "Could not create LAN client RTCPeerConnection!", t);
lanClient.readyState = READYSTATE_INIT_FAILED;
return;
}
try {
const iceCandidates = [];
lanClient.peerConnection.addEventListener("icecandidate", /** @type {function(Event)} */ ((/** !RTCPeerConnectionIceEvent */ evt) => {
if(evt.candidate) {
if(iceCandidates.length === 0) {
const candidateState = [0, 0];
const runnable = () => {
if(lanClient.peerConnection !== null && lanClient.peerConnection.connectionState !== "disconnected") {
const trial = ++candidateState[1];
if(candidateState[0] !== iceCandidates.length && trial < 3) {
candidateState[0] = iceCandidates.length;
setTimeout(runnable, 2000);
return;
}
lanClient.iceCandidate = JSON.stringify(iceCandidates);
iceCandidates.length = 0;
}
};
setTimeout(runnable, 2000);
}
iceCandidates.push({
"sdpMLineIndex": evt.candidate.sdpMLineIndex,
"candidate": evt.candidate.candidate
});
}
}));
lanClient.dataChannel = lanClient.peerConnection.createDataChannel("lan");
lanClient.dataChannel.binaryType = "arraybuffer";
let evtHandler;
evtHandler = () => {
if (iceCandidates.length > 0) {
setTimeout(evtHandler, 10);
return;
}
lanClient.dataChannelClosed = false;
lanClient.dataChannelOpen = true;
};
lanClient.dataChannel.addEventListener("open", evtHandler);
lanClient.dataChannel.addEventListener("message", /** @type {function(Event)} */ ((/** MessageEvent */ evt) => {
clientLANPacketBuffer.push({ "data": evt.data, "_next": null });
}));
lanClient.peerConnection.createOffer().then((/** !RTCSessionDescription */ desc) => {
lanClient.peerConnection.setLocalDescription(desc).then(() => {
lanClient.description = JSON.stringify(desc);
}).catch((err) => {
eagError("Failed to set local description! {}", /** @type {string} */ (err.message));
lanClient.readyState = READYSTATE_FAILED;
lanClient.disconnect(false);
});
}).catch((err) => {
eagError("Failed to set create offer! {}", /** @type {string} */ (err.message));
lanClient.readyState = READYSTATE_FAILED;
lanClient.disconnect(false);
});
lanClient.peerConnection.addEventListener("connectionstatechange", /** @type {function(Event)} */ ((evt) => {
var connectionState = lanClient.peerConnection.connectionState;
if ("disconnected" === connectionState) {
lanClient.disconnect(false);
} else if ("connected" === connectionState) {
lanClient.readyState = READYSTATE_CONNECTED;
} else if ("failed" === connectionState) {
lanClient.readyState = READYSTATE_FAILED;
lanClient.disconnect(false);
}
}));
} catch (t) {
if (lanClient.dataChannel) {
try {
lanClient.dataChannel.close();
} catch (tt) {
}
lanClient.dataChannel = null;
}
if (lanClient.peerConnection) {
try {
lanClient.peerConnection.close();
} catch (tt) {
}
lanClient.peerConnection = null;
}
eagStackTrace(ERROR, "Could not create LAN client RTCDataChannel!", t);
lanClient.readyState = READYSTATE_INIT_FAILED;
}
};
webrtcImports["clearLANClientState"] = function() {
lanClient.iceCandidate = lanClient.description = null;
lanClient.dataChannelOpen = false;
lanClient.dataChannelClosed = true;
};
/**
* @return {string|null}
*/
webrtcImports["clientLANAwaitICECandidate"] = function() {
if (lanClient.iceCandidate === null) {
return null;
}
const ret = lanClient.iceCandidate;
lanClient.iceCandidate = null;
return ret;
};
/**
* @return {string|null}
*/
webrtcImports["clientLANAwaitDescription"] = function() {
if (lanClient.description === null) {
return null;
}
const ret = lanClient.description;
lanClient.description = null;
return ret;
};
/**
* @return {boolean}
*/
webrtcImports["clientLANAwaitChannel"] = function() {
if (lanClient.dataChannelOpen) {
lanClient.dataChannelOpen = false;
return true;
}
return false;
};
/**
* @return {boolean}
*/
webrtcImports["clientLANClosed"] = function() {
return lanClient.dataChannelClosed;
};
/**
* @param {string} candidate
*/
webrtcImports["clientLANSetICECandidate"] = function(candidate) {
try {
const lst = /** @type {Array<!Object>} */ (JSON.parse(candidate));
for (var i = 0; i < lst.length; ++i) {
lanClient.peerConnection.addIceCandidate(new RTCIceCandidate(lst[i]));
}
}catch(/** Error */ ex) {
eagStackTrace(ERROR, "Uncaught exception setting remote ICE candidates", ex);
lanClient.readyState = READYSTATE_FAILED;
lanClient.disconnect(false);
}
};
/**
* @param {string} description
*/
webrtcImports["clientLANSetDescription"] = function(description) {
try {
lanClient.peerConnection.setRemoteDescription(/** @type {!RTCSessionDescription} */ (JSON.parse(description)));
}catch(/** Error */ ex) {
eagStackTrace(ERROR, "Uncaught exception setting remote description", ex);
lanClient.readyState = READYSTATE_FAILED;
lanClient.disconnect(false);
}
};
let lanServer;
lanServer = {
/** @type {!Array<Object>} */
iceServers: [],
/** @type {!Map<string, !LANPeerInternal>} */
peerList: new Map(),
/** @type {!Map<string, !LANPeerInternal>} */
ipcMapList: new Map(),
disconnect: function(/** string */ peerId) {
const thePeer = lanServer.peerList.get(peerId);
if(thePeer) {
lanServer.peerList.delete(peerId);
if(thePeer.ipcChannel) {
lanServer.ipcMapList.delete(thePeer.ipcChannel);
}
try {
thePeer.disconnect();
} catch (ignored) {}
thePeer.pushEvent(EVENT_WEBRTC_CLOSE, null);
}
}
};
/**
* @param {!Array<string>} servers
*/
webrtcImports["serverLANInitializeServer"] = function(servers) {
lanServer.iceServers.length = 0;
for (let url of servers) {
let etr = url.split(";");
if(etr.length === 1) {
lanServer.iceServers.push({
"urls": etr[0]
});
}else if(etr.length === 3) {
lanServer.iceServers.push({
"urls": etr[0],
"username": etr[1],
"credential": etr[2]
});
}
}
};
webrtcImports["serverLANCloseServer"] = function() {
for (let thePeer of Object.values(lanServer.peerList)) {
if (thePeer) {
try {
thePeer.disconnect();
} catch (e) {}
thePeer.pushEvent(EVENT_WEBRTC_CLOSE, null);
}
}
lanServer.peerList.clear();
};
/**
* @param {string} peer
*/
webrtcImports["serverLANCreatePeer"] = function(peer) {
try {
const events = new EaglerLinkedQueue();
/** @type {!LANPeerInternal} */
let peerInstance;
peerInstance = {
peerId: peer,
peerConnection: new RTCPeerConnection(/** @type {RTCConfiguration} */ ({
"iceServers": lanServer.iceServers,
"optional": [
{
"DtlsSrtpKeyAgreement": true
}
]
})),
/** @type {RTCDataChannel} */
dataChannel: null,
/** @type {string|null} */
ipcChannel: null,
pushEvent: function(type, data) {
events.push({
"type": type,
"data": data,
"_next": null
});
},
disconnect: function() {
if (peerInstance.dataChannel) peerInstance.dataChannel.close();
peerInstance.peerConnection.close();
}
};
lanServer.peerList.set(peerInstance.peerId, peerInstance);
const iceCandidates = [];
peerInstance.peerConnection.addEventListener("icecandidate", /** @type {function(Event)} */ ((/** RTCPeerConnectionIceEvent */ evt) => {
if(evt.candidate) {
if(iceCandidates.length === 0) {
const candidateState = [0, 0];
const runnable = () => {
if(peerInstance.peerConnection !== null && peerInstance.peerConnection.connectionState !== "disconnected") {
const trial = ++candidateState[1];
if(candidateState[0] !== iceCandidates.length && trial < 3) {
candidateState[0] = iceCandidates.length;
setTimeout(runnable, 2000);
return;
}
peerInstance.pushEvent(EVENT_WEBRTC_ICE, JSON.stringify(iceCandidates));
iceCandidates.length = 0;
}
};
setTimeout(runnable, 2000);
}
iceCandidates.push({
"sdpMLineIndex": evt.candidate.sdpMLineIndex,
"candidate": evt.candidate.candidate
});
}
}));
let evtHandler;
evtHandler = (/** RTCDataChannelEvent */ evt) => {
if (iceCandidates.length > 0) {
setTimeout(evtHandler, 10, evt);
return;
}
if (!evt.channel) return;
const newDataChannel = evt.channel;
if(peerInstance.dataChannel !== null) {
newDataChannel.close();
return;
}
peerInstance.dataChannel = newDataChannel;
peerInstance.pushEvent(EVENT_WEBRTC_OPEN, null);
peerInstance.dataChannel.addEventListener("message", (evt2) => {
const data = evt2.data;
if(peerInstance.ipcChannel) {
sendIPCPacketFunc(peerInstance.ipcChannel, data);
}else {
peerInstance.pushEvent(EVENT_WEBRTC_PACKET, new Uint8Array(data));
}
});
};
peerInstance.peerConnection.addEventListener("datachannel", /** @type {function(Event)} */ (evtHandler));
peerInstance.peerConnection.addEventListener("connectionstatechange", (evt) => {
const connectionState = peerInstance.peerConnection.connectionState;
if ("disconnected" === connectionState || "failed" === connectionState) {
lanServer.disconnect(peerInstance.peerId);
}
});
return {
"peerId": peerInstance.peerId,
/**
* @return {number}
*/
"countAvailableEvents": function() {
return events.getLength();
},
/**
* @return {Object}
*/
"nextEvent": function() {
return events.shift();
},
/**
* @param {!Uint8Array} dat
*/
"writePacket": function(dat) {
let b = false;
if (peerInstance.dataChannel !== null && "open" === peerInstance.dataChannel.readyState) {
try {
peerInstance.dataChannel.send(dat);
} catch (e) {
b = true;
}
} else {
b = true;
}
if(b) {
lanServer.disconnect(peerInstance.peerId);
}
},
/**
* @param {string} iceCandidates
*/
"handleRemoteICECandidates": function(iceCandidates) {
try {
const candidateList = /** @type {!Array<!RTCIceCandidateInit>} */ (JSON.parse(iceCandidates));
for (let candidate of candidateList) {
peerInstance.peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
}
} catch (err) {
eagError("Failed to parse ice candidate for \"{}\"! {}", peerInstance.peerId, err.message);
lanServer.disconnect(peerInstance.peerId);
}
},
/**
* @param {string} desc
*/
"handleRemoteDescription": function(desc) {
try {
const remoteDesc = /** @type {!RTCSessionDescription} */ (JSON.parse(desc));
peerInstance.peerConnection.setRemoteDescription(remoteDesc).then(() => {
if (remoteDesc.hasOwnProperty("type") && "offer" === remoteDesc["type"]) {
peerInstance.peerConnection.createAnswer().then((desc) => {
peerInstance.peerConnection.setLocalDescription(desc).then(() => {
peerInstance.pushEvent(EVENT_WEBRTC_DESC, JSON.stringify(desc));
}).catch((err) => {
eagError("Failed to set local description for \"{}\"! {}", peerInstance.peerId, err.message);
lanServer.disconnect(peerInstance.peerId);
});
}).catch((err) => {
eagError("Failed to create answer for \"{}\"! {}", peerInstance.peerId, err.message);
lanServer.disconnect(peerInstance.peerId);
});
}
}).catch((err) => {
eagError("Failed to set remote description for \"{}\"! {}", peerInstance.peerId, err.message);
lanServer.disconnect(peerInstance.peerId);
});
} catch (err) {
eagError("Failed to parse remote description for \"{}\"! {}", peerInstance.peerId, err.message);
lanServer.disconnect(peerInstance.peerId);
}
},
/**
* @param {string|null} ipcChannel
*/
"mapIPC": function(ipcChannel) {
if(!peerInstance.ipcChannel) {
if(ipcChannel) {
peerInstance.ipcChannel = ipcChannel;
lanServer.ipcMapList.set(ipcChannel, peerInstance);
}
}else {
if(!ipcChannel) {
lanServer.ipcMapList.delete(peerInstance.ipcChannel);
peerInstance.ipcChannel = null;
}
}
},
"disconnect": function() {
lanServer.disconnect(peerInstance.peerId);
}
};
}catch(/** Error */ tt) {
eagStackTrace(ERROR, "Failed to create WebRTC LAN peer!", tt);
return null;
}
};
/**
* @param {string} channel
* @param {!ArrayBuffer} arr
*/
serverLANPeerPassIPCFunc = function(channel, arr) {
const peer = lanServer.ipcMapList.get(channel);
if(peer) {
let b = false;
if (peer.dataChannel && "open" === peer.dataChannel.readyState) {
try {
peer.dataChannel.send(arr);
} catch (e) {
b = true;
}
} else {
b = true;
}
if(b) {
lanServer.disconnect(peer.peerId);
}
return true;
}else {
return false;
}
};
}
function initializeNoPlatfWebRTC(webrtcImports) {
setUnsupportedFunc(webrtcImports, platfWebRTCName, "supported");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANReadyState");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANCloseConnection");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANSendPacket");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANReadPacket");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANAvailable");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANSetICEServersAndConnect");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clearLANClientState");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANAwaitICECandidate");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANAwaitDescription");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANAwaitChannel");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANClosed");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANSetICECandidate");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANSetDescription");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "clientLANClosed");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "serverLANInitializeServer");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "serverLANCloseServer");
setUnsupportedFunc(webrtcImports, platfWebRTCName, "serverLANCreatePeer");
}

View File

@ -0,0 +1,523 @@
/*
* 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 platfWebViewName = "platformWebView";
function initializePlatfWebView(webViewImports) {
const BEGIN_SHOWING_DIRECT = 0;
const BEGIN_SHOWING_ENABLE_JAVASCRIPT = 1;
const BEGIN_SHOWING_CONTENT_BLOCKED = 2;
const EVENT_WEBVIEW_CHANNEL = 0;
const EVENT_WEBVIEW_MESSAGE = 1;
const EVENT_WEBVIEW_PERMISSION_ALLOW = 2;
const EVENT_WEBVIEW_PERMISSION_BLOCK = 3;
const EVENT_WEBVIEW_PERMISSION_CLEAR = 4;
const EVENT_CHANNEL_OPEN = 0;
const EVENT_CHANNEL_CLOSE = 1;
const EVENT_MESSAGE_STRING = 0;
const EVENT_MESSAGE_BINARY = 1;
const utf8Decoder = new TextDecoder("utf-8");
var supported = false;
var cspSupport = false;
var supportForce = runtimeOpts.forceWebViewSupport;
var enableCSP = runtimeOpts.enableWebViewCSP;
if(supportForce) {
supported = true;
cspSupport = true;
}else {
supported = false;
cspSupport = false;
try {
var tmp = /** @type {HTMLIFrameElement} */ (document.createElement("iframe"));
supported = tmp != null && (typeof tmp.allow === "string") && (typeof tmp.sandbox === "object");
cspSupport = enableCSP && supported && (typeof tmp.csp === "string");
}catch(ex) {
eagError("Error checking iframe support");
eagError(ex);
}
}
if(!supported) {
eagError("This browser does not meet the safety requirements for webview support, this feature will be disabled");
}else if(!cspSupport && enableCSP) {
eagWarn("This browser does not support CSP attribute on iframes! (try Chrome)");
}
const requireSSL = location.protocol && "https:" === location.protocol.toLowerCase();
var webviewResetSerial = 0;
/** @type {HTMLElement} */
var currentIFrameContainer = null;
/** @type {HTMLElement} */
var currentAllowJavaScript = null;
/** @type {HTMLIFrameElement} */
var currentIFrame = null;
/** @type {function(MessageEvent)|null} */
var currentMessageHandler = null;
window.addEventListener("message", /** @type {function(Event)} */ (function(/** MessageEvent */ evt) {
if(currentMessageHandler && evt.source !== window) {
currentMessageHandler(evt);
}
}));
/**
* @return {boolean}
*/
webViewImports["checkSupported"] = function() {
return supported;
};
/**
* @return {boolean}
*/
webViewImports["checkCSPSupported"] = function() {
return cspSupport;
};
/**
* @param {string} ch
* @param {string} str
*/
webViewImports["sendStringMessage"] = function(ch, str) {
try {
var w;
if(currentIFrame != null && (w = currentIFrame.contentWindow) != null) {
w.postMessage({
"ver": 1,
"channel": ch,
"type": "string",
"data": str
}, "*");
}else {
eagError("Server tried to send the WebView a message, but the message channel is not open!");
}
}catch(/** Error */ ex) {
eagStackTrace(ERROR, "Failed to send string message to WebView!", ex);
}
};
/**
* @param {string} ch
* @param {Uint8Array} bin
*/
webViewImports["sendBinaryMessage"] = function(ch, bin) {
try {
var w;
if(currentIFrame != null && (w = currentIFrame.contentWindow) != null) {
const copiedArray = new Uint8Array(bin.length);
copiedArray.set(bin, 0);
w.postMessage({
"ver": 1,
"channel": ch,
"type": "binary",
"data": copiedArray.buffer
}, "*");
}else {
eagError("Server tried to send the WebView a message, but the message channel is not open!");
}
}catch(/** Error */ ex) {
eagStackTrace(ERROR, "Failed to send string message to WebView!", ex);
}
};
/**
* @param {number} state
* @param {Object} options
* @param {number} x
* @param {number} y
* @param {number} w
* @param {number} h
*/
webViewImports["beginShowing"] = function(state, options, x, y, w, h) {
if(!supported) {
return;
}
try {
setupShowing(x, y, w, h);
switch(state) {
case BEGIN_SHOWING_DIRECT:
beginShowingDirect(options);
break;
case BEGIN_SHOWING_ENABLE_JAVASCRIPT:
if(options["contentMode"] === 1) {
const copiedBlob = new Uint8Array(options["blob"].length);
copiedBlob.set(options["blob"], 0);
options["blob"] = copiedBlob;
}
beginShowingEnableJavaScript(options);
break;
case BEGIN_SHOWING_CONTENT_BLOCKED:
if(options["contentMode"] === 1) {
const copiedBlob = new Uint8Array(options["blob"].length);
copiedBlob.set(options["blob"], 0);
options["blob"] = copiedBlob;
}
beginShowingContentBlocked(options);
break;
default:
break;
}
}catch(/** Error */ ex) {
eagStackTrace(ERROR, "Failed to begin showing WebView!", ex);
}
};
/**
* @param {number} x
* @param {number} y
* @param {number} w
* @param {number} h
*/
function setupShowing(x, y, w, h) {
if(currentIFrameContainer !== null) {
endShowingImpl();
}
currentIFrameContainer = /** @type {HTMLElement} */ (document.createElement("div"));
currentIFrameContainer.classList.add("_eaglercraftX_webview_container_element");
currentIFrameContainer.style.border = "5px solid #333333";
currentIFrameContainer.style.zIndex = "11";
currentIFrameContainer.style.position = "absolute";
currentIFrameContainer.style.backgroundColor = "#DDDDDD";
currentIFrameContainer.style.fontFamily = "sans-serif";
resizeImpl(x, y, w, h);
parentElement.appendChild(currentIFrameContainer);
}
/**
* @param {HTMLIFrameElement} iframeElement
* @param {string} str
*/
function setAllowSafe(iframeElement, str) {
iframeElement.allow = str;
return iframeElement.allow === str;
}
/**
* @param {HTMLIFrameElement} iframeElement
* @param {Array<string>} args
*/
function setSandboxSafe(iframeElement, args) {
const theSandbox = iframeElement.sandbox;
for(var i = 0; i < args.length; ++i) {
theSandbox.add(args[i]);
}
for(var i = 0; i < args.length; ++i) {
if(!theSandbox.contains(args[i])) {
return false;
}
}
for(var i = 0; i < theSandbox.length; ++i) {
if(!args.find(itm => itm === theSandbox[i])) {
return false;
}
}
return true;
}
function beginShowingDirect(options) {
if(!supportForce) {
currentIFrame = /** @type {HTMLIFrameElement} */ (document.createElement("iframe"));
currentIFrame.referrerPolicy = "strict-origin";
const sandboxArgs = [ "allow-downloads" ];
if(options["scriptEnabled"]) {
sandboxArgs.push("allow-scripts");
sandboxArgs.push("allow-pointer-lock");
}
if(!setAllowSafe(currentIFrame, "") || !setSandboxSafe(currentIFrame, sandboxArgs)) {
eagError("Caught safety exception while opening webview!");
if(currentIFrame !== null) {
currentIFrame.remove();
currentIFrame = null;
}
eagError("Things you can try:");
eagError("1. Set window.eaglercraftXOpts.forceWebViewSupport to true");
eagError("2. Set window.eaglercraftXOpts.enableWebViewCSP to false");
eagError("(these settings may compromise security)");
beginShowingSafetyError();
return;
}
}else {
currentIFrame = /** @type {HTMLIFrameElement} */ (document.createElement("iframe"));
currentIFrame.allow = "";
currentIFrame.referrerPolicy = "strict-origin";
currentIFrame.sandbox.add("allow-downloads");
if(options["scriptEnabled"]) {
currentIFrame.sandbox.add("allow-scripts");
currentIFrame.sandbox.add("allow-pointer-lock");
}
}
currentIFrame.credentialless = true;
currentIFrame.loading = "lazy";
var cspWarn = false;
if(options["contentMode"] === 1) {
if(enableCSP && cspSupport) {
if(typeof currentIFrame.csp === "string") {
var csp = "default-src 'none';";
var protos = options["strictCSPEnable"] ? "" : (requireSSL ? " https:" : " http: https:");
if(options["scriptEnabled"]) {
csp += (" script-src 'unsafe-eval' 'unsafe-inline' data: blob:" + protos + ";");
csp += (" style-src 'unsafe-eval' 'unsafe-inline' data: blob:" + protos + ";");
csp += (" img-src data: blob:" + protos + ";");
csp += (" font-src data: blob:" + protos + ";");
csp += (" child-src data: blob:" + protos + ";");
csp += (" frame-src data: blob:;");
csp += (" media-src data: mediastream: blob:" + protos + ";");
csp += (" connect-src data: blob:" + protos + ";");
csp += (" worker-src data: blob:" + protos + ";");
}else {
csp += (" style-src data: 'unsafe-inline'" + protos + ";");
csp += (" img-src data:" + protos + ";");
csp += (" font-src data:" + protos + ";");
csp += (" media-src data:" + protos + ";");
}
currentIFrame.csp = csp;
}else {
eagWarn("This browser does not support CSP attribute on iframes! (try Chrome)");
cspWarn = true;
}
}else {
cspWarn = true;
}
if(cspWarn && options["strictCSPEnable"]) {
eagWarn("Strict CSP was requested for this webview, but that feature is not available!");
}
}else {
cspWarn = true;
}
currentIFrame.style.border = "none";
currentIFrame.style.backgroundColor = "white";
currentIFrame.style.width = "100%";
currentIFrame.style.height = "100%";
currentIFrame.classList.add("_eaglercraftX_webview_iframe_element");
currentIFrameContainer.appendChild(currentIFrame);
if(options["contentMode"] === 1) {
const decodedText = utf8Decoder.decode(options["blob"]);
options["blob"] = null;
currentIFrame.srcdoc = decodedText;
}else {
currentIFrame.src = options["uri"];
}
const resetSer = webviewResetSerial;
const curIFrame = currentIFrame;
let focusTracker = false;
currentIFrame.addEventListener("mouseover", function(/** Event */ evt) {
if(resetSer === webviewResetSerial && curIFrame === currentIFrame) {
if(!focusTracker) {
focusTracker = true;
currentIFrame.contentWindow.focus();
}
}
});
currentIFrame.addEventListener("mouseout", function(/** Event */ evt) {
if(resetSer === webviewResetSerial && curIFrame === currentIFrame) {
if(focusTracker) {
focusTracker = false;
window.focus();
}
}
});
if(options["scriptEnabled"] && options["serverMessageAPIEnabled"]) {
currentMessageHandler = function(/** MessageEvent */ evt) {
if(resetSer === webviewResetSerial && curIFrame === currentIFrame && evt.source === curIFrame.contentWindow) {
/** @type {Object} */
const obj = evt.data;
if((typeof obj === "object") && (obj["ver"] === 1) && ((typeof obj["channel"] === "string") && obj["channel"]["length"] > 0)) {
if(typeof obj["open"] === "boolean") {
pushEvent(EVENT_TYPE_WEBVIEW, EVENT_WEBVIEW_CHANNEL, {
"eventType": (obj["open"] ? EVENT_CHANNEL_OPEN : EVENT_CHANNEL_CLOSE),
"channelName": obj["channel"]
});
return;
}else if(typeof obj["data"] === "string") {
pushEvent(EVENT_TYPE_WEBVIEW, EVENT_WEBVIEW_MESSAGE, {
"eventType": EVENT_MESSAGE_STRING,
"channelName": obj["channel"],
"eventData": obj["data"]
});
return;
}else if(obj["data"] instanceof ArrayBuffer) {
pushEvent(EVENT_TYPE_WEBVIEW, EVENT_WEBVIEW_MESSAGE, {
"eventType": EVENT_MESSAGE_BINARY,
"channelName": obj["channel"],
"eventData": obj["data"]
});
return;
}
}
eagWarn("WebView sent an invalid message!");
}else {
eagWarn("Recieved message from on dead IFrame handler: (#{}) {}", resetSer, curIFrame.src);
}
};
}
eagInfo("WebView is loading: \"{}\"", options["contentMode"] === 1 ? "about:srcdoc" : currentIFrame.src);
eagInfo("JavaScript: {}, Strict CSP: {}, Message API: {}", options["scriptEnabled"],
options["strictCSPEnable"] && !cspWarn, options["serverMessageAPIEnabled"]);
}
function beginShowingEnableJSSetup() {
if(currentAllowJavaScript !== null) {
++webviewResetSerial;
currentAllowJavaScript.remove();
currentAllowJavaScript = null;
}
currentAllowJavaScript = /** @type {HTMLElement} */ (document.createElement("div"));
currentAllowJavaScript.style.backgroundColor = "white";
currentAllowJavaScript.style.width = "100%";
currentAllowJavaScript.style.height = "100%";
currentAllowJavaScript.classList.add("_eaglercraftX_webview_permission_screen");
currentIFrameContainer.appendChild(currentAllowJavaScript);
}
function beginShowingEnableJavaScript(options) {
beginShowingEnableJSSetup();
var strictCSPMarkup;
if(options["contentMode"] !== 1) {
strictCSPMarkup = "<span style=\"color:red;\">Impossible</span>";
}else if(!cspSupport || !enableCSP) {
strictCSPMarkup = "<span style=\"color:red;\">Unsupported</span>";
}else if(options["strictCSPEnable"]) {
strictCSPMarkup = "<span style=\"color:green;\">Enabled</span>";
}else {
strictCSPMarkup = "<span style=\"color:red;\">Disabled</span>";
}
var messageAPIMarkup;
if(options["serverMessageAPIEnabled"]) {
messageAPIMarkup = "<span style=\"color:red;\">Enabled</span>";
}else {
messageAPIMarkup = "<span style=\"color:green;\">Disabled</span>";
}
currentAllowJavaScript.innerHTML =
"<div style=\"padding-top:13vh;\"><div style=\"margin:auto;max-width:450px;border:6px double black;text-align:center;padding:20px;\">"
+ "<h2><img width=\"32\" height=\"32\" style=\"vertical-align:middle;\" src=\"" + faviconURL + "\">&emsp;Allow JavaScript</h2>"
+ "<p style=\"font-family:monospace;text-decoration:underline;word-wrap:break-word;\" class=\"_eaglercraftX_permission_target_url\"></p>"
+ "<h4 style=\"line-height:1.4em;\">Strict CSP: " + strictCSPMarkup + "&ensp;|&ensp;"
+ "Message API: " + messageAPIMarkup + "</h4>"
+ "<p><input class=\"_eaglercraftX_remember_javascript\" type=\"checkbox\" checked> Remember my choice</p>"
+ "<p><button style=\"font-size:1.5em;\" class=\"_eaglercraftX_allow_javascript\">Allow</button>&emsp;"
+ "<button style=\"font-size:1.5em;\" class=\"_eaglercraftX_block_javascript\">Block</button></p></div></div>";
const serial = webviewResetSerial;
if(options["contentMode"] !== 1) {
const urlStr = options["url"];
currentAllowJavaScript.querySelector("._eaglercraftX_permission_target_url").innerText = urlStr.length() > 255 ? (urlStr.substring(0, 253) + "...") : urlStr;
}
currentAllowJavaScript.querySelector("._eaglercraftX_allow_javascript").addEventListener("click", function(/** Event */ evt) {
if(webviewResetSerial === serial && currentAllowJavaScript !== null) {
const chkbox = currentAllowJavaScript.querySelector("._eaglercraftX_remember_javascript");
if(chkbox !== null && chkbox.checked) {
pushEvent(EVENT_TYPE_WEBVIEW, EVENT_WEBVIEW_PERMISSION_ALLOW, null);
}
currentAllowJavaScript.remove();
currentAllowJavaScript = null;
++webviewResetSerial;
beginShowingDirect(options);
}
});
currentAllowJavaScript.querySelector("._eaglercraftX_block_javascript").addEventListener("click", function(/** Event */ evt) {
if(webviewResetSerial === serial && currentAllowJavaScript !== null) {
const chkbox = currentAllowJavaScript.querySelector("._eaglercraftX_remember_javascript");
if(chkbox !== null && chkbox.checked) {
pushEvent(EVENT_TYPE_WEBVIEW, EVENT_WEBVIEW_PERMISSION_BLOCK, null);
}
beginShowingContentBlocked(options);
}
});
}
function beginShowingContentBlocked(options) {
beginShowingEnableJSSetup();
currentAllowJavaScript.innerHTML =
"<div style=\"padding-top:13vh;\"><h1 style=\"text-align:center;\">"
+ "<img width=\"48\" height=\"48\" style=\"vertical-align:middle;\" src=\"" + faviconURL + "\">&emsp;Content Blocked</h1>"
+ "<h4 style=\"text-align:center;\">You chose to block JavaScript execution for this embed</h4>"
+ "<p style=\"text-align:center;\"><button style=\"font-size:1.0em;\" class=\"_eaglercraftX_re_evaluate_javascript\">Re-evaluate</button></p></div>";
const serial = webviewResetSerial;
currentAllowJavaScript.querySelector("._eaglercraftX_re_evaluate_javascript").addEventListener("click", function(/** Event */ evt) {
if(webviewResetSerial === serial && currentAllowJavaScript !== null) {
pushEvent(EVENT_TYPE_WEBVIEW, EVENT_WEBVIEW_PERMISSION_CLEAR, null);
beginShowingEnableJavaScript(options);
}
});
}
function beginShowingSafetyError() {
beginShowingEnableJSSetup();
currentAllowJavaScript.innerHTML =
"<div style=\"padding-top:13vh;\"><h1 style=\"text-align:center;\">"
+ "<img width=\"48\" height=\"48\" style=\"vertical-align:middle;\" src=\"" + faviconURL + "\">&emsp;IFrame Safety Error</h1>"
+ "<h4 style=\"text-align:center;\">The content cannot be displayed safely!</h4>"
+ "<h4 style=\"text-align:center;\">Check console for more details</h4></div>";
}
/**
* @param {number} x
* @param {number} y
* @param {number} w
* @param {number} h
*/
function resizeImpl(x, y, w, h) {
if(currentIFrameContainer) {
const s = window.devicePixelRatio;
currentIFrameContainer.style.top = "" + (y / s) + "px";
currentIFrameContainer.style.left = "" + (x / s) + "px";
currentIFrameContainer.style.width = "" + ((w / s) - 10) + "px";
currentIFrameContainer.style.height = "" + ((h / s) - 10) + "px";
}
}
function endShowingImpl() {
++webviewResetSerial;
if(currentIFrame) {
currentIFrame.remove();
currentIFrame = null;
}
currentMessageHandler = null;
if(currentAllowJavaScript) {
currentAllowJavaScript.remove();
currentAllowJavaScript = null;
}
if(currentIFrameContainer) {
currentIFrameContainer.remove();
currentIFrameContainer = null;
}
window.focus();
}
webViewImports["resize"] = resizeImpl;
webViewImports["endShowing"] = endShowingImpl;
}
function initializeNoPlatfWebView(webViewImports) {
setUnsupportedFunc(webViewImports, platfWebViewName, "checkSupported");
setUnsupportedFunc(webViewImports, platfWebViewName, "checkCSPSupported");
setUnsupportedFunc(webViewImports, platfWebViewName, "sendStringMessage");
setUnsupportedFunc(webViewImports, platfWebViewName, "sendBinaryMessage");
setUnsupportedFunc(webViewImports, platfWebViewName, "beginShowing");
setUnsupportedFunc(webViewImports, platfWebViewName, "resize");
setUnsupportedFunc(webViewImports, platfWebViewName, "endShowing");
}

View File

@ -0,0 +1,82 @@
/*
* 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 serverPlatfSPName = "serverPlatformSingleplayer";
/** @type {function(string, boolean)|null} */
var sendIntegratedServerCrash = null;
function initializeServerPlatfSP(spImports) {
const serverMessageQueue = new EaglerLinkedQueue();
/**
* @param {Object} o
*/
self.__eaglerXOnMessage = function(o) {
const channel = o["ch"];
const buf = o["dat"];
if(!channel) {
eagError("Recieved IPC packet with null channel");
return;
}
if(!buf) {
eagError("Recieved IPC packet with null buffer");
return;
}
serverMessageQueue.push({
"ch": channel,
"data": new Uint8Array(buf),
"_next": null
});
};
/**
* @param {string} channel
* @param {Uint8Array} arr
*/
spImports["sendPacket"] = function(channel, arr) {
const copiedArray = new Uint8Array(arr.length);
copiedArray.set(arr, 0);
postMessage({
"ch": channel,
"dat": copiedArray.buffer
});
};
spImports["getAvailablePackets"] = serverMessageQueue.getLength.bind(serverMessageQueue);
spImports["getNextPacket"] = serverMessageQueue.shift.bind(serverMessageQueue);
spImports["setCrashCallback"] = function() {
return {
"call": function(functor) {
sendIntegratedServerCrash = functor;
}
};
};
}
function initializeNoServerPlatfSP(spImports) {
setUnsupportedFunc(spImports, serverPlatfSPName, "sendPacket");
setUnsupportedFunc(spImports, serverPlatfSPName, "getAvailablePackets");
setUnsupportedFunc(spImports, serverPlatfSPName, "getNextPacket");
setUnsupportedFunc(spImports, serverPlatfSPName, "setCrashCallback");
}

View File

@ -0,0 +1,862 @@
/*
* Copyright 2024 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** @type {Object} */
var wasmGC;
(function() {
let globalsCache = new Map();
let exceptionFrameRegex = /.+:wasm-function\[[0-9]+]:0x([0-9a-f]+).*/;
/**
* @param {string} name
*/
let getGlobalName = function(name) {
let result = globalsCache.get(name);
if (typeof result === "undefined") {
result = new Function("return " + name + ";");
globalsCache.set(name, result);
}
return result();
}
/**
* @param {string} name
* @param {Object} value
*/
let setGlobalName = function(name, value) {
new Function("value", name + " = value;")(value);
}
/**
* @param {Object} imports
*/
function defaults(imports) {
let context = {
/** @type {Object} */
exports: null,
/** @type {function(number)|null} */
stackDeobfuscator: null,
/** @type {function(function())|null} */
asyncRunnableQueue: null
};
dateImports(imports);
consoleImports(imports);
coreImports(imports, context);
jsoImports(imports, context);
imports["teavmMath"] = Math;
return {
supplyExports(/** Object */ exports) {
context.exports = exports;
},
supplyStackDeobfuscator(/** function(number) */ deobfuscator) {
context.stackDeobfuscator = deobfuscator;
},
supplyAsyncRunnableQueue(/** function(function()) */ queueFunc) {
context.asyncRunnableQueue = queueFunc;
}
}
}
let javaExceptionSymbol = Symbol("javaException");
class JavaError extends Error {
constructor(context, javaException) {
super();
this.context = context;
this[javaExceptionSymbol] = javaException;
context.exports["teavm.setJsException"](javaException, this);
}
get message() {
let exceptionMessage = this.context.exports["teavm.exceptionMessage"];
if (typeof exceptionMessage === "function") {
let message = exceptionMessage(this[javaExceptionSymbol]);
if (message != null) {
return message;
}
}
return "(could not fetch message)";
}
}
/**
* @param {Object} imports
*/
function dateImports(imports) {
imports["teavmDate"] = {
"currentTimeMillis": () => new Date().getTime(),
"dateToString": (/** number */ timestamp) => new Date(timestamp).toString(),
"getYear": (/** number */ timestamp) => new Date(timestamp).getFullYear(),
"setYear": (/** number */ timestamp, /** number */ year) => {
let date = new Date(timestamp);
date.setFullYear(year);
return date.getTime();
},
"getMonth": (/** number */ timestamp) =>new Date(timestamp).getMonth(),
"setMonth": (/** number */ timestamp, /** number */ month) => {
let date = new Date(timestamp);
date.setMonth(month);
return date.getTime();
},
"getDate": (/** number */ timestamp) =>new Date(timestamp).getDate(),
"setDate": (/** number */ timestamp, /** number */ value) => {
let date = new Date(timestamp);
date.setDate(value);
return date.getTime();
},
"create": (/** number */ year, /** number */ month, /** number */ date, /** number */ hrs, /** number */ min, /** number */ sec) => new Date(year, month, date, hrs, min, sec).getTime(),
"createFromUTC": (/** number */ year, /** number */ month, /** number */ date, /** number */ hrs, /** number */ min, /** number */ sec) => Date.UTC(year, month, date, hrs, min, sec)
};
}
/**
* @param {Object} imports
*/
function consoleImports(imports) {
let stderr = [];
let stdout = [];
imports["teavmConsole"] = {
"putcharStderr": function(/** number */ c) {
if (c === 10) {
let stderrStr = String.fromCharCode(...stderr);
console.error(stderrStr);
if(currentRedirectorFunc) {
currentRedirectorFunc(stderrStr, true);
}
stderr.length = 0;
} else {
stderr.push(c);
}
},
"putcharStdout": function(/** number */ c) {
if (c === 10) {
let stdoutStr = String.fromCharCode(...stdout);
console.log(stdoutStr);
if(currentRedirectorFunc) {
currentRedirectorFunc(stdoutStr, false);
}
stdout.length = 0;
} else {
stdout.push(c);
}
},
};
}
/**
* @param {Object} imports
* @param {Object} context
*/
function coreImports(imports, context) {
let finalizationRegistry = new FinalizationRegistry(heldValue => {
let report = context.exports["teavm.reportGarbageCollectedValue"];
if (typeof report !== "undefined") {
context.asyncRunnableQueue(function() {
report(heldValue.queue, heldValue.ref);
});
}
});
let stringFinalizationRegistry = new FinalizationRegistry(heldValue => {
let report = context.exports["teavm.reportGarbageCollectedString"];
if (typeof report === "function") {
context.asyncRunnableQueue(function() {
report(heldValue);
});
}
});
imports["teavm"] = {
"createWeakRef": (value, ref, queue) => {
if (queue !== null) {
finalizationRegistry.register(value, { ref: ref, queue: queue });
}
return new WeakRef(value);
},
"deref": weakRef => weakRef.deref(),
"createStringWeakRef": (value, heldValue) => {
stringFinalizationRegistry.register(value, heldValue)
return new WeakRef(value);
},
"stringDeref": weakRef => weakRef.deref(),
"takeStackTrace": () => {
let stack = new Error().stack;
let addresses = [];
for (let line of stack.split("\n")) {
let match = exceptionFrameRegex.exec(line);
if (match !== null && match.length >= 2) {
let address = parseInt(match[1], 16);
addresses.push(address);
}
}
return {
"getStack": function() {
let result;
if (context.stackDeobfuscator) {
try {
result = context.stackDeobfuscator(addresses);
} catch (e) {
console.warn("Could not deobfuscate stack", e);
}
}
if (!result) {
result = addresses.map(address => {
return {
className: "java.lang.Throwable$FakeClass",
method: "fakeMethod",
file: "Throwable.java",
line: address
};
});
}
return result;
}
};
},
"decorateException": (javaException) => {
new JavaError(context, javaException);
}
};
}
/**
* @param {Object} imports
* @param {Object} context
*/
function jsoImports(imports, context) {
let javaObjectSymbol = Symbol("javaObject");
let functionsSymbol = Symbol("functions");
let functionOriginSymbol = Symbol("functionOrigin");
let wrapperCallMarkerSymbol = Symbol("wrapperCallMarker");
let jsWrappers = new WeakMap();
let javaWrappers = new WeakMap();
let primitiveWrappers = new Map();
let primitiveFinalization = new FinalizationRegistry(token => primitiveWrappers.delete(token));
let hashCodes = new WeakMap();
let lastHashCode = 2463534242;
let nextHashCode = () => {
let x = lastHashCode;
x ^= x << 13;
x ^= x >>> 17;
x ^= x << 5;
lastHashCode = x;
return x;
}
function identity(value) {
return value;
}
function sanitizeName(str) {
let result = "";
let firstChar = str.charAt(0);
result += isIdentifierStart(firstChar) ? firstChar : '_';
for (let i = 1; i < str.length; ++i) {
let c = str.charAt(i)
result += isIdentifierPart(c) ? c : '_';
}
return result;
}
function isIdentifierStart(s) {
return s >= 'A' && s <= 'Z' || s >= 'a' && s <= 'z' || s === '_' || s === '$';
}
function isIdentifierPart(s) {
return isIdentifierStart(s) || s >= '0' && s <= '9';
}
function setProperty(obj, prop, value) {
if (obj === null) {
setGlobalName(prop, value);
} else {
obj[prop] = value;
}
}
function javaExceptionToJs(e) {
if (e instanceof WebAssembly.Exception) {
let tag = context.exports["teavm.javaException"];
let getJsException = context.exports["teavm.getJsException"];
if (e.is(tag)) {
let javaException = e.getArg(tag, 0);
let extracted = extractException(javaException);
if (extracted !== null) {
return extracted;
}
let wrapper = getJsException(javaException);
if (typeof wrapper === "undefined") {
wrapper = new JavaError(context, javaException);
}
return wrapper;
}
}
return e;
}
function jsExceptionAsJava(e) {
if (javaExceptionSymbol in e) {
return e[javaExceptionSymbol];
} else {
return context.exports["teavm.js.wrapException"](e);
}
}
function rethrowJsAsJava(e) {
context.exports["teavm.js.throwException"](jsExceptionAsJava(e));
}
function extractException(e) {
return context.exports["teavm.js.extractException"](e);
}
function rethrowJavaAsJs(e) {
throw javaExceptionToJs(e);
}
function getProperty(obj, prop) {
try {
return obj !== null ? obj[prop] : getGlobalName(prop)
} catch (e) {
rethrowJsAsJava(e);
}
}
function defineFunction(fn) {
let params = [];
for (let i = 0; i < fn.length; ++i) {
params.push("p" + i);
}
let paramsAsString = params.length === 0 ? "" : params.join(", ");
let ret = new Function("rethrowJavaAsJs", "fn",
`return function(${paramsAsString}) {\n` +
` try {\n` +
` return fn(${paramsAsString});\n` +
` } catch (e) {\n` +
` rethrowJavaAsJs(e);\n` +
` }\n` +
`};`
)(rethrowJavaAsJs, fn);
ret["__impl"] = fn;
ret["__rethrow"] = rethrowJavaAsJs;
return ret;
}
function renameConstructor(name, c) {
return new Function(
"constructor",
`return function ${name}(marker, javaObject) {\n` +
` return constructor.call(this, marker, javaObject);\n` +
`}\n`
)(c);
}
imports["teavmJso"] = {
"emptyString": () => "",
"stringFromCharCode": code => String.fromCharCode(code),
"concatStrings": (a, b) => a + b,
"stringLength": s => s.length,
"charAt": (s, index) => s.charCodeAt(index),
"emptyArray": () => [],
"appendToArray": (array, e) => array.push(e),
"unwrapBoolean": value => value ? 1 : 0,
"wrapBoolean": value => !!value,
"getProperty": getProperty,
"setProperty": setProperty,
"setPropertyPure": setProperty,
"global": (name) => {
try {
return getGlobalName(name);
} catch (e) {
rethrowJsAsJava(e);
}
},
"createClass": (name, parent, constructor) => {
name = sanitizeName(name || "JavaObject");
let action;
if (parent === null) {
action = function (javaObject) {
this[javaObjectSymbol] = javaObject;
this[functionsSymbol] = null;
};
} else {
action = function (javaObject) {
parent.call(this, javaObject);
};
//TODO: what are these for
//fn.prototype = Object.create(parent);
//fn.prototype.constructor = parent;
}
let fn = renameConstructor(name, function (marker, javaObject) {
if (marker === wrapperCallMarkerSymbol) {
action.call(this, javaObject);
} else if (constructor === null) {
throw new Error("This class can't be instantiated directly");
} else {
try {
return constructor.apply(null, arguments);
} catch (e) {
rethrowJavaAsJs(e);
}
}
});
fn.prototype = Object.create(parent || Object.prototype);
fn.prototype.constructor = fn;
let boundFn = renameConstructor(name, function(javaObject) {
return fn.call(this, wrapperCallMarkerSymbol, javaObject);
});
boundFn[wrapperCallMarkerSymbol] = fn;
boundFn.prototype = fn.prototype;
return boundFn;
},
"exportClass": (cls) => {
return cls[wrapperCallMarkerSymbol];
},
"defineMethod": (cls, name, fn) => {
let params = [];
for (let i = 1; i < fn.length; ++i) {
params.push("p" + i);
}
let paramsAsString = params.length === 0 ? "" : params.join(", ");
cls.prototype[name] = new Function("rethrowJavaAsJs", "fn",
`return function(${paramsAsString}) {\n` +
` try {\n` +
` return fn(${['this', params].join(", ")});\n` +
` } catch (e) {\n` +
` rethrowJavaAsJs(e);\n` +
` }\n` +
`};`
)(rethrowJavaAsJs, fn);
},
"defineStaticMethod": (cls, name, fn) => {
cls[name] = defineFunction(fn);
},
"defineFunction": defineFunction,
"defineProperty": (cls, name, getFn, setFn) => {
let descriptor = {
get() {
try {
return getFn(this);
} catch (e) {
rethrowJavaAsJs(e);
}
}
};
if (setFn !== null) {
descriptor.set = function(value) {
try {
setFn(this, value);
} catch (e) {
rethrowJavaAsJs(e);
}
}
}
Object.defineProperty(cls.prototype, name, descriptor);
},
"defineStaticProperty": (cls, name, getFn, setFn) => {
let descriptor = {
get() {
try {
return getFn();
} catch (e) {
rethrowJavaAsJs(e);
}
}
};
if (setFn !== null) {
descriptor.set = function(value) {
try {
setFn(value);
} catch (e) {
rethrowJavaAsJs(e);
}
}
}
Object.defineProperty(cls, name, descriptor);
},
"javaObjectToJS": (instance, cls) => {
if (instance === null) {
return null;
}
let existing = jsWrappers.get(instance);
if (typeof existing != "undefined") {
let result = existing.deref();
if (typeof result !== "undefined") {
return result;
}
}
let obj = new cls(instance);
jsWrappers.set(instance, new WeakRef(obj));
return obj;
},
"unwrapJavaObject": (instance) => {
return instance[javaObjectSymbol];
},
"asFunction": (instance, propertyName) => {
let functions = instance[functionsSymbol];
if (functions === null) {
functions = Object.create(null);
instance[functionsSymbol] = functions;
}
let result = functions[propertyName];
if (typeof result !== 'function') {
result = function() {
return instance[propertyName].apply(instance, arguments);
}
result[functionOriginSymbol] = instance;
functions[propertyName] = result;
}
return result;
},
"functionAsObject": (fn, property) => {
let origin = fn[functionOriginSymbol];
if (typeof origin !== 'undefined') {
let functions = origin[functionsSymbol];
if (functions !== void 0 && functions[property] === fn) {
return origin;
}
}
return {
[property]: function(...args) {
try {
return fn(...args);
} catch (e) {
rethrowJavaAsJs(e);
}
}
};
},
"wrapObject": (obj) => {
if (obj === null) {
return null;
}
if (typeof obj === "object" || typeof obj === "function" || typeof "obj" === "symbol") {
let result = obj[javaObjectSymbol];
if (typeof result === "object") {
return result;
}
result = javaWrappers.get(obj);
if (result !== void 0) {
result = result.deref();
if (result !== void 0) {
return result;
}
}
result = context.exports["teavm.jso.createWrapper"](obj);
javaWrappers.set(obj, new WeakRef(result));
return result;
} else {
let result = primitiveWrappers.get(obj);
if (result !== void 0) {
result = result.deref();
if (result !== void 0) {
return result;
}
}
result = context.exports["teavm.jso.createWrapper"](obj);
primitiveWrappers.set(obj, new WeakRef(result));
primitiveFinalization.register(result, obj);
return result;
}
},
"isPrimitive": (value, type) => typeof value === type,
"instanceOf": (value, type) => value instanceof type,
"instanceOfOrNull": (value, type) => value === null || value instanceof type,
"sameRef": (a, b) => a === b,
"hashCode": (obj) => {
if (typeof obj === "object" || typeof obj === "function" || typeof obj === "symbol") {
let code = hashCodes.get(obj);
if (typeof code === "number") {
return code;
}
code = nextHashCode();
hashCodes.set(obj, code);
return code;
} else if (typeof obj === "number") {
return obj | 0;
} else if (typeof obj === "bigint") {
return BigInt.asIntN(32, obj);
} else if (typeof obj === "boolean") {
return obj ? 1 : 0;
} else {
return 0;
}
},
"apply": (instance, method, args) => {
try {
if (instance === null) {
let fn = getGlobalName(method);
return fn(...args);
} else {
return instance[method](...args);
}
} catch (e) {
rethrowJsAsJava(e);
}
},
"concatArray": (a, b) => a.concat(b),
"getJavaException": e => e[javaExceptionSymbol]
};
for (let name of ["wrapByte", "wrapShort", "wrapChar", "wrapInt", "wrapFloat", "wrapDouble", "unwrapByte",
"unwrapShort", "unwrapChar", "unwrapInt", "unwrapFloat", "unwrapDouble"]) {
imports["teavmJso"][name] = identity;
}
function wrapCallFromJavaToJs(call) {
try {
return call();
} catch (e) {
rethrowJsAsJava(e);
}
}
let argumentList = [];
for (let i = 0; i < 32; ++i) {
let args = argumentList.length === 0 ? "" : argumentList.join(", ");
let argsAndBody = [...argumentList, "body"].join(", ");
imports["teavmJso"]["createFunction" + i] = new Function("wrapCallFromJavaToJs", ...argumentList, "body",
`return new Function('wrapCallFromJavaToJs', ${argsAndBody}).bind(this, wrapCallFromJavaToJs);`
).bind(null, wrapCallFromJavaToJs);
imports["teavmJso"]["bindFunction" + i] = (f, ...args) => f.bind(null, ...args);
imports["teavmJso"]["callFunction" + i] = new Function("rethrowJsAsJava", "fn", ...argumentList,
`try {\n` +
` return fn(${args});\n` +
`} catch (e) {\n` +
` rethrowJsAsJava(e);\n` +
`}`
).bind(null, rethrowJsAsJava);
imports["teavmJso"]["callMethod" + i] = new Function("rethrowJsAsJava", "getGlobalName", "instance",
"method", ...argumentList,
`try {\n`+
` return instance !== null\n` +
` ? instance[method](${args})\n` +
` : getGlobalName(method)(${args});\n` +
`} catch (e) {\n` +
` rethrowJsAsJava(e);\n` +
`}`
).bind(null, rethrowJsAsJava, getGlobalName);
imports["teavmJso"]["construct" + i] = new Function("rethrowJsAsJava", "constructor", ...argumentList,
`try {\n` +
` return new constructor(${args});\n` +
`} catch (e) {\n` +
` rethrowJsAsJava(e);\n` +
`}`
).bind(null, rethrowJsAsJava);
imports["teavmJso"]["arrayOf" + i] = new Function(...argumentList, "return [" + args + "]");
let param = "p" + (i + 1);
argumentList.push(param);
}
}
/**
* @param {Object} importObj
*/
function wrapImport(importObj) {
return new Proxy(importObj, {
get(target, prop) {
let result = target[prop];
return new WebAssembly.Global({ "value": "externref", "mutable": false }, result);
}
});
}
/*
* @param {!WebAssembly.Module} wasmModule
* @param {Object} imports
* @return {!Promise}
*
async function wrapImports(wasmModule, imports) {
let promises = [];
let propertiesToAdd = {};
for (let { module, name, kind } of WebAssembly.Module.imports(wasmModule)) {
if (kind !== "global" || module in imports) {
continue;
}
let names = propertiesToAdd[module];
if (names === void 0) {
let namesByModule = [];
names = namesByModule;
propertiesToAdd[module] = names;
promises.push((async () => {
let moduleInstance = await import(module);
let importsByModule = {};
for (let name of namesByModule) {
let importedName = name === "__self__" ? moduleInstance : moduleInstance[name];
importsByModule[name] = new WebAssembly.Global(
{ value: "externref", mutable: false },
importedName
);
}
imports[module] = importsByModule;
})());
}
names.push(name);
}
if (promises.length === 0) {
return;
}
await Promise.all(promises);
}*/
/**
* @param {string|!WebAssembly.Module} path
* @param {Object} options
* @return {!Promise<Object>}
*/
async function load(path, options) {
if (!options) {
options = {};
}
let deobfuscatorOptions = options.stackDeobfuscator || {};
let [deobfuscatorFactory, module, debugInfo] = await Promise.all([
deobfuscatorOptions.enabled ? getDeobfuscator(deobfuscatorOptions) : Promise.resolve(null),
(path instanceof WebAssembly.Module) ? Promise.resolve(path) : WebAssembly.compileStreaming(fetch(path)),
fetchExternalDebugInfo(deobfuscatorOptions.infoLocation, deobfuscatorOptions)
]);
const importObj = {};
const defaultsResult = defaults(importObj);
if (typeof options.installImports !== "undefined") {
options.installImports(importObj);
}
//if (!options.noAutoImports) {
// await wrapImports(module, importObj);
//}
defaultsResult.supplyAsyncRunnableQueue(setupAsyncCallbackPoll(importObj));
let instance = /** @type {!WebAssembly.Instance} */ (await WebAssembly.instantiate(module, importObj));
let userExports = {};
defaultsResult.supplyExports(instance.exports);
if (deobfuscatorFactory) {
let deobfuscator = createDeobfuscator(null, debugInfo, deobfuscatorFactory.instance);
if (deobfuscator !== null) {
defaultsResult.supplyStackDeobfuscator(deobfuscator);
userExports["deobfuscator"] = deobfuscator;
}
}
let teavm = {
exports: userExports,
instance: instance,
modules: {
classes: module,
deobfuscator: (deobfuscatorFactory ? deobfuscatorFactory.module : null)
}
};
for (let key in instance.exports) {
let exportObj = instance.exports[key];
if (exportObj instanceof WebAssembly.Global) {
Object.defineProperty(userExports, key, {
get: () => exportObj.value
});
}else if (typeof exportObj === "function") {
userExports[key] = exportObj;
}
}
userExports.memory = instance.exports["teavm.memory"];
userExports.debugInfo = debugInfo;
return teavm;
}
/**
* @param {Object} options
* @return {!Promise<?{module:!WebAssembly.Module,instance:!WebAssembly.Instance}>}
*/
async function getDeobfuscator(options) {
try {
const importObj = {};
const defaultsResult = defaults(importObj);
const module = (options.path instanceof WebAssembly.Module) ?
options.path : await WebAssembly.compileStreaming(fetch(options.path));
const instance = new WebAssembly.Instance(module, importObj);
defaultsResult.supplyExports(instance.exports)
return { module, instance };
} catch (e) {
console.warn("Could not load deobfuscator", e);
return null;
}
}
/**
* @param {WebAssembly.Module} module
* @param {Int8Array} externalData
* @param {WebAssembly.Instance} deobfuscatorFactory
* @return {function(number)}
*/
function createDeobfuscator(module, externalData, deobfuscatorFactory) {
let deobfuscator = null;
let deobfuscatorInitialized = false;
function ensureDeobfuscator() {
if (!deobfuscatorInitialized) {
deobfuscatorInitialized = true;
if (externalData !== null) {
try {
deobfuscator = deobfuscatorFactory.exports["createFromExternalFile"].value(externalData);
} catch (e) {
console.warn("Could not load create deobfuscator", e);
}
}
if (deobfuscator == null && module !== null) {
try {
deobfuscator = deobfuscatorFactory.exports["createForModule"].value(module);
} catch (e) {
console.warn("Could not create deobfuscator from module data", e);
}
}
}
}
return addresses => {
ensureDeobfuscator();
return deobfuscator !== null ? deobfuscator["deobfuscate"](addresses) : [];
}
}
/**
* @param {string} debugInfoLocation
* @param {Object} options
* @return {!Promise<Int8Array>}
*/
async function fetchExternalDebugInfo(debugInfoLocation, options) {
if (!options.enabled) {
return null;
}
if (debugInfoLocation !== "auto" && debugInfoLocation !== "external") {
return null;
}
if(options.externalInfoPath instanceof ArrayBuffer) {
return new Int8Array(options.externalInfoPath);
}else {
let response = await fetch(options.externalInfoPath);
if (!response.ok) {
return null;
}
return new Int8Array(await response.arrayBuffer());
}
}
/**
* @param {Object} importObj
* @return {function(function())}
*/
function setupAsyncCallbackPoll(importObj) {
const queueObj = new EaglerLinkedQueue();
importObj["teavm"]["pollAsyncCallbacks"] = function() {
var v;
while(v = queueObj.shift()) {
v["fn"]();
}
};
return function(/** function() */ fn) {
queueObj.push({
"fn": fn,
"_next": null
});
};
}
wasmGC = (function() {
//include();
return { load, defaults, wrapImport };
})();
})();