mirror of
https://github.com/Eaglercraft-Archive/Eaglercraftx-1.8.8-src.git
synced 2025-06-27 18:38:14 -05:00
Update #44 - WebAssembly GC support, fix more WebRTC bugs
This commit is contained in:
80
sources/wasm-gc-teavm/js/WASMGCBufferAllocator.js
Normal file
80
sources/wasm-gc-teavm/js/WASMGCBufferAllocator.js
Normal 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);
|
||||
}
|
209
sources/wasm-gc-teavm/js/clientPlatformSingleplayer.js
Normal file
209
sources/wasm-gc-teavm/js/clientPlatformSingleplayer.js
Normal 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");
|
||||
}
|
98
sources/wasm-gc-teavm/js/eagruntime_entrypoint.js
Normal file
98
sources/wasm-gc-teavm/js/eagruntime_entrypoint.js
Normal 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("???");
|
||||
}
|
970
sources/wasm-gc-teavm/js/eagruntime_main.js
Normal file
970
sources/wasm-gc-teavm/js/eagruntime_main.js
Normal 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 :(</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";
|
||||
}
|
233
sources/wasm-gc-teavm/js/eagruntime_util.js
Normal file
233
sources/wasm-gc-teavm/js/eagruntime_util.js
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
129
sources/wasm-gc-teavm/js/externs.js
Normal file
129
sources/wasm-gc-teavm/js/externs.js
Normal 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 = "";
|
541
sources/wasm-gc-teavm/js/fix-webm-duration.js
Normal file
541
sources/wasm-gc-teavm/js/fix-webm-duration.js
Normal 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);
|
||||
}
|
||||
}
|
502
sources/wasm-gc-teavm/js/platformApplication.js
Normal file
502
sources/wasm-gc-teavm/js/platformApplication.js
Normal 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");
|
||||
}
|
106
sources/wasm-gc-teavm/js/platformAssets.js
Normal file
106
sources/wasm-gc-teavm/js/platformAssets.js
Normal 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");
|
||||
}
|
78
sources/wasm-gc-teavm/js/platformAudio.js
Normal file
78
sources/wasm-gc-teavm/js/platformAudio.js
Normal 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");
|
||||
}
|
295
sources/wasm-gc-teavm/js/platformFilesystem.js
Normal file
295
sources/wasm-gc-teavm/js/platformFilesystem.js
Normal 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);
|
1138
sources/wasm-gc-teavm/js/platformInput.js
Normal file
1138
sources/wasm-gc-teavm/js/platformInput.js
Normal file
File diff suppressed because it is too large
Load Diff
365
sources/wasm-gc-teavm/js/platformNetworking.js
Normal file
365
sources/wasm-gc-teavm/js/platformNetworking.js
Normal 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;
|
||||
};
|
||||
|
||||
})();
|
387
sources/wasm-gc-teavm/js/platformOpenGL.js
Normal file
387
sources/wasm-gc-teavm/js/platformOpenGL.js
Normal 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");
|
||||
}
|
208
sources/wasm-gc-teavm/js/platformRuntime.js
Normal file
208
sources/wasm-gc-teavm/js/platformRuntime.js
Normal 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;
|
||||
};
|
123
sources/wasm-gc-teavm/js/platformScreenRecord.js
Normal file
123
sources/wasm-gc-teavm/js/platformScreenRecord.js
Normal 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");
|
||||
}
|
197
sources/wasm-gc-teavm/js/platformVoiceClient.js
Normal file
197
sources/wasm-gc-teavm/js/platformVoiceClient.js
Normal 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");
|
||||
}
|
651
sources/wasm-gc-teavm/js/platformWebRTC.js
Normal file
651
sources/wasm-gc-teavm/js/platformWebRTC.js
Normal 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");
|
||||
}
|
523
sources/wasm-gc-teavm/js/platformWebView.js
Normal file
523
sources/wasm-gc-teavm/js/platformWebView.js
Normal 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 + "\"> 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 + " | "
|
||||
+ "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> "
|
||||
+ "<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 + "\"> 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 + "\"> 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");
|
||||
}
|
82
sources/wasm-gc-teavm/js/serverPlatformSingleplayer.js
Normal file
82
sources/wasm-gc-teavm/js/serverPlatformSingleplayer.js
Normal 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");
|
||||
}
|
862
sources/wasm-gc-teavm/js/teavm_runtime.js
Normal file
862
sources/wasm-gc-teavm/js/teavm_runtime.js
Normal 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 };
|
||||
})();
|
||||
|
||||
})();
|
Reference in New Issue
Block a user