/* * 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} */ 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} */ 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} */ 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 }; })(); })();