diff --git a/CREDITS b/CREDITS index c1ec6e6..5b07322 100644 --- a/CREDITS +++ b/CREDITS @@ -784,6 +784,81 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + Project Name: Emscripten + Project Author: Emscripten authors + Project URL: https://emscripten.org/ + + Used For: Compiling the WASM runtime's loader.wasm program + + * Emscripten is available under 2 licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. + * + * Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file. + * + * 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. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + Project Name: XZ Embedded + Project Author: Lasse Collin (Larhzu) + Project URL: https://tukaani.org/xz/embedded.html + + Used For: Decompressing the WASM runtime + + * Copyright (C) The XZ Embedded authors and contributors + * + * Permission to use, copy, modify, and/or distribute this + * software for any purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + Project Name: XZ for Java + Project Author: Lasse Collin (Larhzu) + Project URL: https://tukaani.org/xz/java.html + + Used For: Compression in the MakeWASMClientBundle command + + * Copyright (C) The XZ for Java authors and contributors + * + * Permission to use, copy, modify, and/or distribute this + * software for any purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Make sure you also update the copy of this file in "sources/resources/assets/eagler/CREDITS.txt" diff --git a/README.md b/README.md index fe4d57b..2b93a57 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,14 @@ ## Getting Started: -### To compile the latest version of the client, on Windows: +### To compile the latest version of the JavaScript client, on Windows: 1. Make sure you have at least Java 11 installed and added to your PATH, it is recommended to use Java 17 2. Download (clone) this repository to your computer 3. Double click `CompileLatestClient.bat`, a GUI resembling a classic windows installer should open 4. Follow the steps shown to you in the new window to finish compiling -### To compile the latest version of the client, on Linux/macOS: +### To compile the latest version of the JavaScript client, on Linux/macOS: 1. Make sure you have at least Java 11 installed, it is recommended to use Java 17 2. Download (clone) this repository to your computer @@ -37,9 +37,21 @@ 5. Type `./CompileLatestClient.sh` and hit enter, a GUI resembling a classic windows installer should open 6. Follow the steps shown to you in the new window to finish compiling +### To set up the development environment + +1. Prepare the required files in the mcp918 folder ([readme](mcp918/readme.txt)) +2. Run the `build_init` script +3. Run the `build_make_workspace` script + ## Browser Compatibility -EaglercraftX 1.8 is currently known to work on browsers as old as Chrome 38 on Windows XP, the game supports both WebGL 1.0 and WebGL 2.0 however features such as dynamic lighting and PBR shaders require WebGL 2.0. The game also supports mobile browsers that don't have a keyboard or mouse, the game will enter touch screen mode automatically when touch input is detected. The game also includes an embedded OGG codec (JOrbis) for loading audio files on iOS where the browsers don't support loading OGG files in an AudioContext. +The JavaScript runtime of EaglercraftX 1.8 is currently known to work on browsers as old as Chrome 38 on Windows XP, the game supports both WebGL 1.0 and WebGL 2.0 however features such as dynamic lighting and PBR shaders require WebGL 2.0. The game also supports mobile browsers that don't have a keyboard or mouse, the game will enter touch screen mode automatically when touch input is detected. The game also includes an embedded OGG codec (JOrbis) for loading audio files on iOS where the browsers don't support loading OGG files in an AudioContext. + +## WebAssembly GC Support + +EaglercraftX 1.8 also has an experimental WebAssembly GC (WASM-GC) runtime, almost all of the features supported on the JavaScript runtime are also supported on the WebAssembly GC runtime, however it is still incompatible with several major browsers (especially Safari) and will not run in Chrome unless you can access the `chrome://flags` menu or request an origin trial token from Google for your website. Its based on experimental technology and may still crash sometimes due to browser bugs or unresolved issues in the Java to WASM compiler. Hopefully in the coming months the required feature (JSPI, WebAssembly JavaScript Promise Integration) will become enabled by default on the Chrome browser. It performs significantly better than the JavaScript client, around 50% more FPS and TPS in some cases, and will hopefully replace it someday. Just make sure you enable VSync when you play it, otherwise the game will run "too fast" and choke the browser's event loop, causing input lag. + +You can compile the WebAssembly GC runtime by creating a development environment (workspace) and reading the README in the "wasm_gc_teavm" folder. ## Singleplayer @@ -200,6 +212,7 @@ The default eaglercraftXOpts values is this: - `ramdiskMode:` if worlds and resource packs should be stored in RAM instead of IndexedDB - `singleThreadMode:` if the game should run the client and integrated server in the same context instead of creating a worker object - `enableEPKVersionCheck:` if the game should attempt to bypass the browser's cache and retry downloading assets.epk when its outdated +- `enforceVSync:` (WASM only) if the game should automatically re-enable VSync at launch if its disabled - `hooks:` can be used to define JavaScript callbacks for certain events * `localStorageSaved:` JavaScript callback to save local storage keys (key, data) * `localStorageLoaded:` JavaScript callback to load local storage keys (key) returns data diff --git a/buildtools/BuildTools.jar b/buildtools/BuildTools.jar index 577ba56..6ead1ff 100644 Binary files a/buildtools/BuildTools.jar and b/buildtools/BuildTools.jar differ diff --git a/buildtools/src/main/java/net/lax1dude/eaglercraft/v1_8/buildtools/task/diff/PullRequestTask.java b/buildtools/src/main/java/net/lax1dude/eaglercraft/v1_8/buildtools/task/diff/PullRequestTask.java index e883306..85f2f95 100644 --- a/buildtools/src/main/java/net/lax1dude/eaglercraft/v1_8/buildtools/task/diff/PullRequestTask.java +++ b/buildtools/src/main/java/net/lax1dude/eaglercraft/v1_8/buildtools/task/diff/PullRequestTask.java @@ -72,6 +72,11 @@ public class PullRequestTask { File originalSourceTeaVMC = new File(EaglerBuildTools.repositoryRoot, "sources/teavmc-classpath/resources"); File originalSourceBootMenu = new File(EaglerBuildTools.repositoryRoot, "sources/teavm-boot-menu/java"); File originalSourceLWJGL = new File(EaglerBuildTools.repositoryRoot, "sources/lwjgl/java"); + File originalSourceWASMGCTeaVMJava = new File(EaglerBuildTools.repositoryRoot, "sources/wasm-gc-teavm/java"); + File originalSourceWASMGCTeaVMJS = new File(EaglerBuildTools.repositoryRoot, "sources/wasm-gc-teavm/js"); + File originalSourceWASMGCTeaVMBootstrapJS = new File(EaglerBuildTools.repositoryRoot, "sources/wasm-gc-teavm-bootstrap/js"); + File originalSourceWASMGCTeaVMLoaderC = new File(EaglerBuildTools.repositoryRoot, "sources/wasm-gc-teavm-loader/c"); + File originalSourceWASMGCTeaVMLoaderJS = new File(EaglerBuildTools.repositoryRoot, "sources/wasm-gc-teavm-loader/js"); File originalUnpatchedSourceResourcesJar = new File(EaglerBuildToolsConfig.getTemporaryDirectory(), "MinecraftSrc/minecraft_res.jar"); File originalSourceResourcesJar = new File(EaglerBuildToolsConfig.getTemporaryDirectory(), "MinecraftSrc/minecraft_res_patch.jar"); File originalSourceResources = new File(EaglerBuildTools.repositoryRoot, "sources/resources"); @@ -83,6 +88,11 @@ public class PullRequestTask { File diffFromBootMenu = new File(EaglerBuildToolsConfig.getWorkspaceDirectory(), "src/teavm-boot-menu/java"); File diffFromTeaVMC = new File(EaglerBuildToolsConfig.getWorkspaceDirectory(), "src/teavmc-classpath/resources"); File diffFromLWJGL = new File(EaglerBuildToolsConfig.getWorkspaceDirectory(), "src/lwjgl/java"); + File diffFromWASMGCTeaVMJava = new File(EaglerBuildToolsConfig.getWorkspaceDirectory(), "src/wasm-gc-teavm/java"); + File diffFromWASMGCTeaVMJS = new File(EaglerBuildToolsConfig.getWorkspaceDirectory(), "src/wasm-gc-teavm/js"); + File diffFromWASMGCTeaVMBootstrapJS = new File(EaglerBuildToolsConfig.getWorkspaceDirectory(), "src/wasm-gc-teavm-bootstrap/js"); + File diffFromWASMGCTeaVMLoaderC = new File(EaglerBuildToolsConfig.getWorkspaceDirectory(), "src/wasm-gc-teavm-loader/c"); + File diffFromWASMGCTeaVMLoaderJS = new File(EaglerBuildToolsConfig.getWorkspaceDirectory(), "src/wasm-gc-teavm-loader/js"); File diffFromResources = new File(EaglerBuildToolsConfig.getWorkspaceDirectory(), "desktopRuntime/resources"); File pullRequestTo = new File(EaglerBuildTools.repositoryRoot, "pullrequest"); @@ -163,6 +173,36 @@ public class PullRequestTask { } System.out.println("Found " + i + " changed files in /src/lwjgl/java/"); + i = copyAllModified(diffFromWASMGCTeaVMJava, originalSourceWASMGCTeaVMJava); + if(i > 0) { + flag = true; + } + System.out.println("Found " + i + " changed files in /src/wasm-gc-teavm/java/"); + + i = copyAllModified(diffFromWASMGCTeaVMJS, originalSourceWASMGCTeaVMJS); + if(i > 0) { + flag = true; + } + System.out.println("Found " + i + " changed files in /src/wasm-gc-teavm/js/"); + + i = copyAllModified(diffFromWASMGCTeaVMBootstrapJS, originalSourceWASMGCTeaVMBootstrapJS); + if(i > 0) { + flag = true; + } + System.out.println("Found " + i + " changed files in /src/wasm-gc-teavm-bootstrap/js/"); + + i = copyAllModified(diffFromWASMGCTeaVMLoaderC, originalSourceWASMGCTeaVMLoaderC); + if(i > 0) { + flag = true; + } + System.out.println("Found " + i + " changed files in /src/wasm-gc-teavm-loader/c/"); + + i = copyAllModified(diffFromWASMGCTeaVMLoaderJS, originalSourceWASMGCTeaVMLoaderJS); + if(i > 0) { + flag = true; + } + System.out.println("Found " + i + " changed files in /src/wasm-gc-teavm-loader/js/"); + i = createDiffFiles(null, minecraftJavadocTmp, originalUnpatchedSourceMainJar, originalSourceMainJar, diffFromGame, pullRequestToMain, true); if(i > 0) { diff --git a/buildtools/src/main/java/net/lax1dude/eaglercraft/v1_8/buildtools/task/init/SetupWorkspace.java b/buildtools/src/main/java/net/lax1dude/eaglercraft/v1_8/buildtools/task/init/SetupWorkspace.java index 10fea86..4f1378f 100644 --- a/buildtools/src/main/java/net/lax1dude/eaglercraft/v1_8/buildtools/task/init/SetupWorkspace.java +++ b/buildtools/src/main/java/net/lax1dude/eaglercraft/v1_8/buildtools/task/init/SetupWorkspace.java @@ -129,6 +129,11 @@ public class SetupWorkspace { File repoSourcesProtoRelay = new File(repoSources, "protocol-relay/java"); File repoSourcesBootMenu = new File(repoSources, "teavm-boot-menu/java"); File repoSourcesTeavmCRes = new File(repoSources, "teavmc-classpath/resources"); + File repoSourcesWASMGCTeaVMJava = new File(repoSources, "wasm-gc-teavm/java"); + File repoSourcesWASMGCTeaVMJS = new File(repoSources, "wasm-gc-teavm/js"); + File repoSourcesWASMGCTeaVMBootstrapJS = new File(repoSources, "wasm-gc-teavm-bootstrap/js"); + File repoSourcesWASMGCTeaVMLoaderC = new File(repoSources, "wasm-gc-teavm-loader/c"); + File repoSourcesWASMGCTeaVMLoaderJS = new File(repoSources, "wasm-gc-teavm-loader/js"); File repoSourcesResources = new File(repoSources, "resources"); File srcMainJava = new File(workspaceDirectory, "src/main/java"); File srcGameJava = new File(workspaceDirectory, "src/game/java"); @@ -138,6 +143,11 @@ public class SetupWorkspace { File srcProtoRelay = new File(workspaceDirectory, "src/protocol-relay/java"); File srcBootMenu = new File(workspaceDirectory, "src/teavm-boot-menu/java"); File srcTeavmCRes = new File(workspaceDirectory, "src/teavmc-classpath/resources"); + File srcWASMGCTeaVMJava = new File(workspaceDirectory, "src/wasm-gc-teavm/java"); + File srcWASMGCTeaVMJS = new File(workspaceDirectory, "src/wasm-gc-teavm/js"); + File srcWASMGCTeaVMBootstrapJS = new File(workspaceDirectory, "src/wasm-gc-teavm-bootstrap/js"); + File srcWASMGCTeaVMLoaderC = new File(workspaceDirectory, "src/wasm-gc-teavm-loader/c"); + File srcWASMGCTeaVMLoaderJS = new File(workspaceDirectory, "src/wasm-gc-teavm-loader/js"); File resourcesExtractTo = new File(workspaceDirectory, "desktopRuntime/resources"); File mcLanguagesZip = new File(mcTmpDirectory, "minecraft_languages.zip"); File mcLanguagesExtractTo = new File(workspaceDirectory, "javascript/lang"); @@ -165,6 +175,11 @@ public class SetupWorkspace { System.err.println("ERROR: Could not rename \".gitignore.default\" to \".gitignore\" in the workspace directory!"); } + existingGi = new File(workspaceDirectory, "wasm_gc_teavm/.gitignore"); + if((existingGi.exists() && !existingGi.delete()) || !(new File(workspaceDirectory, "wasm_gc_teavm/.gitignore.default").renameTo(existingGi))) { + System.err.println("ERROR: Could not rename \"wasm_gc_teavm/.gitignore.default\" to \"wasm_gc_teavm/.gitignore\" in the workspace directory!"); + } + if(repoSourcesTeaVM.isDirectory()) { System.out.println("Copying files from \"/sources/teavm/java/\" to workspace..."); @@ -183,6 +198,10 @@ public class SetupWorkspace { System.out.println("Copying files from \"/sources/main/java/\" to workspace..."); try { + if(!srcMainJava.isDirectory() && !srcMainJava.mkdirs()) { + System.err.println("ERROR: Could not create destination directory!"); + return false; + } FileUtils.copyDirectory(repoSourcesMain, srcMainJava); }catch(IOException ex) { System.err.println("ERROR: could not copy \"/sources/main/java/\" to \"" + srcMainJava.getAbsolutePath() + "\"!"); @@ -193,6 +212,10 @@ public class SetupWorkspace { System.out.println("Copying files from \"/sources/protocol-game/java/\" to workspace..."); try { + if(!srcProtoGame.isDirectory() && !srcProtoGame.mkdirs()) { + System.err.println("ERROR: Could not create destination directory!"); + return false; + } FileUtils.copyDirectory(repoSourcesProtoGame, srcProtoGame); }catch(IOException ex) { System.err.println("ERROR: could not copy \"/sources/protocol-game/java/\" to \"" + srcProtoGame.getAbsolutePath() + "\"!"); @@ -204,6 +227,10 @@ public class SetupWorkspace { System.out.println("Copying files from \"/sources/protocol-relay/java/\" to workspace..."); try { + if(!srcProtoRelay.isDirectory() && !srcProtoRelay.mkdirs()) { + System.err.println("ERROR: Could not create destination directory!"); + return false; + } FileUtils.copyDirectory(repoSourcesProtoRelay, srcProtoRelay); }catch(IOException ex) { System.err.println("ERROR: could not copy \"/sources/protocol-relay/java/\" to \"" + srcProtoRelay.getAbsolutePath() + "\"!"); @@ -215,6 +242,10 @@ public class SetupWorkspace { System.out.println("Copying files from \"/sources/teavmc-classpath/resources/\" to workspace..."); try { + if(!srcTeavmCRes.isDirectory() && !srcTeavmCRes.mkdirs()) { + System.err.println("ERROR: Could not create destination directory!"); + return false; + } FileUtils.copyDirectory(repoSourcesTeavmCRes, srcTeavmCRes); }catch(IOException ex) { System.err.println("ERROR: could not copy \"/sources/teavmc-classpath/resources/\" to \"" + srcTeavmCRes.getAbsolutePath() + "\"!"); @@ -226,6 +257,10 @@ public class SetupWorkspace { System.out.println("Copying files from \"/sources/teavm-boot-menu/java/\" to workspace..."); try { + if(!srcBootMenu.isDirectory() && !srcBootMenu.mkdirs()) { + System.err.println("ERROR: Could not create destination directory!"); + return false; + } FileUtils.copyDirectory(repoSourcesBootMenu, srcBootMenu); }catch(IOException ex) { System.err.println("ERROR: could not copy \"/sources/teavm-boot-menu/java/\" to \"" + srcBootMenu.getAbsolutePath() + "\"!"); @@ -247,6 +282,81 @@ public class SetupWorkspace { throw ex; } } + + if(repoSourcesWASMGCTeaVMJava.isDirectory()) { + System.out.println("Copying files from \"/sources/wasm-gc-teavm/java/\" to workspace..."); + + try { + if(!srcWASMGCTeaVMJava.isDirectory() && !srcWASMGCTeaVMJava.mkdirs()) { + System.err.println("ERROR: Could not create destination directory!"); + return false; + } + FileUtils.copyDirectory(repoSourcesWASMGCTeaVMJava, srcWASMGCTeaVMJava); + }catch(IOException ex) { + System.err.println("ERROR: could not copy \"/sources/wasm-gc-teavm/java/\" to \"" + srcWASMGCTeaVMJava.getAbsolutePath() + "\"!"); + throw ex; + } + } + + if(repoSourcesWASMGCTeaVMJS.isDirectory()) { + System.out.println("Copying files from \"/sources/wasm-gc-teavm/js/\" to workspace..."); + + try { + if(!srcWASMGCTeaVMJS.isDirectory() && !srcWASMGCTeaVMJS.mkdirs()) { + System.err.println("ERROR: Could not create destination directory!"); + return false; + } + FileUtils.copyDirectory(repoSourcesWASMGCTeaVMJS, srcWASMGCTeaVMJS); + }catch(IOException ex) { + System.err.println("ERROR: could not copy \"/sources/wasm-gc-teavm/js/\" to \"" + srcWASMGCTeaVMJS.getAbsolutePath() + "\"!"); + throw ex; + } + } + + if(repoSourcesWASMGCTeaVMBootstrapJS.isDirectory()) { + System.out.println("Copying files from \"/sources/wasm-gc-teavm-bootstrap/js/\" to workspace..."); + + try { + if(!srcWASMGCTeaVMBootstrapJS.isDirectory() && !srcWASMGCTeaVMBootstrapJS.mkdirs()) { + System.err.println("ERROR: Could not create destination directory!"); + return false; + } + FileUtils.copyDirectory(repoSourcesWASMGCTeaVMBootstrapJS, srcWASMGCTeaVMBootstrapJS); + }catch(IOException ex) { + System.err.println("ERROR: could not copy \"/sources/wasm-gc-teavm-bootstrap/js/\" to \"" + srcWASMGCTeaVMBootstrapJS.getAbsolutePath() + "\"!"); + throw ex; + } + } + + if(repoSourcesWASMGCTeaVMLoaderC.isDirectory()) { + System.out.println("Copying files from \"/sources/wasm-gc-teavm-loader/c/\" to workspace..."); + + try { + if(!srcWASMGCTeaVMLoaderC.isDirectory() && !srcWASMGCTeaVMLoaderC.mkdirs()) { + System.err.println("ERROR: Could not create destination directory!"); + return false; + } + FileUtils.copyDirectory(repoSourcesWASMGCTeaVMLoaderC, srcWASMGCTeaVMLoaderC); + }catch(IOException ex) { + System.err.println("ERROR: could not copy \"/sources/wasm-gc-teavm-loader/c/\" to \"" + srcWASMGCTeaVMLoaderC.getAbsolutePath() + "\"!"); + throw ex; + } + } + + if(repoSourcesWASMGCTeaVMLoaderJS.isDirectory()) { + System.out.println("Copying files from \"/sources/wasm-gc-teavm-loader/js/\" to workspace..."); + + try { + if(!srcWASMGCTeaVMLoaderJS.isDirectory() && !srcWASMGCTeaVMLoaderJS.mkdirs()) { + System.err.println("ERROR: Could not create destination directory!"); + return false; + } + FileUtils.copyDirectory(repoSourcesWASMGCTeaVMLoaderJS, srcWASMGCTeaVMLoaderJS); + }catch(IOException ex) { + System.err.println("ERROR: could not copy \"/sources/wasm-gc-teavm-loader/js/\" to \"" + srcWASMGCTeaVMLoaderJS.getAbsolutePath() + "\"!"); + throw ex; + } + } System.out.println("Copying files from \"/sources/resources/\" to workspace..."); diff --git a/client_version b/client_version index 4f77ea9..15a5b3c 100644 --- a/client_version +++ b/client_version @@ -1 +1 @@ -u43 \ No newline at end of file +u44 \ No newline at end of file diff --git a/patches/minecraft/delete.txt b/patches/minecraft/delete.txt index 4e6a55a..fbf3884 100644 --- a/patches/minecraft/delete.txt +++ b/patches/minecraft/delete.txt @@ -1,4 +1,4 @@ -# 145 files to delete: +# 146 files to delete: net/minecraft/client/renderer/VertexBufferUploader.java net/minecraft/realms/DisconnectedRealmsScreen.java net/minecraft/client/stream/Metadata.java @@ -99,6 +99,7 @@ net/minecraft/realms/RealmsAnvilLevelStorageSource.java net/minecraft/realms/RealmsSliderButton.java net/minecraft/world/storage/ThreadedFileIOBase.java net/minecraft/client/renderer/vertex/VertexBuffer.java +net/minecraft/world/MinecraftException.java net/minecraft/network/rcon/RConConsoleSource.java net/minecraft/world/chunk/storage/AnvilSaveHandler.java net/minecraft/realms/RealmsVertexFormatElement.java diff --git a/patches/minecraft/net/minecraft/client/Minecraft.edit.java b/patches/minecraft/net/minecraft/client/Minecraft.edit.java index 7df27e1..affa45c 100644 --- a/patches/minecraft/net/minecraft/client/Minecraft.edit.java +++ b/patches/minecraft/net/minecraft/client/Minecraft.edit.java @@ -18,7 +18,7 @@ > DELETE 1 @ 1 : 4 -> CHANGE 1 : 74 @ 1 : 4 +> CHANGE 1 : 75 @ 1 : 4 ~ ~ import net.lax1dude.eaglercraft.v1_8.ClientUUIDLoadingCache; @@ -52,6 +52,7 @@ ~ import net.lax1dude.eaglercraft.v1_8.minecraft.EaglerFontRenderer; ~ import net.lax1dude.eaglercraft.v1_8.minecraft.EnumInputEvent; ~ import net.lax1dude.eaglercraft.v1_8.minecraft.GuiScreenGenericErrorMessage; +~ import net.lax1dude.eaglercraft.v1_8.minecraft.GuiScreenVSyncReEnabled; ~ import net.lax1dude.eaglercraft.v1_8.minecraft.GuiScreenVideoSettingsWarning; ~ import net.lax1dude.eaglercraft.v1_8.notifications.ServerNotificationRenderer; ~ import net.lax1dude.eaglercraft.v1_8.opengl.EaglerMeshLoader; @@ -359,12 +360,6 @@ ~ mainMenu = new GuiConnecting(mainMenu, this, this.serverName, this.serverPort); -> CHANGE 2 : 5 @ 2 : 7 - -~ int vidIssues = gameSettings.checkBadVideoSettings(); -~ if (vidIssues != 0) { -~ mainMenu = new GuiScreenVideoSettingsWarning(mainMenu, vidIssues); - > CHANGE 2 : 6 @ 2 : 7 ~ mainMenu = new GuiScreenEditProfile(mainMenu); @@ -372,8 +367,25 @@ ~ if (!EagRuntime.getConfiguration().isForceProfanityFilter() && !gameSettings.hasShownProfanityFilter) { ~ mainMenu = new GuiScreenContentWarning(mainMenu); -> CHANGE 2 : 14 @ 2 : 3 +> CHANGE 2 : 7 @ 2 : 7 +~ boolean vsyncScreen = false; +~ if (EagRuntime.getConfiguration().isEnforceVSync() && Display.isVSyncSupported() && !gameSettings.enableVsync) { +~ gameSettings.enableVsync = true; +~ gameSettings.saveOptions(); +~ vsyncScreen = true; + +> CHANGE 2 : 23 @ 2 : 3 + +~ int vidIssues = gameSettings.checkBadVideoSettings(); +~ if (vidIssues != 0) { +~ mainMenu = new GuiScreenVideoSettingsWarning(mainMenu, vidIssues); +~ } +~ +~ if (vsyncScreen) { +~ mainMenu = new GuiScreenVSyncReEnabled(mainMenu); +~ } +~ ~ this.displayGuiScreen(mainMenu); ~ ~ this.renderEngine.deleteTexture(this.mojangLogo); diff --git a/patches/minecraft/net/minecraft/client/gui/GuiOverlayDebug.edit.java b/patches/minecraft/net/minecraft/client/gui/GuiOverlayDebug.edit.java index b30c05d..5888e29 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiOverlayDebug.edit.java +++ b/patches/minecraft/net/minecraft/client/gui/GuiOverlayDebug.edit.java @@ -345,7 +345,7 @@ > CHANGE 8 : 36 @ 8 : 25 ~ ArrayList arraylist; -~ if (EagRuntime.getPlatformType() != EnumPlatformType.JAVASCRIPT) { +~ if (EagRuntime.getPlatformType() == EnumPlatformType.DESKTOP) { ~ long i = EagRuntime.maxMemory(); ~ long j = EagRuntime.totalMemory(); ~ long k = EagRuntime.freeMemory(); diff --git a/patches/minecraft/net/minecraft/client/gui/GuiScreen.edit.java b/patches/minecraft/net/minecraft/client/gui/GuiScreen.edit.java index 31635d0..df86be6 100644 --- a/patches/minecraft/net/minecraft/client/gui/GuiScreen.edit.java +++ b/patches/minecraft/net/minecraft/client/gui/GuiScreen.edit.java @@ -85,7 +85,7 @@ + long millis = EagRuntime.steadyTimeMillis(); + long closeKeyTimeout = millis - showingCloseKey; -+ if (closeKeyTimeout < 3000l) { ++ if (closeKeyTimeout < 3000l && showingCloseKey != 0l) { + int alpha1 = 0xC0000000; + int alpha2 = 0xFF000000; + if (closeKeyTimeout > 2500l) { diff --git a/patches/minecraft/net/minecraft/client/network/NetHandlerPlayClient.edit.java b/patches/minecraft/net/minecraft/client/network/NetHandlerPlayClient.edit.java index e4253be..1c04a98 100644 --- a/patches/minecraft/net/minecraft/client/network/NetHandlerPlayClient.edit.java +++ b/patches/minecraft/net/minecraft/client/network/NetHandlerPlayClient.edit.java @@ -168,7 +168,154 @@ > DELETE 3 @ 3 : 4 -> DELETE 105 @ 105 : 106 +> CHANGE 3 : 7 @ 3 : 5 + +~ Entity object = null; +~ boolean b = false; +~ switch (packetIn.getType()) { +~ case 10: + +> CHANGE 2 : 5 @ 2 : 3 + +~ break; +~ case 90: +~ b = true; + +> CHANGE 4 : 6 @ 4 : 7 + +~ break; +~ case 60: + +> CHANGE 1 : 3 @ 1 : 2 + +~ break; +~ case 61: + +> CHANGE 1 : 4 @ 1 : 2 + +~ break; +~ case 71: +~ b = true; + +> CHANGE 3 : 6 @ 3 : 5 + +~ break; +~ case 77: +~ b = true; + +> CHANGE 2 : 4 @ 2 : 4 + +~ break; +~ case 65: + +> CHANGE 1 : 3 @ 1 : 2 + +~ break; +~ case 72: + +> CHANGE 1 : 3 @ 1 : 2 + +~ break; +~ case 76: + +> CHANGE 1 : 4 @ 1 : 2 + +~ break; +~ case 63: +~ b = true; + +> CHANGE 3 : 6 @ 3 : 5 + +~ break; +~ case 64: +~ b = true; + +> CHANGE 3 : 6 @ 3 : 5 + +~ break; +~ case 66: +~ b = true; + +> CHANGE 3 : 5 @ 3 : 5 + +~ break; +~ case 62: + +> CHANGE 1 : 4 @ 1 : 2 + +~ break; +~ case 73: +~ b = true; + +> CHANGE 1 : 4 @ 1 : 3 + +~ break; +~ case 75: +~ b = true; + +> CHANGE 1 : 3 @ 1 : 3 + +~ break; +~ case 1: + +> CHANGE 1 : 3 @ 1 : 2 + +~ break; +~ case 50: + +> CHANGE 1 : 3 @ 1 : 2 + +~ break; +~ case 78: + +> CHANGE 1 : 3 @ 1 : 2 + +~ break; +~ case 51: + +> CHANGE 1 : 3 @ 1 : 2 + +~ break; +~ case 2: + +> CHANGE 1 : 4 @ 1 : 2 + +~ break; +~ case 70: +~ b = true; + +> INSERT 2 : 7 @ 2 + ++ break; ++ } ++ ++ if (b) { ++ // fix for compiler bug + +> CHANGE 4 : 10 @ 4 : 10 + +~ object.serverPosX = packetIn.getX(); +~ object.serverPosY = packetIn.getY(); +~ object.serverPosZ = packetIn.getZ(); +~ object.rotationPitch = (float) (packetIn.getPitch() * 360) / 256.0F; +~ object.rotationYaw = (float) (packetIn.getYaw() * 360) / 256.0F; +~ Entity[] aentity = object.getParts(); + +> CHANGE 1 : 2 @ 1 : 2 + +~ int i = packetIn.getEntityID() - object.getEntityId(); + +> CHANGE 6 : 8 @ 6 : 8 + +~ object.setEntityId(packetIn.getEntityID()); +~ this.clientWorldController.addEntityToWorld(packetIn.getEntityID(), object); + +> CHANGE 8 : 10 @ 8 : 10 + +~ object.setVelocity((double) packetIn.getSpeedX() / 8000.0D, (double) packetIn.getSpeedY() / 8000.0D, +~ (double) packetIn.getSpeedZ() / 8000.0D); + +> DELETE 6 @ 6 : 7 > DELETE 12 @ 12 : 13 diff --git a/patches/minecraft/net/minecraft/client/renderer/BlockRendererDispatcher.edit.java b/patches/minecraft/net/minecraft/client/renderer/BlockRendererDispatcher.edit.java index 917f69d..40307c1 100644 --- a/patches/minecraft/net/minecraft/client/renderer/BlockRendererDispatcher.edit.java +++ b/patches/minecraft/net/minecraft/client/renderer/BlockRendererDispatcher.edit.java @@ -18,7 +18,39 @@ ~ public void renderBlockDamage(IBlockState state, BlockPos pos, EaglerTextureAtlasSprite texture, -> CHANGE 55 : 59 @ 55 : 61 +> INSERT 15 : 16 @ 15 + ++ boolean res; + +> CHANGE 2 : 3 @ 2 : 3 + +~ res = false; + +> CHANGE 3 : 5 @ 3 : 4 + +~ res = this.fluidRenderer.renderFluid(blockAccess, state, pos, worldRendererIn); +~ break; + +> CHANGE 1 : 3 @ 1 : 2 + +~ res = false; +~ break; + +> CHANGE 2 : 4 @ 2 : 3 + +~ res = this.blockModelRenderer.renderModel(blockAccess, ibakedmodel, state, pos, worldRendererIn); +~ break; + +> CHANGE 1 : 3 @ 1 : 2 + +~ res = false; +~ break; + +> INSERT 2 : 3 @ 2 + ++ return res; + +> CHANGE 24 : 28 @ 24 : 30 ~ ~ try { diff --git a/patches/minecraft/net/minecraft/crash/CrashReport.edit.java b/patches/minecraft/net/minecraft/crash/CrashReport.edit.java index 56b6994..5f83518 100644 --- a/patches/minecraft/net/minecraft/crash/CrashReport.edit.java +++ b/patches/minecraft/net/minecraft/crash/CrashReport.edit.java @@ -26,13 +26,19 @@ ~ private String[] stacktrace; -> INSERT 4 : 5 @ 4 +> INSERT 2 : 5 @ 2 + ++ if (causeThrowable == null) { ++ throw new NullPointerException("Crash report created for null throwable!"); ++ } + +> INSERT 2 : 3 @ 2 + this.stacktrace = EagRuntime.getStackTraceElements(causeThrowable); > CHANGE 26 : 37 @ 26 : 54 -~ if (EagRuntime.getPlatformType() != EnumPlatformType.JAVASCRIPT) { +~ if (EagRuntime.getPlatformType() == EnumPlatformType.DESKTOP) { ~ this.theReportCategory.addCrashSectionCallable("Memory", new Callable() { ~ public String call() { ~ long i = EagRuntime.maxMemory(); diff --git a/patches/minecraft/net/minecraft/entity/EntityList.edit.java b/patches/minecraft/net/minecraft/entity/EntityList.edit.java index c446c88..7eddaaf 100644 --- a/patches/minecraft/net/minecraft/entity/EntityList.edit.java +++ b/patches/minecraft/net/minecraft/entity/EntityList.edit.java @@ -7,12 +7,14 @@ > DELETE 2 @ 2 : 4 -> CHANGE 4 : 11 @ 4 : 8 +> CHANGE 4 : 13 @ 4 : 8 ~ ~ import com.google.common.collect.Lists; ~ import com.google.common.collect.Maps; ~ +~ import net.lax1dude.eaglercraft.v1_8.EagRuntime; +~ import net.lax1dude.eaglercraft.v1_8.internal.EnumPlatformType; ~ import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; ~ import net.lax1dude.eaglercraft.v1_8.log4j.Logger; ~ import net.lax1dude.eaglercraft.v1_8.minecraft.EntityConstructor; @@ -126,7 +128,22 @@ ~ Set set = stringToClassMapping.keySet(); -> CHANGE 29 : 97 @ 29 : 91 +> CHANGE 2 : 9 @ 2 : 6 + +~ // TODO: Eventually TeaVM will support getModifiers +~ if (EagRuntime.getPlatformType() != EnumPlatformType.WASM_GC) { +~ for (String s : set) { +~ Class oclass = (Class) stringToClassMapping.get(s); +~ if ((oclass.getModifiers() & 1024) != 1024) { +~ arraylist.add(s); +~ } + +> INSERT 1 : 3 @ 1 + ++ } else { ++ arraylist.addAll(set); + +> CHANGE 22 : 90 @ 22 : 84 ~ addMapping(EntityItem.class, EntityItem::new, "Item", 1); ~ addMapping(EntityXPOrb.class, EntityXPOrb::new, "XPOrb", 2); diff --git a/patches/minecraft/net/minecraft/server/MinecraftServer.edit.java b/patches/minecraft/net/minecraft/server/MinecraftServer.edit.java index d326633..6244d53 100644 --- a/patches/minecraft/net/minecraft/server/MinecraftServer.edit.java +++ b/patches/minecraft/net/minecraft/server/MinecraftServer.edit.java @@ -38,7 +38,9 @@ > DELETE 7 @ 7 : 8 -> CHANGE 10 : 11 @ 10 : 12 +> DELETE 4 @ 4 : 5 + +> CHANGE 5 : 6 @ 5 : 7 ~ import net.minecraft.world.chunk.Chunk; @@ -195,7 +197,11 @@ ~ for (int i = 0; i < this.worldServers.length; ++i) { ~ WorldServer worldserver = this.worldServers[i]; -> DELETE 20 @ 20 : 23 +> CHANGE 6 : 7 @ 6 : 11 + +~ worldserver.saveAllChunks(true, (IProgressUpdate) null); + +> DELETE 9 @ 9 : 12 > CHANGE 16 : 22 @ 16 : 21 diff --git a/patches/minecraft/net/minecraft/world/World.edit.java b/patches/minecraft/net/minecraft/world/World.edit.java index 4f65941..a3a9aa9 100644 --- a/patches/minecraft/net/minecraft/world/World.edit.java +++ b/patches/minecraft/net/minecraft/world/World.edit.java @@ -314,7 +314,9 @@ ~ public EntityPlayer getPlayerEntityByUUID(EaglercraftUUID uuid) { -> CHANGE 197 : 198 @ 197 : 198 +> DELETE 13 @ 13 : 17 + +> CHANGE 180 : 181 @ 180 : 181 ~ public EaglercraftRandom setRandomSeed(int parInt1, int parInt2, int parInt3) { diff --git a/patches/minecraft/net/minecraft/world/WorldServer.edit.java b/patches/minecraft/net/minecraft/world/WorldServer.edit.java index 2429e2b..07ea9b2 100644 --- a/patches/minecraft/net/minecraft/world/WorldServer.edit.java +++ b/patches/minecraft/net/minecraft/world/WorldServer.edit.java @@ -108,13 +108,21 @@ ~ EaglercraftRandom random = new EaglercraftRandom(this.getSeed()); -> CHANGE 62 : 65 @ 62 : 63 +> CHANGE 49 : 50 @ 49 : 50 + +~ public void saveAllChunks(boolean progressCallback, IProgressUpdate parIProgressUpdate) { + +> CHANGE 12 : 15 @ 12 : 13 ~ List lst = Lists.newArrayList(this.theChunkProviderServer.func_152380_a()); ~ for (int i = 0, l = lst.size(); i < l; ++i) { ~ Chunk chunk = lst.get(i); -> CHANGE 79 : 82 @ 79 : 80 +> CHANGE 14 : 15 @ 14 : 16 + +~ protected void saveLevel() { + +> CHANGE 63 : 66 @ 63 : 64 ~ List lst = this.playerEntities; ~ for (int i = 0, l = lst.size(); i < l; ++i) { diff --git a/patches/minecraft/net/minecraft/world/WorldServerMulti.edit.java b/patches/minecraft/net/minecraft/world/WorldServerMulti.edit.java index f8851de..c4b788e 100644 --- a/patches/minecraft/net/minecraft/world/WorldServerMulti.edit.java +++ b/patches/minecraft/net/minecraft/world/WorldServerMulti.edit.java @@ -14,4 +14,6 @@ ~ public WorldServerMulti(MinecraftServer server, ISaveHandler saveHandlerIn, int dimensionId, WorldServer delegate) { ~ super(server, saveHandlerIn, new DerivedWorldInfo(delegate.getWorldInfo()), dimensionId); +> DELETE 32 @ 32 : 35 + > EOF diff --git a/patches/minecraft/net/minecraft/world/chunk/storage/IChunkLoader.edit.java b/patches/minecraft/net/minecraft/world/chunk/storage/IChunkLoader.edit.java new file mode 100644 index 0000000..a81c606 --- /dev/null +++ b/patches/minecraft/net/minecraft/world/chunk/storage/IChunkLoader.edit.java @@ -0,0 +1,14 @@ + +# Eagler Context Redacted Diff +# Copyright (c) 2024 lax1dude. All rights reserved. + +# Version: 1.0 +# Author: lax1dude + +> DELETE 3 @ 3 : 4 + +> CHANGE 6 : 7 @ 6 : 7 + +~ void saveChunk(World var1, Chunk var2) throws IOException; + +> EOF diff --git a/patches/minecraft/net/minecraft/world/gen/ChunkProviderServer.edit.java b/patches/minecraft/net/minecraft/world/gen/ChunkProviderServer.edit.java index 9405f6c..b416766 100644 --- a/patches/minecraft/net/minecraft/world/gen/ChunkProviderServer.edit.java +++ b/patches/minecraft/net/minecraft/world/gen/ChunkProviderServer.edit.java @@ -9,7 +9,9 @@ + import net.lax1dude.eaglercraft.v1_8.sp.server.EaglerMinecraftServer; -> CHANGE 14 : 17 @ 14 : 16 +> DELETE 6 @ 6 : 7 + +> CHANGE 7 : 10 @ 7 : 9 ~ import net.lax1dude.eaglercraft.v1_8.HString; ~ import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; @@ -46,17 +48,14 @@ + ++EaglerMinecraftServer.counterChunkWrite; -> CHANGE 1 : 3 @ 1 : 2 +> CHANGE 1 : 3 @ 1 : 5 ~ logger.error("Couldn\'t save chunk"); ~ logger.error(ioexception); -> CHANGE 1 : 3 @ 1 : 3 +> DELETE 1 @ 1 : 2 -~ logger.error("Couldn\'t save chunk; already in use by another instance of Minecraft?"); -~ logger.error(minecraftexception); - -> CHANGE 31 : 32 @ 31 : 32 +> CHANGE 29 : 30 @ 29 : 30 ~ for (int j = 0, l = arraylist.size(); j < l; ++j) { diff --git a/patches/minecraft/net/minecraft/world/storage/ISaveHandler.edit.java b/patches/minecraft/net/minecraft/world/storage/ISaveHandler.edit.java index a592e56..6a76531 100644 --- a/patches/minecraft/net/minecraft/world/storage/ISaveHandler.edit.java +++ b/patches/minecraft/net/minecraft/world/storage/ISaveHandler.edit.java @@ -7,13 +7,15 @@ > DELETE 2 @ 2 : 3 -> INSERT 1 : 2 @ 1 +> CHANGE 1 : 2 @ 1 : 2 -+ import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFile2; +~ import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFile2; -> DELETE 3 @ 3 : 5 +> DELETE 2 @ 2 : 4 -> CHANGE 16 : 17 @ 16 : 17 +> DELETE 4 @ 4 : 6 + +> CHANGE 10 : 11 @ 10 : 11 ~ VFile2 getWorldDirectory(); diff --git a/patches/minecraft/net/minecraft/world/storage/SaveHandler.edit.java b/patches/minecraft/net/minecraft/world/storage/SaveHandler.edit.java index 26191af..bc69356 100644 --- a/patches/minecraft/net/minecraft/world/storage/SaveHandler.edit.java +++ b/patches/minecraft/net/minecraft/world/storage/SaveHandler.edit.java @@ -12,7 +12,9 @@ ~ import java.util.List; ~ -> CHANGE 7 : 11 @ 7 : 12 +> DELETE 4 @ 4 : 5 + +> CHANGE 2 : 6 @ 2 : 7 ~ import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFile2; ~ import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; @@ -40,9 +42,9 @@ ~ public VFile2 getWorldDirectory() { -> DELETE 4 @ 4 : 19 +> DELETE 3 @ 3 : 21 -> CHANGE 3 : 4 @ 3 : 4 +> CHANGE 1 : 2 @ 1 : 2 ~ throw new RuntimeException("eagler"); diff --git a/patches/minecraft/net/minecraft/world/storage/SaveHandlerMP.edit.java b/patches/minecraft/net/minecraft/world/storage/SaveHandlerMP.edit.java index 88dde83..8b1964f 100644 --- a/patches/minecraft/net/minecraft/world/storage/SaveHandlerMP.edit.java +++ b/patches/minecraft/net/minecraft/world/storage/SaveHandlerMP.edit.java @@ -9,9 +9,11 @@ ~ import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFile2; -> DELETE 4 @ 4 : 7 +> DELETE 1 @ 1 : 2 -> DELETE 9 @ 9 : 13 +> DELETE 2 @ 2 : 5 + +> DELETE 6 @ 6 : 13 > CHANGE 13 : 19 @ 13 : 14 diff --git a/patches/resources/assets/minecraft/lang/en_US.edit.lang b/patches/resources/assets/minecraft/lang/en_US.edit.lang index 2138769..a848f84 100644 --- a/patches/resources/assets/minecraft/lang/en_US.edit.lang +++ b/patches/resources/assets/minecraft/lang/en_US.edit.lang @@ -257,7 +257,7 @@ + eaglercraft.command.clientStub=This command is client side! + -> INSERT 163 : 561 @ 163 +> INSERT 163 : 568 @ 163 + eaglercraft.singleplayer.busy.killTask=Cancel Task + eaglercraft.singleplayer.busy.cancelWarning=Are you sure? @@ -657,6 +657,13 @@ + eaglercraft.options.badVideoSettingsDetected.continueAnyway=Continue Anyway + eaglercraft.options.badVideoSettingsDetected.doNotShowAgain=Do Not Show Again + ++ eaglercraft.options.vsyncReEnabled.title=Issues Detected ++ eaglercraft.options.vsyncReEnabled.0=You are using the WASM-GC client ++ eaglercraft.options.vsyncReEnabled.1=VSync has been automatically re-enabled ++ eaglercraft.options.vsyncReEnabled.2=Using the WASM-GC version of EaglercraftX without ++ eaglercraft.options.vsyncReEnabled.3=VSync enabled causes bad input lag, sorry! ++ eaglercraft.options.vsyncReEnabled.continue=Continue ++ > INSERT 18 : 19 @ 18 diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/DesktopClientConfigAdapter.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/DesktopClientConfigAdapter.java index ff9869c..4d82760 100644 --- a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/DesktopClientConfigAdapter.java +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/lwjgl/DesktopClientConfigAdapter.java @@ -198,6 +198,11 @@ public class DesktopClientConfigAdapter implements IClientConfigAdapter { return false; } + @Override + public boolean isEnforceVSync() { + return false; + } + @Override public IClientConfigAdapterHooks getHooks() { return hooks; diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/ServerPlatformSingleplayer.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/ServerPlatformSingleplayer.java index 073838a..e0e813d 100644 --- a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/ServerPlatformSingleplayer.java +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/ServerPlatformSingleplayer.java @@ -10,6 +10,7 @@ import net.lax1dude.eaglercraft.v1_8.internal.IEaglerFilesystem; import net.lax1dude.eaglercraft.v1_8.internal.IPCPacketData; import net.lax1dude.eaglercraft.v1_8.internal.PlatformWebRTC; import net.lax1dude.eaglercraft.v1_8.internal.lwjgl.DesktopClientConfigAdapter; +import net.lax1dude.eaglercraft.v1_8.sp.server.IWASMCrashCallback; import net.lax1dude.eaglercraft.v1_8.sp.server.internal.lwjgl.MemoryConnection; /** @@ -91,4 +92,8 @@ public class ServerPlatformSingleplayer { return false; } + public static void setCrashCallbackWASM(IWASMCrashCallback callback) { + + } + } diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/EagUtils.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/EagUtils.java index f19b97e..95f5b80 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/EagUtils.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/EagUtils.java @@ -99,4 +99,9 @@ public class EagUtils { return EaglercraftUUID.nameUUIDFromBytes(("EaglercraftXClientOld:" + name).getBytes(StandardCharsets.UTF_8)); } + public static void sleepPrint(String string) { + System.out.println(string); + PlatformRuntime.sleep(500); + } + } diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/EaglercraftVersion.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/EaglercraftVersion.java index f565593..1011c6d 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/EaglercraftVersion.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/EaglercraftVersion.java @@ -10,7 +10,7 @@ public class EaglercraftVersion { /// Customize these to fit your fork: public static final String projectForkName = "EaglercraftX"; - public static final String projectForkVersion = "u43"; + public static final String projectForkVersion = "u44"; public static final String projectForkVendor = "lax1dude"; public static final String projectForkURL = "https://gitlab.com/lax1dude/eaglercraftx-1.8"; @@ -20,20 +20,20 @@ public class EaglercraftVersion { public static final String projectOriginName = "EaglercraftX"; public static final String projectOriginAuthor = "lax1dude"; public static final String projectOriginRevision = "1.8"; - public static final String projectOriginVersion = "u43"; + public static final String projectOriginVersion = "u44"; public static final String projectOriginURL = "https://gitlab.com/lax1dude/eaglercraftx-1.8"; // rest in peace // EPK Version Identifier - public static final String EPKVersionIdentifier = "u43"; // Set to null to disable EPK version check + public static final String EPKVersionIdentifier = "u44"; // Set to null to disable EPK version check // Updating configuration public static final boolean enableUpdateService = true; public static final String updateBundlePackageName = "net.lax1dude.eaglercraft.v1_8.client"; - public static final int updateBundlePackageVersionInt = 43; + public static final int updateBundlePackageVersionInt = 44; public static final String updateLatestLocalStorageKey = "latestUpdate_" + updateBundlePackageName; @@ -52,8 +52,8 @@ public class EaglercraftVersion { // Miscellaneous variables: public static final String mainMenuStringA = "Minecraft 1.8.8"; - public static final String mainMenuStringB = projectOriginName + " " + - projectOriginRevision + "-" + projectOriginVersion + " ultimate"; + public static final String mainMenuStringB = projectOriginName + " " + projectOriginRevision + "-" + + projectOriginVersion + " ultimate [" + EagRuntime.getPlatformType().getName() + "]"; public static final String mainMenuStringC = ""; public static final String mainMenuStringD = "Resources Copyright Mojang AB"; @@ -63,6 +63,9 @@ public class EaglercraftVersion { public static final String mainMenuStringG = "Collector's Edition"; public static final String mainMenuStringH = "PBR Shaders"; + public static final String screenRecordingFilePrefix = projectOriginName + " " + + projectOriginRevision + "-" + projectOriginVersion; + public static final long demoWorldSeed = (long) "North Carolina".hashCode(); public static final boolean mainMenuEnableGithubButton = false; diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/internal/EnumPlatformType.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/internal/EnumPlatformType.java index e2c7bad..77fcf32 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/internal/EnumPlatformType.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/internal/EnumPlatformType.java @@ -16,7 +16,7 @@ package net.lax1dude.eaglercraft.v1_8.internal; * */ public enum EnumPlatformType { - DESKTOP("Desktop"), JAVASCRIPT("JavaScript"), WASM_GC("ASM"); + DESKTOP("Desktop"), JAVASCRIPT("JS"), WASM_GC("WASM-GC"); private final String name; diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/internal/IClientConfigAdapter.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/internal/IClientConfigAdapter.java index 0e2e5cb..799ee83 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/internal/IClientConfigAdapter.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/internal/IClientConfigAdapter.java @@ -98,6 +98,8 @@ public interface IClientConfigAdapter { boolean isRamdiskMode(); + boolean isEnforceVSync(); + IClientConfigAdapterHooks getHooks(); } diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/internal/vfs2/VFileOutputStream.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/internal/vfs2/VFileOutputStream.java index 8be7175..dcff029 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/internal/vfs2/VFileOutputStream.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/internal/vfs2/VFileOutputStream.java @@ -39,11 +39,9 @@ class VFileOutputStream extends EaglerOutputStream { try { copyBuffer.put(buf, 0, count); copyBuffer.flip(); - try { - vfsFile.getFS().eaglerWrite(vfsFile.path, copyBuffer); - }catch(Throwable t) { - throw new IOException("Could not write stream contents to file!", t); - } + vfsFile.getFS().eaglerWrite(vfsFile.path, copyBuffer); + }catch(Throwable t) { + throw new IOException("Could not write stream contents to file!", t); }finally { PlatformRuntime.freeByteBuffer(copyBuffer); } diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/log4j/Logger.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/log4j/Logger.java index f734f35..1052018 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/log4j/Logger.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/log4j/Logger.java @@ -159,6 +159,10 @@ public class Logger { } private void logExcp(final Level level, String h, Throwable msg) { + if(msg == null) { + log(level, "{}: ", h); + return; + } log(level, "{}: {}", h, msg.toString()); EagRuntime.getStackTrace(msg, (e) -> log(level, " at {}", e)); PlatformRuntime.printJSExceptionIfBrowser(msg); diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/minecraft/EaglerFontRenderer.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/minecraft/EaglerFontRenderer.java index 0e0f561..4f021fd 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/minecraft/EaglerFontRenderer.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/minecraft/EaglerFontRenderer.java @@ -189,11 +189,15 @@ public class EaglerFontRenderer extends FontRenderer { if(hasStrike) { GlStateManager.color(0.25f, 0.25f, 0.25f, 1.0f); GlStateManager.translate(1.0f, 1.0f, 0.0f); + GlStateManager.disableTexture2D(); tessellator.draw(); GlStateManager.translate(-1.0f, -1.0f, 0.0f); GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f); + GlStateManager.enableTexture2D(); InstancedFontRenderer.render(8, 8, texScale, texScale, true); + GlStateManager.disableTexture2D(); EaglercraftGPU.renderAgain(); + GlStateManager.enableTexture2D(); }else { GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f); InstancedFontRenderer.render(8, 8, texScale, texScale, true); @@ -201,7 +205,9 @@ public class EaglerFontRenderer extends FontRenderer { }else { GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f); if(hasStrike) { + GlStateManager.disableTexture2D(); tessellator.draw(); + GlStateManager.enableTexture2D(); } InstancedFontRenderer.render(8, 8, texScale, texScale, false); } diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/minecraft/GuiScreenVSyncReEnabled.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/minecraft/GuiScreenVSyncReEnabled.java new file mode 100644 index 0000000..353c5d3 --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/minecraft/GuiScreenVSyncReEnabled.java @@ -0,0 +1,51 @@ +package net.lax1dude.eaglercraft.v1_8.minecraft; + +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.resources.I18n; + +/** + * 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. + * + */ +public class GuiScreenVSyncReEnabled extends GuiScreen { + + private GuiScreen cont; + + public GuiScreenVSyncReEnabled(GuiScreen cont) { + this.cont = cont; + } + + public void initGui() { + this.buttonList.clear(); + this.buttonList.add(new GuiButton(0, this.width / 2 - 100, this.height / 6 + 136, I18n.format("options.vsyncReEnabled.continue"))); + } + + public void drawScreen(int par1, int par2, float par3) { + this.drawDefaultBackground(); + this.drawCenteredString(fontRendererObj, I18n.format("options.vsyncReEnabled.title"), this.width / 2, 70, 11184810); + this.drawCenteredString(fontRendererObj, I18n.format("options.vsyncReEnabled.0"), this.width / 2, 95, 16777215); + this.drawCenteredString(fontRendererObj, I18n.format("options.vsyncReEnabled.1"), this.width / 2, 120, 16777215); + this.drawCenteredString(fontRendererObj, I18n.format("options.vsyncReEnabled.2"), this.width / 2, 145, 16777215); + this.drawCenteredString(fontRendererObj, I18n.format("options.vsyncReEnabled.3"), this.width / 2, 160, 16777215); + super.drawScreen(par1, par2, par3); + } + + protected void actionPerformed(GuiButton par1GuiButton) { + if(par1GuiButton.id == 0) { + this.mc.displayGuiScreen(cont); + } + } + +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/socket/ServerQueryImpl.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/socket/ServerQueryImpl.java index 19c3715..21bc973 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/socket/ServerQueryImpl.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/socket/ServerQueryImpl.java @@ -65,7 +65,7 @@ class ServerQueryImpl implements IServerQuery { IWebSocketFrame frame = lst.get(i); alive = true; if(pingTimer == -1) { - pingTimer = PlatformRuntime.steadyTimeMillis() - pingStart; + pingTimer = frame.getTimestamp() - pingStart; if(pingTimer < 1) { pingTimer = 1; } diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/SingleplayerServerController.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/SingleplayerServerController.java index 7156bba..04b7a39 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/SingleplayerServerController.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/SingleplayerServerController.java @@ -15,6 +15,7 @@ import org.apache.commons.lang3.StringUtils; import net.lax1dude.eaglercraft.v1_8.EagRuntime; import net.lax1dude.eaglercraft.v1_8.internal.EnumEaglerConnectionState; +import net.lax1dude.eaglercraft.v1_8.internal.EnumPlatformType; import net.lax1dude.eaglercraft.v1_8.internal.IPCPacketData; import net.lax1dude.eaglercraft.v1_8.internal.PlatformApplication; import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; @@ -84,6 +85,7 @@ public class SingleplayerServerController implements ISaveFormat { issuesDetected.clear(); statusState = IntegratedServerState.WORLD_WORKER_BOOTING; loggingState = true; + callFailed = false; boolean singleThreadSupport = ClientPlatformSingleplayer.isSingleThreadModeSupported(); if(!singleThreadSupport && forceSingleThread) { throw new UnsupportedOperationException("Single thread mode is not supported!"); @@ -294,10 +296,12 @@ public class SingleplayerServerController implements ISaveFormat { } } - boolean logWindowState = PlatformApplication.isShowingDebugConsole(); - if(loggingState != logWindowState) { - loggingState = logWindowState; - sendIPCPacket(new IPCPacket1BEnableLogging(logWindowState)); + if(EagRuntime.getPlatformType() == EnumPlatformType.JAVASCRIPT) { + boolean logWindowState = PlatformApplication.isShowingDebugConsole(); + if(loggingState != logWindowState) { + loggingState = logWindowState; + sendIPCPacket(new IPCPacket1BEnableLogging(logWindowState)); + } } if(ClientPlatformSingleplayer.isRunningSingleThreadMode()) { diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/lan/LANClientPeer.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/lan/LANClientPeer.java index 902ccf3..356133e 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/lan/LANClientPeer.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/lan/LANClientPeer.java @@ -41,6 +41,8 @@ class LANClientPeer { protected long startTime; + protected String localICECandidate = null; + protected LANClientPeer(String clientId) { this.clientId = clientId; this.startTime = EagRuntime.steadyTimeMillis(); @@ -50,7 +52,13 @@ class LANClientPeer { protected void handleICECandidates(String candidates) { if(state == SENT_DESCRIPTION) { PlatformWebRTC.serverLANPeerICECandidates(clientId, candidates); - state = RECEIVED_ICE_CANDIDATE; + if(localICECandidate != null) { + LANServerController.lanRelaySocket.writePacket(new RelayPacket03ICECandidate(clientId, localICECandidate)); + localICECandidate = null; + state = SENT_ICE_CANDIDATE; + }else { + state = RECEIVED_ICE_CANDIDATE; + } }else { logger.error("Relay [{}] unexpected IPacket03ICECandidate for '{}'", LANServerController.lanRelaySocket.getURI(), clientId); } @@ -100,6 +108,12 @@ class LANClientPeer { disconnect(); }else { switch(state) { + case SENT_DESCRIPTION:{ + if(evt instanceof LANPeerEvent.LANPeerICECandidateEvent) { + localICECandidate = ((LANPeerEvent.LANPeerICECandidateEvent)evt).candidates; + continue read_loop; + } + } case RECEIVED_ICE_CANDIDATE: { if(evt instanceof LANPeerEvent.LANPeerICECandidateEvent) { LANServerController.lanRelaySocket.writePacket(new RelayPacket03ICECandidate(clientId, ((LANPeerEvent.LANPeerICECandidateEvent)evt).candidates)); @@ -136,7 +150,7 @@ class LANClientPeer { } } if(state != CLOSED) { - logger.error("LAN client '{}' had an accident: {}", clientId, evt.getClass().getSimpleName()); + logger.error("LAN client '{}' had an accident: {} (state {})", clientId, evt.getClass().getSimpleName(), state); } disconnect(); return; diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/lan/LANServerController.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/lan/LANServerController.java index 899bf94..4b2a5e1 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/lan/LANServerController.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/lan/LANServerController.java @@ -82,7 +82,7 @@ public class LANServerController { } } EagUtils.sleep(50); - }while(EagRuntime.steadyTimeMillis() - millis < 1000l); + }while(EagRuntime.steadyTimeMillis() - millis < 2500l); logger.info("Relay [{}] relay provide ICE servers timeout", sock.getURI()); closeLAN(); return null; diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerChunkLoader.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerChunkLoader.java index 11bd903..c8a8a9e 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerChunkLoader.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerChunkLoader.java @@ -8,7 +8,6 @@ import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFile2; import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; import net.lax1dude.eaglercraft.v1_8.log4j.Logger; import net.minecraft.world.ChunkCoordIntPair; -import net.minecraft.world.MinecraftException; import net.minecraft.world.World; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.storage.AnvilChunkLoader; @@ -88,7 +87,7 @@ public class EaglerChunkLoader extends AnvilChunkLoader { } @Override - public void saveChunk(World var1, Chunk var2) throws IOException, MinecraftException { + public void saveChunk(World var1, Chunk var2) throws IOException { NBTTagCompound chunkData = new NBTTagCompound(); this.writeChunkToNBT(var2, var1, chunkData); NBTTagCompound fileData = new NBTTagCompound(); diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerIntegratedServerWorker.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerIntegratedServerWorker.java index 532eb67..dab127d 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerIntegratedServerWorker.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/EaglerIntegratedServerWorker.java @@ -485,6 +485,8 @@ public class EaglerIntegratedServerWorker { // signal thread startup successful sendIPCPacket(new IPCPacketFFProcessKeepAlive(0xFF)); + ServerPlatformSingleplayer.setCrashCallbackWASM(EaglerIntegratedServerWorker::sendIntegratedServerCrashWASMCB); + while(true) { mainLoop(false); ServerPlatformSingleplayer.immediateContinue(); @@ -525,4 +527,11 @@ public class EaglerIntegratedServerWorker { mainLoop(true); } + public static void sendIntegratedServerCrashWASMCB(String stringValue, boolean terminated) { + sendIPCPacket(new IPCPacket15Crashed(stringValue)); + if(terminated) { + sendIPCPacket(new IPCPacketFFProcessKeepAlive(IPCPacketFFProcessKeepAlive.EXITED)); + } + } + } diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/IWASMCrashCallback.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/IWASMCrashCallback.java new file mode 100644 index 0000000..d538336 --- /dev/null +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/IWASMCrashCallback.java @@ -0,0 +1,22 @@ +package net.lax1dude.eaglercraft.v1_8.sp.server; + +/** + * 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. + * + */ +public interface IWASMCrashCallback { + + void callback(String crashReport, boolean terminated); + +} diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/voice/IntegratedVoiceService.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/voice/IntegratedVoiceService.java index f6a802a..9b82a66 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/voice/IntegratedVoiceService.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/sp/server/voice/IntegratedVoiceService.java @@ -139,14 +139,24 @@ public class IntegratedVoiceService { } public void handleVoiceSignalPacketTypeConnect(EntityPlayerMP sender) { - if (voicePlayers.containsKey(sender.getUniqueID())) { + EaglercraftUUID senderUuid = sender.getUniqueID(); + if (voicePlayers.containsKey(senderUuid)) { return; } boolean hasNoOtherPlayers = voicePlayers.isEmpty(); - voicePlayers.put(sender.getUniqueID(), sender); + voicePlayers.put(senderUuid, sender); if (hasNoOtherPlayers) { return; } + GameMessagePacket v3p = null; + GameMessagePacket v4p = null; + for(EntityPlayerMP conn : voicePlayers.values()) { + if(conn.playerNetServerHandler.getEaglerMessageProtocol().ver <= 3) { + conn.playerNetServerHandler.sendEaglerMessage(v3p == null ? (v3p = new SPacketVoiceSignalConnectV3EAG(senderUuid.msb, senderUuid.lsb, true, false)) : v3p); + } else { + conn.playerNetServerHandler.sendEaglerMessage(v4p == null ? (v4p = new SPacketVoiceSignalConnectAnnounceV4EAG(senderUuid.msb, senderUuid.lsb)) : v4p); + } + } Collection userDatas = new ArrayList<>(voicePlayers.size()); for(EntityPlayerMP player : voicePlayers.values()) { EaglercraftUUID uuid = player.getUniqueID(); diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/VoiceClientController.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/VoiceClientController.java index a6ea655..fd73588 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/VoiceClientController.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/voice/VoiceClientController.java @@ -94,13 +94,16 @@ public class VoiceClientController { } public static void handleVoiceSignalPacketTypeGlobalNew(Collection voicePlayers) { + boolean isGlobal = voiceChannel == EnumVoiceChannelType.GLOBAL; uuidToNameLookup.clear(); for (SPacketVoiceSignalGlobalEAG.UserData player : voicePlayers) { EaglercraftUUID uuid = new EaglercraftUUID(player.uuidMost, player.uuidLeast); if(player.username != null) { uuidToNameLookup.put(uuid, player.username); } - sendPacketRequestIfNeeded(uuid); + if (isGlobal) { + sendPacketRequestIfNeeded(uuid); + } } } @@ -138,7 +141,7 @@ public class VoiceClientController { } public static void handleVoiceSignalPacketTypeConnectAnnounce(EaglercraftUUID user) { - if (voiceChannel != EnumVoiceChannelType.NONE) sendPacketRequest(user); + if (voiceChannel != EnumVoiceChannelType.NONE && (voiceChannel == EnumVoiceChannelType.GLOBAL || listeningSet.contains(user))) sendPacketRequest(user); } public static void handleVoiceSignalPacketTypeDisconnect(EaglercraftUUID user) { diff --git a/sources/resources/EPKVersionIdentifier.txt b/sources/resources/EPKVersionIdentifier.txt index 4f77ea9..15a5b3c 100644 --- a/sources/resources/EPKVersionIdentifier.txt +++ b/sources/resources/EPKVersionIdentifier.txt @@ -1 +1 @@ -u43 \ No newline at end of file +u44 \ No newline at end of file diff --git a/sources/resources/assets/eagler/CREDITS.txt b/sources/resources/assets/eagler/CREDITS.txt index 5a34b1d..7460d54 100644 --- a/sources/resources/assets/eagler/CREDITS.txt +++ b/sources/resources/assets/eagler/CREDITS.txt @@ -785,3 +785,78 @@ * THE SOFTWARE. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + Project Name: Emscripten + Project Author: Emscripten authors + Project URL: https://emscripten.org/ + + Used For: Compiling the WASM runtime's loader.wasm program + + * Emscripten is available under 2 licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. + * + * Copyright (c) 2010-2014 Emscripten authors, see AUTHORS file. + * + * 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. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + Project Name: XZ Embedded + Project Author: Lasse Collin (Larhzu) + Project URL: https://tukaani.org/xz/embedded.html + + Used For: Decompressing the WASM runtime + + * Copyright (C) The XZ Embedded authors and contributors + * + * Permission to use, copy, modify, and/or distribute this + * software for any purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + Project Name: XZ for Java + Project Author: Lasse Collin (Larhzu) + Project URL: https://tukaani.org/xz/java.html + + Used For: Compression in the MakeWASMClientBundle command + + * Copyright (C) The XZ for Java authors and contributors + * + * Permission to use, copy, modify, and/or distribute this + * software for any purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/sources/resources/plugin_download.zip b/sources/resources/plugin_download.zip index 3da750a..0db389f 100644 Binary files a/sources/resources/plugin_download.zip and b/sources/resources/plugin_download.zip differ diff --git a/sources/resources/plugin_version.json b/sources/resources/plugin_version.json index ae3a168..d5b0c6f 100644 --- a/sources/resources/plugin_version.json +++ b/sources/resources/plugin_version.json @@ -1 +1 @@ -{"pluginName":"EaglercraftXBungee","pluginVersion":"1.3.3","pluginButton":"Download \"EaglerXBungee-1.3.3.jar\"","pluginFilename":"EaglerXBungee.zip"} \ No newline at end of file +{"pluginName":"EaglercraftXBungee","pluginVersion":"1.3.4","pluginButton":"Download \"EaglerXBungee-1.3.4.jar\"","pluginFilename":"EaglerXBungee.zip"} \ No newline at end of file diff --git a/sources/setup/workspace_template/wasm_gc_teavm/.gitignore.default b/sources/setup/workspace_template/wasm_gc_teavm/.gitignore.default new file mode 100644 index 0000000..7dcbbfe --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/.gitignore.default @@ -0,0 +1,12 @@ +.gradle +.settings +.classpath +.project +build +bin +javascript/eagruntime.js +javascript/classes.wasm +javascript/classes.wasm.teadbg +javascript/assets.epk +javascript_dist/assets.epw +javascript_dist/EaglercraftX_1.8_* diff --git a/sources/setup/workspace_template/wasm_gc_teavm/CompileBootstrapJS.bat b/sources/setup/workspace_template/wasm_gc_teavm/CompileBootstrapJS.bat new file mode 100644 index 0000000..81490cc --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/CompileBootstrapJS.bat @@ -0,0 +1,6 @@ +@echo off +title CompileEPWBootstrapJS +set srcFolder=../src/wasm-gc-teavm-bootstrap/js +echo Compiling %srcFolder% +java -jar buildtools/closure-compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --assume_function_wrapper --emit_use_strict --isolation_mode IIFE --js "%srcFolder%/externs.js" "%srcFolder%/main.js" --js_output_file javascript_dist/bootstrap.js +pause \ No newline at end of file diff --git a/sources/setup/workspace_template/wasm_gc_teavm/CompileBootstrapJS.sh b/sources/setup/workspace_template/wasm_gc_teavm/CompileBootstrapJS.sh new file mode 100644 index 0000000..7ebd609 --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/CompileBootstrapJS.sh @@ -0,0 +1,4 @@ +#!/bin/sh +srcFolder=../src/wasm-gc-teavm-bootstrap/js +echo Compiling $srcFolder +java -jar buildtools/closure-compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --assume_function_wrapper --emit_use_strict --isolation_mode IIFE --js $srcFolder/externs.js $srcFolder/main.js --js_output_file javascript_dist/bootstrap.js diff --git a/sources/setup/workspace_template/wasm_gc_teavm/CompileEPK.bat b/sources/setup/workspace_template/wasm_gc_teavm/CompileEPK.bat new file mode 100644 index 0000000..5d0906b --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/CompileEPK.bat @@ -0,0 +1,6 @@ +@echo off +title epkcompiler +echo compiling, please wait... +java -jar "../desktopRuntime/CompileEPK.jar" "../desktopRuntime/resources" "javascript/assets.epk" none +echo finished compiling epk +pause \ No newline at end of file diff --git a/sources/setup/workspace_template/wasm_gc_teavm/CompileEPK.sh b/sources/setup/workspace_template/wasm_gc_teavm/CompileEPK.sh new file mode 100644 index 0000000..9537ae2 --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/CompileEPK.sh @@ -0,0 +1,2 @@ +#!/bin/sh +java -jar "../desktopRuntime/CompileEPK.jar" "../desktopRuntime/resources" "javascript/assets.epk" none \ No newline at end of file diff --git a/sources/setup/workspace_template/wasm_gc_teavm/CompileEagRuntimeJS.bat b/sources/setup/workspace_template/wasm_gc_teavm/CompileEagRuntimeJS.bat new file mode 100644 index 0000000..8fa5ed6 --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/CompileEagRuntimeJS.bat @@ -0,0 +1,6 @@ +@echo off +title CompileEagRuntimeJS +set srcFolder=../src/wasm-gc-teavm/js +echo Compiling %srcFolder% +java -jar buildtools/closure-compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --assume_function_wrapper --emit_use_strict --isolation_mode IIFE --js "%srcFolder%/externs.js" "%srcFolder%/eagruntime_util.js" "%srcFolder%/eagruntime_main.js" "%srcFolder%/platformApplication.js" "%srcFolder%/platformAssets.js" "%srcFolder%/platformAudio.js" "%srcFolder%/platformFilesystem.js" "%srcFolder%/platformInput.js" "%srcFolder%/platformNetworking.js" "%srcFolder%/platformOpenGL.js" "%srcFolder%/platformRuntime.js" "%srcFolder%/platformScreenRecord.js" "%srcFolder%/platformVoiceClient.js" "%srcFolder%/platformWebRTC.js" "%srcFolder%/platformWebView.js" "%srcFolder%/clientPlatformSingleplayer.js" "%srcFolder%/serverPlatformSingleplayer.js" "%srcFolder%/WASMGCBufferAllocator.js" "%srcFolder%/fix-webm-duration.js" "%srcFolder%/teavm_runtime.js" "%srcFolder%/eagruntime_entrypoint.js" --js_output_file javascript/eagruntime.js +pause \ No newline at end of file diff --git a/sources/setup/workspace_template/wasm_gc_teavm/CompileEagRuntimeJS.sh b/sources/setup/workspace_template/wasm_gc_teavm/CompileEagRuntimeJS.sh new file mode 100644 index 0000000..968ba83 --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/CompileEagRuntimeJS.sh @@ -0,0 +1,4 @@ +#!/bin/sh +srcFolder=../src/wasm-gc-teavm/js +echo Compiling $srcFolder +java -jar buildtools/closure-compiler.jar --compilation_level ADVANCED_OPTIMIZATIONS --assume_function_wrapper --emit_use_strict --isolation_mode IIFE --js $srcFolder/externs.js $srcFolder/eagruntime_util.js $srcFolder/eagruntime_main.js $srcFolder/platformApplication.js $srcFolder/platformAssets.js $srcFolder/platformAudio.js $srcFolder/platformFilesystem.js $srcFolder/platformInput.js $srcFolder/platformNetworking.js $srcFolder/platformOpenGL.js $srcFolder/platformRuntime.js $srcFolder/platformScreenRecord.js $srcFolder/platformVoiceClient.js $srcFolder/platformWebRTC.js $srcFolder/platformWebView.js $srcFolder/clientPlatformSingleplayer.js $srcFolder/serverPlatformSingleplayer.js $srcFolder/WASMGCBufferAllocator.js $srcFolder/fix-webm-duration.js $srcFolder/teavm_runtime.js $srcFolder/eagruntime_entrypoint.js --js_output_file javascript/eagruntime.js diff --git a/sources/setup/workspace_template/wasm_gc_teavm/CompileLoaderWASM.bat b/sources/setup/workspace_template/wasm_gc_teavm/CompileLoaderWASM.bat new file mode 100644 index 0000000..86c08b2 --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/CompileLoaderWASM.bat @@ -0,0 +1,9 @@ +@echo off +title CompileLoaderWASM +mkdir "bin/emscripten" +call emcc -c -O3 ../src/wasm-gc-teavm-loader/c/main.c -o bin/emscripten/main.o +call emcc -c -O3 ../src/wasm-gc-teavm-loader/c/xz/xz_crc32.c -o bin/emscripten/xz_crc32.o +call emcc -c -O3 ../src/wasm-gc-teavm-loader/c/xz/xz_dec_lzma2.c -o bin/emscripten/xz_dec_lzma2.o +call emcc -c -O3 ../src/wasm-gc-teavm-loader/c/xz/xz_dec_stream.c -o bin/emscripten/xz_dec_stream.o +call emcc -O3 -sMALLOC=dlmalloc -sALLOW_MEMORY_GROWTH -sINITIAL_HEAP=16777216 -sMAXIMUM_MEMORY=67108864 --pre-js ../src/wasm-gc-teavm-loader/js/pre.js --js-library ../src/wasm-gc-teavm-loader/js/library.js bin/emscripten/main.o bin/emscripten/xz_crc32.o bin/emscripten/xz_dec_lzma2.o bin/emscripten/xz_dec_stream.o -o javascript/loader.js +pause \ No newline at end of file diff --git a/sources/setup/workspace_template/wasm_gc_teavm/CompileLoaderWASM.sh b/sources/setup/workspace_template/wasm_gc_teavm/CompileLoaderWASM.sh new file mode 100644 index 0000000..aa7b022 --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/CompileLoaderWASM.sh @@ -0,0 +1,7 @@ +#!/bin/sh +mkdir -p bin/emscripten +emcc -c -O3 ../src/wasm-gc-teavm-loader/c/main.c -o bin/emscripten/main.o +emcc -c -O3 ../src/wasm-gc-teavm-loader/c/xz/xz_crc32.c -o bin/emscripten/xz_crc32.o +emcc -c -O3 ../src/wasm-gc-teavm-loader/c/xz/xz_dec_lzma2.c -o bin/emscripten/xz_dec_lzma2.o +emcc -c -O3 ../src/wasm-gc-teavm-loader/c/xz/xz_dec_stream.c -o bin/emscripten/xz_dec_stream.o +emcc -O3 -sMALLOC=dlmalloc -sALLOW_MEMORY_GROWTH -sINITIAL_HEAP=16777216 -sMAXIMUM_MEMORY=67108864 --pre-js ../src/wasm-gc-teavm-loader/js/pre.js --js-library ../src/wasm-gc-teavm-loader/js/library.js bin/emscripten/main.o bin/emscripten/xz_crc32.o bin/emscripten/xz_dec_lzma2.o bin/emscripten/xz_dec_stream.o -o javascript/loader.js diff --git a/sources/setup/workspace_template/wasm_gc_teavm/CompileWASM.bat b/sources/setup/workspace_template/wasm_gc_teavm/CompileWASM.bat new file mode 100644 index 0000000..90a8275 --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/CompileWASM.bat @@ -0,0 +1,4 @@ +@echo off +title gradlew generateWasmGC +call gradlew generateWasmGC +pause \ No newline at end of file diff --git a/sources/setup/workspace_template/wasm_gc_teavm/CompileWASM.sh b/sources/setup/workspace_template/wasm_gc_teavm/CompileWASM.sh new file mode 100644 index 0000000..705b08d --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/CompileWASM.sh @@ -0,0 +1,3 @@ +#!/bin/sh +chmod +x gradlew +./gradlew generateWasmGC diff --git a/sources/setup/workspace_template/wasm_gc_teavm/MakeWASMClientBundle.bat b/sources/setup/workspace_template/wasm_gc_teavm/MakeWASMClientBundle.bat new file mode 100644 index 0000000..9eae281 --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/MakeWASMClientBundle.bat @@ -0,0 +1,5 @@ +@echo off +title MakeWASMClientBundle +cd javascript +java -cp "../buildtools/org.tukanni.xz.jar;../buildtools/MakeWASMClientBundle.jar;../../desktopRuntime/CompileEPK.jar;../../desktopRuntime/MakeOfflineDownload.jar" net.lax1dude.eaglercraft.v1_8.buildtools.workspace.MakeWASMClientBundle epw_src.txt epw_meta.txt "../javascript_dist" +pause \ No newline at end of file diff --git a/sources/setup/workspace_template/wasm_gc_teavm/MakeWASMClientBundle.sh b/sources/setup/workspace_template/wasm_gc_teavm/MakeWASMClientBundle.sh new file mode 100644 index 0000000..e577738 --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/MakeWASMClientBundle.sh @@ -0,0 +1,3 @@ +#!/bin/sh +cd javascript +java -cp "../buildtools/org.tukanni.xz.jar:../buildtools/MakeWASMClientBundle.jar:../../desktopRuntime/CompileEPK.jar:../../desktopRuntime/MakeOfflineDownload.jar" net.lax1dude.eaglercraft.v1_8.buildtools.workspace.MakeWASMClientBundle epw_src.txt epw_meta.txt "../javascript_dist" diff --git a/sources/setup/workspace_template/wasm_gc_teavm/README.md b/sources/setup/workspace_template/wasm_gc_teavm/README.md new file mode 100644 index 0000000..298a4b5 --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/README.md @@ -0,0 +1,25 @@ + +# EaglercraftX WASM-GC Runtime + +This folder contains the Gradle project for compiling the EaglercraftX 1.8 client to WASM. This requires a special fork of TeaVM that has been modified for Eaglercraft. The `settings.gradle` and `build.gradle` are set up to download the binaries automatically but if you would like to build the TeaVM fork yourself you can use the TeaVM fork's `publishToMavenLocal` gradle task and replace the URL maven repository declarations in the gradle build scripts with `mavenLocal()` instead. + +**TeaVM Fork: [https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm/tree/eagler-r1](https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm/tree/eagler-r1)** + +### To compile the client: +1. Run `CompileEPK` to compile the assets.epk file +2. Run `CompileWASM` to compile the classes.wasm file +3. Run `CompileEagRuntimeJS` to compile the eagruntime.js file +4. Run `MakeWASMClientBundle` to bundle the client into an EPW file + +The final assets.epw and offline download will be in the "javascript_dist" folder + +### Optional Steps: +- Run `CompileBootstrapJS` to recompile bootstrap.js in the javascript_dist folder +- Run `CompileLoaderWASM`to recompile loader.js and loader.wasm (requires emscripten) + +### Potential issues when porting: +- Disabling VSync causes bad input lag, the solution to this problem is to remove the vsync option and force people to play with vsync enabled, like all other browser games +- TeaVM's WASM GC backend is still under development and will sometimes generate broken code with nested try/finally statements in a try/catch block, or other strange runtime glitches +- Fewer reflection features are supported in WASM GC than the JavaScript backend (so far) +- Do not use `@Async` or any sort of callback (like addEventListener) in your Java, you must implement async functions in JavaScript in `../src/wasm-gc-teavm/js`, using JSPI to suspend/resume the thread for promises, or by pushing events into a queue that you can poll from your Java for event handlers. +- Functions imported via the `@Import` will not catch exceptions, if you want proper exception handling you must call the imported function through the JSO diff --git a/sources/setup/workspace_template/wasm_gc_teavm/build.gradle b/sources/setup/workspace_template/wasm_gc_teavm/build.gradle new file mode 100644 index 0000000..5c1ce33 --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/build.gradle @@ -0,0 +1,56 @@ +import org.teavm.gradle.api.OptimizationLevel +import org.teavm.gradle.api.WasmDebugInfoLocation +import org.teavm.gradle.api.WasmDebugInfoLevel + +plugins { + id "java" + id "eclipse" + id "org.teavm" version "0.11.0-EAGLER-R1" +} + +sourceSets { + main { + java { + srcDirs( + "../src/main/java", + "../src/game/java", + "../src/protocol-game/java", + "../src/protocol-relay/java", + "../src/wasm-gc-teavm/java" + ) + } + } + +} + +repositories { + maven { + url = uri("https://eaglercraft-teavm-fork.github.io/maven/") + } + mavenCentral() +} + +dependencies { + compileOnly "org.teavm:teavm-core:0.11.0-EAGLER-R1" // workaround for a few hacks +} + +def folder = "javascript" +def name = "classes.wasm" + +teavm.wasmGC { + targetFileName = "../" + name + optimization = OptimizationLevel.AGGRESSIVE + outOfProcess = false + fastGlobalAnalysis = false + processMemory = 512 + mainClass = "net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.MainClass" + outputDir = file(folder) + properties = [ "java.util.TimeZone.autodetect": "true" ] + debugInformation = true + debugInfoLocation = WasmDebugInfoLocation.EXTERNAL; + debugInfoLevel = WasmDebugInfoLevel.DEOBFUSCATION; + directMallocSupport = true + minHeapSize = 32 + maxHeapSize = 384 + disassembly = true +} diff --git a/sources/setup/workspace_template/wasm_gc_teavm/buildtools/MakeWASMClientBundle.jar b/sources/setup/workspace_template/wasm_gc_teavm/buildtools/MakeWASMClientBundle.jar new file mode 100644 index 0000000..6a1c494 Binary files /dev/null and b/sources/setup/workspace_template/wasm_gc_teavm/buildtools/MakeWASMClientBundle.jar differ diff --git a/sources/setup/workspace_template/wasm_gc_teavm/buildtools/closure-compiler.jar b/sources/setup/workspace_template/wasm_gc_teavm/buildtools/closure-compiler.jar new file mode 100644 index 0000000..1025f31 Binary files /dev/null and b/sources/setup/workspace_template/wasm_gc_teavm/buildtools/closure-compiler.jar differ diff --git a/sources/setup/workspace_template/wasm_gc_teavm/buildtools/org.tukanni.xz.jar b/sources/setup/workspace_template/wasm_gc_teavm/buildtools/org.tukanni.xz.jar new file mode 100644 index 0000000..a641a8b Binary files /dev/null and b/sources/setup/workspace_template/wasm_gc_teavm/buildtools/org.tukanni.xz.jar differ diff --git a/sources/setup/workspace_template/wasm_gc_teavm/gradle.properties b/sources/setup/workspace_template/wasm_gc_teavm/gradle.properties new file mode 100644 index 0000000..578eae0 --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx4G -Xms4G \ No newline at end of file diff --git a/sources/setup/workspace_template/wasm_gc_teavm/gradle/wrapper/gradle-wrapper.jar b/sources/setup/workspace_template/wasm_gc_teavm/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7f93135 Binary files /dev/null and b/sources/setup/workspace_template/wasm_gc_teavm/gradle/wrapper/gradle-wrapper.jar differ diff --git a/sources/setup/workspace_template/wasm_gc_teavm/gradle/wrapper/gradle-wrapper.properties b/sources/setup/workspace_template/wasm_gc_teavm/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e6aba25 --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/sources/setup/workspace_template/wasm_gc_teavm/gradlew b/sources/setup/workspace_template/wasm_gc_teavm/gradlew new file mode 100644 index 0000000..1aa94a4 --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# 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 +# +# https://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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/sources/setup/workspace_template/wasm_gc_teavm/gradlew.bat b/sources/setup/workspace_template/wasm_gc_teavm/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/sources/setup/workspace_template/wasm_gc_teavm/javascript/OfflineDownloadTemplate.txt b/sources/setup/workspace_template/wasm_gc_teavm/javascript/OfflineDownloadTemplate.txt new file mode 100644 index 0000000..560bb1c --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/javascript/OfflineDownloadTemplate.txt @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + +EaglercraftX 1.8 WASM-GC + + + + + + + + + + +
+
+

This file is from ${date}

+

Game will launch in 5...

+
+

+
+
+ + diff --git a/sources/setup/workspace_template/wasm_gc_teavm/javascript/classes.wasm-deobfuscator.wasm b/sources/setup/workspace_template/wasm_gc_teavm/javascript/classes.wasm-deobfuscator.wasm new file mode 100644 index 0000000..64aa55a Binary files /dev/null and b/sources/setup/workspace_template/wasm_gc_teavm/javascript/classes.wasm-deobfuscator.wasm differ diff --git a/sources/setup/workspace_template/wasm_gc_teavm/javascript/crashLogo.png b/sources/setup/workspace_template/wasm_gc_teavm/javascript/crashLogo.png new file mode 100644 index 0000000..0daab99 Binary files /dev/null and b/sources/setup/workspace_template/wasm_gc_teavm/javascript/crashLogo.png differ diff --git a/sources/setup/workspace_template/wasm_gc_teavm/javascript/enableJSPIScreen.html b/sources/setup/workspace_template/wasm_gc_teavm/javascript/enableJSPIScreen.html new file mode 100644 index 0000000..278002d --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/javascript/enableJSPIScreen.html @@ -0,0 +1,204 @@ + + + + + + JSPI is not enabled! + + + + +

Error: JSPI is not enabled!

+

Chrome, Edge, and Firefox all support JSPI (WebAssembly JavaScript Promise Integration), however it is not enabled by default. You can manually enable it yourself through your browser's flags, or if you are the owner of this website you can request an origin trial from Google and Microsoft to enable it automatically for all of your visitors.

+
+ +
+
+

Step 1

+

On a new tab, enter chrome://flags/

+

+

Step 2

+

Search for "JSPI", there should be an "Experimental WebAssembly JavaScript Promise Integration" option available

+

+

Step 3

+

Make sure its enabled

+

+

Step 4

+

Relauch chrome (if required)

+

+

Other Notes

+

The option may just be called "Experimental WebAssembly" on older versions or forks of Google Chrome

+

If you own a website, you can request an origin trial from Google to enable JSPI for visitors automatically

+
+ + +
+
+ + \ No newline at end of file diff --git a/sources/setup/workspace_template/wasm_gc_teavm/javascript/epw_meta.txt b/sources/setup/workspace_template/wasm_gc_teavm/javascript/epw_meta.txt new file mode 100644 index 0000000..75a2330 --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/javascript/epw_meta.txt @@ -0,0 +1,8 @@ +client-version-integer=44 +client-package-name=net.lax1dude.eaglercraft.v1_8.client +client-origin-name=EaglercraftX +client-origin-version=u44 +client-origin-vendor=lax1dude +client-fork-name=EaglercraftX +client-fork-version=u44 +client-fork-vendor=lax1dude diff --git a/sources/setup/workspace_template/wasm_gc_teavm/javascript/epw_src.txt b/sources/setup/workspace_template/wasm_gc_teavm/javascript/epw_src.txt new file mode 100644 index 0000000..4853e1c --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/javascript/epw_src.txt @@ -0,0 +1,22 @@ +loader-js-file=loader.js +loader-wasm-file=loader.wasm +eagruntime-js-file=eagruntime.js +classes-wasm-file=classes.wasm +classes-deobf-teadbg-file=classes.wasm.teadbg +classes-deobf-wasm-file=classes.wasm-deobfuscator.wasm +assets-epk-0-file=assets.epk +assets-epk-0-path=/ +assets-epk-1-file=../../javascript/lang +assets-epk-1-path=/assets/minecraft/lang/ +splash-logo-image-file=splash.png +splash-logo-image-mime=image/png +press-any-key-image-file=pressAnyKey.png +press-any-key-image-mime=image/png +crash-logo-image-file=crashLogo.png +crash-logo-image-mime=image/png +favicon-image-file=favicon.png +favicon-image-mime=image/png +jspi-unavailable-file=enableJSPIScreen.html +offline-download-template=OfflineDownloadTemplate.txt +offline-download-script=../javascript_dist/bootstrap.js +offline-download-name=EaglercraftX_1.8_WASM-GC_Offline_Download.html diff --git a/sources/setup/workspace_template/wasm_gc_teavm/javascript/favicon.png b/sources/setup/workspace_template/wasm_gc_teavm/javascript/favicon.png new file mode 100644 index 0000000..9c9fc89 Binary files /dev/null and b/sources/setup/workspace_template/wasm_gc_teavm/javascript/favicon.png differ diff --git a/sources/setup/workspace_template/wasm_gc_teavm/javascript/index.html b/sources/setup/workspace_template/wasm_gc_teavm/javascript/index.html new file mode 100644 index 0000000..17fe28a --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/javascript/index.html @@ -0,0 +1,120 @@ + + + + + + + + EaglercraftX 1.8 WASM-GC + + + + + + + + + \ No newline at end of file diff --git a/sources/setup/workspace_template/wasm_gc_teavm/javascript/loader.js b/sources/setup/workspace_template/wasm_gc_teavm/javascript/loader.js new file mode 100644 index 0000000..312dc73 --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/javascript/loader.js @@ -0,0 +1 @@ +var Module=typeof Module!="undefined"?Module:{};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof WorkerGlobalScope!="undefined";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string"&&process.type!="renderer";if(ENVIRONMENT_IS_NODE){}Module["locateFile"]=function(path){if(path==="loader.wasm"){return window.__eaglercraftXLoaderContextPre.loaderWASMURL}else{return path}};const rootElement=window.__eaglercraftXLoaderContextPre.rootElement;const optsObj=window.__eaglercraftXLoaderContextPre.eaglercraftXOpts;const epwFile=window.__eaglercraftXLoaderContextPre.theEPWFileBuffer;const splashURL=window.__eaglercraftXLoaderContextPre.splashURL;const results=[null];function createCrashParentElement(){var oldSplash=null;var node;while(node=rootElement.lastChild){if(!oldSplash){oldSplash=node}rootElement.removeChild(node)}const parentElement=document.createElement("div");parentElement.classList.add("_eaglercraftX_wrapper_element");parentElement.setAttribute("style","position:relative;width:100%;height:100%;overflow:hidden;");if(oldSplash){oldSplash.classList.add("_eaglercraftX_early_splash_element");oldSplash.style.position="absolute";oldSplash.style.top="0px";oldSplash.style.left="0px";oldSplash.style.right="0px";oldSplash.style.bottom="0px";oldSplash.style.zIndex="2";parentElement.appendChild(oldSplash)}rootElement.appendChild(parentElement);return parentElement}var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("fs");var nodePath=require("path");scriptDirectory=__dirname+"/";readBinary=filename=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);var ret=fs.readFileSync(filename);return ret};readAsync=(filename,binary=true)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);return new Promise((resolve,reject)=>{fs.readFile(filename,binary?undefined:"utf8",(err,data)=>{if(err)reject(err);else resolve(binary?data.buffer:data)})})};if(!Module["thisProgram"]&&process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);if(typeof module!="undefined"){module["exports"]=Module}quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(scriptDirectory.startsWith("blob:")){scriptDirectory=""}else{scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=url=>{if(isFileURI(url)){return new Promise((resolve,reject)=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){resolve(xhr.response);return}reject(xhr.status)};xhr.onerror=reject;xhr.send(null)})}return fetch(url,{credentials:"same-origin"}).then(response=>{if(response.ok){return response.arrayBuffer()}return Promise.reject(new Error(response.status+" : "+response.url))})}}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];var wasmBinary=Module["wasmBinary"];var wasmMemory;var ABORT=false;var EXITSTATUS;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;callRuntimeCallbacks(__ATINIT__)}function preMain(){callRuntimeCallbacks(__ATMAIN__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function addRunDependency(id){runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)}function removeRunDependency(id){runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);throw e}var dataURIPrefix="data:application/octet-stream;base64,";var isDataURI=filename=>filename.startsWith(dataURIPrefix);var isFileURI=filename=>filename.startsWith("file://");function findWasmBinary(){var f="loader.wasm";if(!isDataURI(f)){return locateFile(f)}return f}var wasmBinaryFile;function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}function getBinaryPromise(binaryFile){if(!wasmBinary){return readAsync(binaryFile).then(response=>new Uint8Array(response),()=>getBinarySync(binaryFile))}return Promise.resolve().then(()=>getBinarySync(binaryFile))}function instantiateArrayBuffer(binaryFile,imports,receiver){return getBinaryPromise(binaryFile).then(binary=>WebAssembly.instantiate(binary,imports)).then(receiver,reason=>{err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)})}function instantiateAsync(binary,binaryFile,imports,callback){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(binaryFile)&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE&&typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{var result=WebAssembly.instantiateStreaming(response,imports);return result.then(callback,function(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(binaryFile,imports,callback)})})}return instantiateArrayBuffer(binaryFile,imports,callback)}function getWasmImports(){return{a:wasmImports}}function createWasm(){function receiveInstance(instance,module){wasmExports=instance.exports;wasmMemory=wasmExports["o"];updateMemoryViews();addOnInit(wasmExports["p"]);removeRunDependency("wasm-instantiate");return wasmExports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}var info=getWasmImports();if(Module["instantiateWasm"]){try{return Module["instantiateWasm"](info,receiveInstance)}catch(e){err(`Module.instantiateWasm callback failed with error: ${e}`);return false}}wasmBinaryFile??=findWasmBinary();instantiateAsync(wasmBinary,wasmBinaryFile,info,receiveInstantiationResult);return{}}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};var noExitRuntime=Module["noExitRuntime"]||true;var __emscripten_memcpy_js=(dest,src,num)=>HEAPU8.copyWithin(dest,src,src+num);var UTF8Decoder=typeof TextDecoder!="undefined"?new TextDecoder:undefined;var UTF8ArrayToString=(heapOrArray,idx=0,maxBytesToRead=NaN)=>{var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};var UTF8ToString=(ptr,maxBytesToRead)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):"";function _dbgErr(msg){console.error("LoaderMain: [ERROR] "+UTF8ToString(msg))}function _dbgLog(msg){console.log("LoaderMain: [INFO] "+UTF8ToString(msg))}var getHeapMax=()=>67108864;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var b=wasmMemory.buffer;var pages=(size-b.byteLength+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};function _getEPWLength(){return epwFile.byteLength}function _getJSPISupported(){return typeof WebAssembly.Suspending!=="undefined"}function _initEPWStringResult(off,len){const id=results.length;results.push(UTF8Decoder.decode(new Uint8Array(epwFile,off,len)));return id}function _initResult(bufLen){const id=results.length;results.push(new Uint8Array(bufLen));return id}function _memcpyFromEPW(dest,off,len){HEAPU8.set(new Uint8Array(epwFile,off,len),dest)}function _memcpyFromEPWToResult(bufId,dest,off,len){results[bufId].set(new Uint8Array(epwFile,off,len),dest)}function _memcpyToResult(bufId,src,off,len){results[bufId].set(new Uint8Array(HEAPU8.buffer,src,len),off)}function _resultFailed(msg){const parentElement=createCrashParentElement();const messageContainer=document.createElement("div");messageContainer.setAttribute("style","z-index:100;position:absolute;top:10%;left:10%;right:10%;bottom:10%;background-color:white;border:2px solid #cccccc;overflow-x:hidden;overflow-y:scroll;");messageContainer.classList.add("_eaglercraftX_loader_failed_container");const failMsg=UTF8ToString(msg);console.error("LoaderMain: [FAILED] "+failMsg);const failureMsgElement=document.createElement("h2");failureMsgElement.style.color="#AA0000";failureMsgElement.style.padding="25px";failureMsgElement.style.fontFamily="sans-serif";failureMsgElement.style["marginBlock"]="0px";failureMsgElement.appendChild(document.createTextNode(failMsg));messageContainer.appendChild(failureMsgElement);const failureMsgElement2=document.createElement("h4");failureMsgElement2.style.color="#AA0000";failureMsgElement2.style.padding="25px";failureMsgElement2.style.fontFamily="sans-serif";failureMsgElement2.style["marginBlock"]="0px";failureMsgElement2.appendChild(document.createTextNode("Try again later"));messageContainer.appendChild(failureMsgElement2);parentElement.appendChild(messageContainer)}function _resultJSPIUnsupported(result){const idx=result>>2;const crashImgData=results[HEAP32[idx]];const crashImgMIME=results[HEAP32[idx+1]];const crashImg=crashImgData?URL.createObjectURL(new Blob([crashImgData],{type:crashImgMIME||"image/png"})):null;const markupData=results[HEAP32[idx+2]];const markup=markupData?UTF8Decoder.decode(markupData):"

Failed to load error screen

";const parentElement=createCrashParentElement();const img=document.createElement("img");img.setAttribute("style","z-index:100;position:absolute;top:10px;left:calc(50% - 151px);");img.src=crashImg;parentElement.appendChild(img);const iframeContainer=document.createElement("div");iframeContainer.setAttribute("style","z-index:100;position:absolute;top:135px;left:10%;right:10%;bottom:50px;background-color:white;border:2px solid #cccccc;");iframeContainer.classList.add("_eaglercraftX_jspi_unsupported_container");const iframe=document.createElement("iframe");iframe.classList.add("_eaglercraftX_jspi_unsupported_frame");iframe.setAttribute("style","border:none;width:100%;height:100%;");iframe.srcdoc=markup;iframeContainer.appendChild(iframe);parentElement.appendChild(iframeContainer)}function _resultSuccess(result){const idx=result>>2;const eagRuntimeJSURL=URL.createObjectURL(new Blob([results[HEAP32[idx]]],{type:"text/javascript;charset=utf-8"}));const classesWASMURL=URL.createObjectURL(new Blob([results[HEAP32[idx+1]]],{type:"application/wasm"}));const classesDeobfTEADBGURL=URL.createObjectURL(new Blob([results[HEAP32[idx+2]]],{type:"application/octet-stream"}));const classesDeobfWASMURL=URL.createObjectURL(new Blob([results[HEAP32[idx+3]]],{type:"application/wasm"}));const pressAnyKey=URL.createObjectURL(new Blob([results[HEAP32[idx+4]]],{type:results[HEAP32[idx+5]]}));const crashImg=URL.createObjectURL(new Blob([results[HEAP32[idx+6]]],{type:results[HEAP32[idx+7]]}));const faviconImg=URL.createObjectURL(new Blob([results[HEAP32[idx+8]]],{type:results[HEAP32[idx+9]]}));const numEPKs=HEAP32[idx+10];const epkFiles=new Array(numEPKs);for(var i=0,j;inoExitRuntime||runtimeKeepaliveCounter>0;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){Module["onExit"]?.(code);ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var lengthBytesUTF8=str=>{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx};var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);var stackAlloc=sz=>__emscripten_stack_alloc(sz);var stringToUTF8OnStack=str=>{var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8(str,ret,size);return ret};var wasmImports={k:__emscripten_memcpy_js,c:_dbgErr,b:_dbgLog,j:_emscripten_resize_heap,n:_getEPWLength,i:_getJSPISupported,f:_initEPWStringResult,e:_initResult,d:_memcpyFromEPW,g:_memcpyFromEPWToResult,l:_memcpyToResult,a:_resultFailed,h:_resultJSPIUnsupported,m:_resultSuccess};var wasmExports=createWasm();var ___wasm_call_ctors=()=>(___wasm_call_ctors=wasmExports["p"])();var _main=Module["_main"]=(a0,a1)=>(_main=Module["_main"]=wasmExports["q"])(a0,a1);var __emscripten_stack_alloc=a0=>(__emscripten_stack_alloc=wasmExports["s"])(a0);var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function callMain(args=[]){var entryFunction=_main;args.unshift(thisProgram);var argc=args.length;var argv=stackAlloc((argc+1)*4);var argv_ptr=argv;args.forEach(arg=>{HEAPU32[argv_ptr>>2]=stringToUTF8OnStack(arg);argv_ptr+=4});HEAPU32[argv_ptr>>2]=0;try{var ret=entryFunction(argc,argv);exitJS(ret,true);return ret}catch(e){return handleException(e)}}function run(args=arguments_){if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();Module["onRuntimeInitialized"]?.();if(shouldRunNow)callMain(args);postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(()=>{setTimeout(()=>Module["setStatus"](""),1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}var shouldRunNow=true;if(Module["noInitialRun"])shouldRunNow=false;run(); diff --git a/sources/setup/workspace_template/wasm_gc_teavm/javascript/loader.wasm b/sources/setup/workspace_template/wasm_gc_teavm/javascript/loader.wasm new file mode 100644 index 0000000..de16ec8 Binary files /dev/null and b/sources/setup/workspace_template/wasm_gc_teavm/javascript/loader.wasm differ diff --git a/sources/setup/workspace_template/wasm_gc_teavm/javascript/pressAnyKey.png b/sources/setup/workspace_template/wasm_gc_teavm/javascript/pressAnyKey.png new file mode 100644 index 0000000..6f62bec Binary files /dev/null and b/sources/setup/workspace_template/wasm_gc_teavm/javascript/pressAnyKey.png differ diff --git a/sources/setup/workspace_template/wasm_gc_teavm/javascript/splash.png b/sources/setup/workspace_template/wasm_gc_teavm/javascript/splash.png new file mode 100644 index 0000000..e9fd01b Binary files /dev/null and b/sources/setup/workspace_template/wasm_gc_teavm/javascript/splash.png differ diff --git a/sources/setup/workspace_template/wasm_gc_teavm/javascript_dist/bootstrap.js b/sources/setup/workspace_template/wasm_gc_teavm/javascript_dist/bootstrap.js new file mode 100644 index 0000000..1062d55 --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/javascript_dist/bootstrap.js @@ -0,0 +1,12 @@ +(function(){'use strict';function g(a){console.log("LoaderBootstrap: [INFO] "+a)}function n(a){console.error("LoaderBootstrap: [ERROR] "+a)}var q=null; +function r(){const a=[];for(var c=0;64>c;++c)a["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charCodeAt(c)]=c;a[45]=62;a[95]=63;return function(b,d){var e=b.length-d;if(0>16&255,f[h++]=d>>8&255,f[h++]=d&255;2===e?(d=a[b.charCodeAt(k)]<<2|a[b.charCodeAt(k+1)]>>4,f[h++]=d&255):1===e&&(d=a[b.charCodeAt(k)]<<10|a[b.charCodeAt(k+1)]<<4|a[b.charCodeAt(k+2)]>>2,f[h++]=d>>8&255,f[h++]=d&255);return f.buffer}}function u(){return new Promise(function(a){setTimeout(a,20)})}function v(a){return new Promise(function(c){fetch(a,{cache:"force-cache"}).then(function(b){return b.arrayBuffer()}).then(c).catch(function(b){n("Failed to fetch URL! "+b);c(null)})})} +function w(a){return a.startsWith("data:application/octet-stream;base64,")?new Promise(function(c){v(a).then(function(b){if(b)c(b);else{console.log("LoaderBootstrap: [WARN] Failed to decode base64 via fetch, doing it the slow way instead...");try{q||=r();var d=q(a,37);c(d)}catch(e){n("Failed to decode base64! "+e),c(null)}}})}):v(a)} +function x(a,c){const b=document.createElement("h2");b.style.color="#AA0000";b.style.padding="25px";b.style.fontFamily="sans-serif";b.style.marginBlock="0px";b.appendChild(document.createTextNode(c));a.appendChild(b);c=document.createElement("h4");c.style.color="#AA0000";c.style.padding="25px";c.style.fontFamily="sans-serif";c.style.marginBlock="0px";c.appendChild(document.createTextNode("Try again later"));a.style.backgroundColor="white";a.appendChild(c)} +window.main=async function(){if("undefined"===typeof window.eaglercraftXOpts)n("window.eaglercraftXOpts is not defined!"),alert("window.eaglercraftXOpts is not defined!");else{var a=window.eaglercraftXOpts.container;if("string"!==typeof a)n("window.eaglercraftXOpts.container is not a string!"),alert("window.eaglercraftXOpts.container is not a string!");else{var c=window.eaglercraftXOpts.assetsURI;if("string"!==typeof c)if("object"===typeof c&&"object"===typeof c[0]&&"string"===typeof c[0].url)c=c[0].url; +else{n("window.eaglercraftXOpts.assetsURI is not a string!");alert("window.eaglercraftXOpts.assetsURI is not a string!");return}var b=document.getElementById(a);if(b){for(;a=b.lastChild;)b.removeChild(a);a=document.createElement("div");a.style.width="100%";a.style.height="100%";a.style.setProperty("image-rendering","pixelated");a.style.background='center / contain no-repeat url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAAAAAB3tzPbAAAACXBIWXMAAC4jAAAuIwF4pT92AAAG+UlEQVR42u2cy23jOhRATwbTwGwFvAJoF6BFGjColcGkASNuIPA6C68DN+BADZiCVxLSQBYqIGYBAbSdEvwWkvUzZWfymwlwCQwQUZeXPOT9URPkYs/3bj8QAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAH4x9vPvzFpAhAzM98UILmqfjDf1YT0N/cBk+71v+wDSczHmDeJ6TqO+SIfyD7IvC9g33Yc7dP6CQDxB+q62Hc2xnyJD2Sf5vuzL3Hi5MM0WbCN51u/Y/30ryEGmDVHlhwsY9Y7xlq0CuzVc4lh2n7NkGsnQ1nB7IefmrY/araJcbrq6Ryk9YqW4l3J/dHww1jdej+8kte042EW0Nba1hyWdl+9irq/FNXaD6BbQoexuvf+tQC2vX1+AFvP0kxiuyidfWwEbOtQtK0n0r6xbYCKsLcM21+pLZX3u4984Kq2xlnWDimllRudAXEpkGSHfqMzsmxfWnLWNf9aQznW4wMZWOMJxvGs/Ff5X+yPcD0g3dqZesdsI2f7Z2/73W2JSok9Gqu7P1q/I2qtj0qn/ZkTaCPWO2a0VyjrxY7sNUG1LxRlaE90MpDpGVeAxpaGobN2XPWH0aQVE1stfXPAj0+XzUmcob3aTRdVZ2+tRv+gMNBDaTkZ4k6uhtYPaK7iUkUcx9lgij92gZ6aXmxoDeK8D1hPfm18oBvTfPGwXoVG+4VfXcwl8dEOtCJS7De9M0VTqTA2p081O3kJ+uk5cU/RVN8C262Ms9HMlLHSmhNFTcc9u1uQRX4jMhqyNIk1GRk69a6hb0IDZ3pITnbfNqFuJWE9gbYrfmSqen/SiKy27G0VS20VWc+UEn59/YDPkc+0EunrAXQ/JXucYL+3VutyAqvP5wFvtEoyQPsMJMpKc3v7/Su9ALLkhAJDPCObGTDmonfNHAij3sg5866fmTHGnFt/crroh6vEv/Rq6vhEoP7hWWb2ylSQZP5zOVrDqVxSZnm/xL6OFnZwF3/4JoyGjyXu1X3n0rEFyE5Jzc5KEDfT7s2ZYs52s5e1HU88hB17nKTqAroXWPpXiHbN7R3Q8fVDbjzU6vb8hUbX67FWN8Xo4U5SIWjbukr1knY9XrcwS30aOuTatqa0vkA6cI05dyPrzWBbj7ZZrPUT2O7pdpKFtp4rph0E0AxtfN0u9kNVg25d4BPiDF0+R83dPol7/l4m4yQmQzdX+ISewqTnc8ngp94yaCan4vT+Hc228q8/T35+e8+XueSqCaPmEz9ofdbX6eSqE5iN/m4A8Qd9w/1bAEl2fPmafT3Axdv/ytlFeXUwTZyyf+NA3hWDGPrm+HXtHSdQ7nrz7fvv+MPFe/9Q3nAS+iYA3zcKCYAACIAACIAACIAACIAACIAACIAA1C2Komh++r9cogdv90M0+GoZAVHkSiGSaFmOmJdTRdESiKJ5Je4eovnSldoGNJ44gTBNbx+XH7tDYxwOniAPgEdygGWxTm/jBCAHV0u7xa90PV64IW0uOWdCapK7t600vfF2j4Ad5FCE4IopCSWMSg0Q4NgRVNKrwIBJ1ZDGxXO/5+fxhDvFQ87EsHxZMy9Sli/raMbjf9eqMpiciQG3yYOJwW1eQoBoesNBzG3yKdvqNwie1HMwiXFcwo7L7aMBtlSrC7c79RzyUm5w0f66Gk1vcJs8vFYHxUvy/u8leJz4N8t8vX5ccl04Chz5BOLR+mVVWXX5lsU4ncSOFevL7WFsJbYiPfQpcvJwhNsBxKiwcHDPNnoojzp8Jh8PnusiSMcLd1B8R5i+Igq5/BZKU3IEO8cIpoqw6L5NR8kjuOIaFR6GlmKdvmnhuFTsfqNwTBnzBOo+ZFua+jh3jAZtnksMu/b850wIfh1sVwVPhMEzKK9lz/+7Hi3Kx8CjOajVbVCEz3kIT1wyYnsD6s5t8tUaGLFpTfC7q2TH4rjzHMCoGgqTOJiMFi/TY5kduOJWHfzdtzdFrS4PYBwzhi0LAKcAdTcvKhur+VWQ3/TWcq/+LJG5VahUsILHUDGiGCmKy26cOrxlxwZUsMHlvVDW7lMQwghGOGZpmt6zcdFD47EhtQVyWySQRHUgVDzhmkeClyZFlGmiA5BH0WpyB+twPp/cgQpQBH0Lqt6qaTwfs+OW6Kl/RrdET/WqQi5BgWLDqNxmdV/Mo1X1QX5Ms0Pq/jmaP7d2/b6IVq3HW+a9qT7v6/TDNv2+tVA0hzz8klroc07AbXKmN98YQMppARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARCAD2//A2iD9ZsgY5XpAAAAAElFTkSuQmCC") white'; +b.appendChild(a);c.startsWith("data:")?(g('Downloading EPW file ""...'),c=await w(c)):(g('Downloading EPW file "'+c+'"...'),c=await v(c));var d=!1;c?384>c.byteLength&&(n("The EPW file is too short"),d=!0):d=!0;if(d)b.removeChild(a),x(b,"Failed to download EPW file!"),n("Failed to download EPW file!");else{var e=new DataView(c);if(608649541!==e.getUint32(0,!0)||1297301847!==e.getUint32(4,!0))n("The file is not an EPW file"),d=!0;var f=c.byteLength;e.getUint32(8,!0)!==f&&(n("The EPW file is the wrong length"), +d=!0);if(d)b.removeChild(a),x(b,"EPW file is invalid!"),n("EPW file is invalid!");else{var l=new TextDecoder("utf-8"),h=e.getUint32(100,!0),k=e.getUint32(104,!0),m=e.getUint32(108,!0),p=e.getUint32(112,!0);if(0>h||h+k>f||0>m||m+p>f)n("The EPW file contains an invalid offset (component: splash)"),d=!0;if(d)b.removeChild(a),x(b,"EPW file is invalid!"),n("EPW file is invalid!");else{h=new Uint8Array(c,h,k);m=new Uint8Array(c,m,p);l=URL.createObjectURL(new Blob([h],{type:l.decode(m)}));g("Loaded splash img: "+ +l);a.style.background='center / contain no-repeat url("'+l+'"), 0px 0px / 1000000% 1000000% no-repeat url("'+l+'") white';await u();p=e.getUint32(164,!0);h=e.getUint32(168,!0);m=e.getUint32(180,!0);e=e.getUint32(184,!0);if(0>p||p+h>f||0>m||m+e>f)n("The EPW file contains an invalid offset (component: loader)"),d=!0;if(d)b.removeChild(a),x(b,"EPW file is invalid!"),n("EPW file is invalid!");else{a=new Uint8Array(c,p,h);a=URL.createObjectURL(new Blob([a],{type:"text/javascript;charset=utf-8"}));g("Loaded loader.js: "+ +l);d=new Uint8Array(c,m,e);d=URL.createObjectURL(new Blob([d],{type:"application/wasm"}));g("Loaded loader.wasm: "+d);f={};for(const [t,y]of Object.entries(window.eaglercraftXOpts))"container"!==t&&"assetsURI"!==t&&(f[t]=y);window.__eaglercraftXLoaderContextPre={rootElement:b,eaglercraftXOpts:f,theEPWFileBuffer:c,loaderWASMURL:d,splashURL:l};g("Appending loader.js to document...");b=document.createElement("script");b.type="text/javascript";b.src=a;document.head.appendChild(b)}}}}}else b='window.eaglercraftXOpts.container "'+ +a+'" is not a known element id!',n(b),alert(b)}}};}).call(this); diff --git a/sources/setup/workspace_template/wasm_gc_teavm/javascript_dist/favicon.png b/sources/setup/workspace_template/wasm_gc_teavm/javascript_dist/favicon.png new file mode 100644 index 0000000..9c9fc89 Binary files /dev/null and b/sources/setup/workspace_template/wasm_gc_teavm/javascript_dist/favicon.png differ diff --git a/sources/setup/workspace_template/wasm_gc_teavm/javascript_dist/index.html b/sources/setup/workspace_template/wasm_gc_teavm/javascript_dist/index.html new file mode 100644 index 0000000..2f7be1a --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/javascript_dist/index.html @@ -0,0 +1,57 @@ + + + + + + + + EaglercraftX 1.8 WASM-GC + + + + + + + + + + + \ No newline at end of file diff --git a/sources/setup/workspace_template/wasm_gc_teavm/settings.gradle b/sources/setup/workspace_template/wasm_gc_teavm/settings.gradle new file mode 100644 index 0000000..d21322f --- /dev/null +++ b/sources/setup/workspace_template/wasm_gc_teavm/settings.gradle @@ -0,0 +1,19 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/6.0/userguide/multi_project_builds.html + */ + +pluginManagement { + repositories { + maven { + url = uri("https://eaglercraft-teavm-fork.github.io/maven/") + } + mavenCentral() + } +} + +rootProject.name = 'eagler-workspace-wasm-gc-teavm' diff --git a/sources/teavm-boot-menu/java/net/lax1dude/eaglercraft/v1_8/boot_menu/teavm/BootMenuEntryPoint.java b/sources/teavm-boot-menu/java/net/lax1dude/eaglercraft/v1_8/boot_menu/teavm/BootMenuEntryPoint.java index 61090f3..aefbbd6 100644 --- a/sources/teavm-boot-menu/java/net/lax1dude/eaglercraft/v1_8/boot_menu/teavm/BootMenuEntryPoint.java +++ b/sources/teavm-boot-menu/java/net/lax1dude/eaglercraft/v1_8/boot_menu/teavm/BootMenuEntryPoint.java @@ -48,11 +48,13 @@ public class BootMenuEntryPoint { private static native void setHasAlreadyBooted(); public static boolean checkShouldLaunchFlag(Window win) { + wasManuallyInvoked = false; int flag = BootMenuDataManager.getBootMenuFlags(win); if(flag == -1) { return IBootMenuConfigAdapter.instance.isShowBootMenuOnLaunch() && !getHasAlreadyBooted(); } if((flag & 2) != 0) { + wasManuallyInvoked = true; BootMenuDataManager.setBootMenuFlags(win, flag & ~2); setHasAlreadyBooted(); return true; @@ -63,6 +65,7 @@ public class BootMenuEntryPoint { private static boolean hasInit = false; private static byte[] signatureData = null; private static byte[] bundleData = null; + public static boolean wasManuallyInvoked = false; public static void launchMenu(Window parentWindow, HTMLElement parentElement) { signatureData = PlatformUpdateSvc.getClientSignatureData(); diff --git a/sources/teavm-boot-menu/java/net/lax1dude/eaglercraft/v1_8/boot_menu/teavm/BootMenuMain.java b/sources/teavm-boot-menu/java/net/lax1dude/eaglercraft/v1_8/boot_menu/teavm/BootMenuMain.java index 545ffcb..df8adb5 100644 --- a/sources/teavm-boot-menu/java/net/lax1dude/eaglercraft/v1_8/boot_menu/teavm/BootMenuMain.java +++ b/sources/teavm-boot-menu/java/net/lax1dude/eaglercraft/v1_8/boot_menu/teavm/BootMenuMain.java @@ -144,7 +144,7 @@ public class BootMenuMain { bootMenuFatOfflineLoader = new BootMenuFatOfflineLoader(parentWindow.getDocument().getHead()); logger.info("Entering boot menu display state"); eventQueue.clear(); - changeState(new MenuStateBoot(true)); + changeState(new MenuStateBoot(!BootMenuEntryPoint.wasManuallyInvoked)); enterUpdateLoop(); } diff --git a/sources/teavm-boot-menu/java/net/lax1dude/eaglercraft/v1_8/boot_menu/teavm/OfflineDownloadParser.java b/sources/teavm-boot-menu/java/net/lax1dude/eaglercraft/v1_8/boot_menu/teavm/OfflineDownloadParser.java index 6bb9698..c55e508 100644 --- a/sources/teavm-boot-menu/java/net/lax1dude/eaglercraft/v1_8/boot_menu/teavm/OfflineDownloadParser.java +++ b/sources/teavm-boot-menu/java/net/lax1dude/eaglercraft/v1_8/boot_menu/teavm/OfflineDownloadParser.java @@ -74,6 +74,12 @@ public class OfflineDownloadParser { logger.info("Detected format: EAGLERCRAFTX_1_8_OFFLINE (International)"); return EnumOfflineParseType.EAGLERCRAFTX_1_8_OFFLINE; } + if(foundWithin(offlineDownloadData.indexOf(StringUtils.reverse("]OFNI[ :partstooBredaoL")), 0, 16384)) { + if(offlineDownloadData.indexOf(StringUtils.reverse(",46esab;maerts-tetco/noitacilppa:atad\" = IRUstessa.stpOXtfarcrelgae.wodniw")) != -1) { + logger.info("Detected format: EAGLERCRAFTX_1_8_OFFLINE (WASM-GC)"); + return EnumOfflineParseType.EAGLERCRAFTX_1_8_OFFLINE; + } + } if(foundWithin(offlineDownloadData.indexOf(StringUtils.reverse("{ = stpOtfarcrelgae.wodniw")), 32, 2048) && foundWithin(offlineDownloadData.indexOf(StringUtils.reverse(">\"rekrow_ps\"=di \"rekrowrelgae/txet\"=epyt tpircs<")), 4194304, offlineDownloadData.length() - 1048576)) { logger.info("Detected format: EAGLERCRAFTX_1_5_NEW_OFFLINE"); return EnumOfflineParseType.EAGLERCRAFT_1_5_NEW_OFFLINE; diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformInput.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformInput.java index 4bde1e1..8b6bff7 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformInput.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformInput.java @@ -1569,7 +1569,7 @@ public class PlatformInput { isOnMobilePressAnyKey = true; setupAnyKeyScreenMobile(allowBootMenu); if(pressAnyKeyScreenMobile() && allowBootMenu) { - PlatformRuntime.enterBootMenu(); + PlatformRuntime.enterBootMenu(true); } }finally { isOnMobilePressAnyKey = false; diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformRuntime.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformRuntime.java index fba11f6..9d97459 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformRuntime.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformRuntime.java @@ -185,8 +185,7 @@ public class PlatformRuntime { } CSSStyleDeclaration style = root.getStyle(); - style.setProperty("overflowX", "hidden"); - style.setProperty("overflowY", "hidden"); + style.setProperty("overflow", "hidden"); TeaVMClientConfigAdapter teavmCfg = (TeaVMClientConfigAdapter) getClientConfigAdapter(); boolean allowBootMenu = teavmCfg.isAllowBootMenu(); @@ -241,8 +240,7 @@ public class PlatformRuntime { style.setProperty("position", "relative"); style.setProperty("width", "100%"); style.setProperty("height", "100%"); - style.setProperty("overflowX", "hidden"); - style.setProperty("overflowY", "hidden"); + style.setProperty("overflow", "hidden"); root.appendChild(parent); ClientMain.configRootElement = parent; // hack @@ -421,7 +419,7 @@ public class PlatformRuntime { Collections.sort(exts); logger.info("Unlocked the following OpenGL ES extensions:"); for(int i = 0, l = exts.size(); i < l; ++i) { - logger.info(" - " + exts.get(i)); + logger.info(" - {}", exts.get(i)); } } @@ -443,7 +441,7 @@ public class PlatformRuntime { if(allowBootMenu && BootMenuEntryPoint.checkShouldLaunchFlag(win)) { logger.info("Boot menu enable flag is set, entering boot menu..."); - enterBootMenu(); + enterBootMenu(BootMenuEntryPoint.wasManuallyInvoked); } byte[] finalLoadScreen = PlatformAssets.getResourceBytes("/assets/eagler/eagtek.png"); @@ -1134,7 +1132,7 @@ public class PlatformRuntime { if(PlatformInput.keyboardGetEventKeyState()) { int key = PlatformInput.keyboardGetEventKey(); if(key == KeyboardConstants.KEY_DELETE || key == KeyboardConstants.KEY_BACK) { - enterBootMenu(); + enterBootMenu(true); } } } @@ -1143,7 +1141,7 @@ public class PlatformRuntime { @JSBody(params = {}, script = "delete __isEaglerX188Running;") private static native void clearRunningFlag(); - static void enterBootMenu() { + static void enterBootMenu(boolean manual) { if(!getClientConfigAdapter().isAllowBootMenu()) { throw new IllegalStateException("Boot menu is disabled"); } @@ -1170,7 +1168,7 @@ public class PlatformRuntime { immediateContinueChannel = null; clearRunningFlag(); logger.info("Firing boot menu escape signal..."); - throw new TeaVMEnterBootMenuException(); + throw new TeaVMEnterBootMenuException(manual); } public static void postCreate() { diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformScreenRecord.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformScreenRecord.java index 5def437..bf7f168 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformScreenRecord.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformScreenRecord.java @@ -188,7 +188,7 @@ public class PlatformScreenRecord { TeaVMUtils.addEventListener(mediaRec, "dataavailable", new EventListener() { @Override public void handleEvent(DataAvailableEvent evt) { - final String fileName = EaglercraftVersion.mainMenuStringB + " - " + EaglerProfile.getName() + " - " + fmt.format(new Date()) + "." + params.codec.fileExt; + final String fileName = EaglercraftVersion.screenRecordingFilePrefix + " - " + EaglerProfile.getName() + " - " + fmt.format(new Date()) + "." + params.codec.fileExt; if("video/webm".equals(params.codec.container)) { FixWebMDurationJS.getRecUrl(evt, (int) (PlatformRuntime.steadyTimeMillis() - startTime), url -> { PlatformApplication.downloadURLWithNameTeaVM(fileName, url, () -> TeaVMUtils.freeDataURL(url)); diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformWebRTC.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformWebRTC.java index 56356d1..1a434d8 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformWebRTC.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformWebRTC.java @@ -421,7 +421,7 @@ public class PlatformWebRTC { final Object[] evtHandler = new Object[1]; evtHandler[0] = (EventListener) evt -> { if (!iceCandidates.isEmpty()) { - Window.setTimeout(() -> ((EventListener)evtHandler[0]).handleEvent(evt), 1); + Window.setTimeout(() -> ((EventListener)evtHandler[0]).handleEvent(evt), 10); return; } clientDataChannelClosed = false; @@ -541,7 +541,7 @@ public class PlatformWebRTC { final Object[] evtHandler = new Object[1]; evtHandler[0] = (EventListener) evt -> { if (!iceCandidates.isEmpty()) { - Window.setTimeout(() -> ((EventListener)evtHandler[0]).handleEvent(evt), 1); + Window.setTimeout(() -> ((EventListener)evtHandler[0]).handleEvent(evt), 10); return; } if (getChannel(evt) == null) return; diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformWebView.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformWebView.java index 739314d..4df5267 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformWebView.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformWebView.java @@ -200,7 +200,6 @@ public class PlatformWebView { try { List sandboxArgs = new ArrayList<>(); sandboxArgs.add("allow-downloads"); - sandboxArgs.add("allow-same-origin"); if(options.scriptEnabled) { sandboxArgs.add("allow-scripts"); sandboxArgs.add("allow-pointer-lock"); diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/ClientMain.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/ClientMain.java index f692fab..8d859ae 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/ClientMain.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/ClientMain.java @@ -202,6 +202,7 @@ public class ClientMain { }catch(TeaVMEnterBootMenuException ee) { try { systemOut.println("ClientMain: [INFO] launching eaglercraftx boot menu"); + BootMenuEntryPoint.wasManuallyInvoked = ee.isManual; BootMenuEntryPoint.launchMenu(Window.current(), configRootElement); }catch(Throwable t) { showCrashScreen("Failed to enter boot menu!", t); @@ -550,6 +551,7 @@ public class ClientMain { } if(el == null) { + Window.alert("Compatibility error: " + t); System.err.println("Compatibility error: " + t); return; } @@ -573,11 +575,9 @@ public class ClientMain { + "


Things you can try:

" + "
    " + "
  1. Just try using Eaglercraft on a different device, it isn't a bug it's common sense
  2. " - + "
  3. If you are on a mobile device, please try a proper desktop or a laptop computer
  4. " - + "
  5. If you are using a device with no mouse cursor, please use a device with a mouse cursor
  6. " + + "
  7. If this screen just appeared randomly, try restarting your browser or device
  8. " + "
  9. If you are not using Chrome/Edge, try installing the latest Google Chrome
  10. " + "
  11. If your browser is out of date, please update it to the latest version
  12. " - + "
  13. If you are using an old OS such as Windows 7, please try Windows 10 or 11
  14. " + "
" + ""); diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/TeaVMClientConfigAdapter.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/TeaVMClientConfigAdapter.java index 53ed5f5..31dd4e7 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/TeaVMClientConfigAdapter.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/TeaVMClientConfigAdapter.java @@ -69,7 +69,7 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter, IBootMenu private boolean openDebugConsoleOnLaunch = false; private boolean fixDebugConsoleUnloadListener = false; private boolean forceWebViewSupport = false; - private boolean enableWebViewCSP = false; + private boolean enableWebViewCSP = true; private boolean autoFixLegacyStyleAttr = false; private boolean showBootMenuOnLaunch = false; private boolean bootMenuBlocksUnsignedClients = false; @@ -77,7 +77,7 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter, IBootMenu private boolean forceProfanityFilter = false; private boolean forceWebGL1 = false; private boolean forceWebGL2 = false; - private boolean allowExperimentalWebGL1 = false; + private boolean allowExperimentalWebGL1 = true; private boolean useWebGLExt = true; private boolean useDelayOnSwap = false; private boolean useJOrbisAudioDecoder = false; @@ -550,6 +550,11 @@ public class TeaVMClientConfigAdapter implements IClientConfigAdapter, IBootMenu return ramdiskMode; } + @Override + public boolean isEnforceVSync() { + return false; + } + @Override public IClientConfigAdapterHooks getHooks() { return hooks; diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/TeaVMEnterBootMenuException.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/TeaVMEnterBootMenuException.java index 7a67daa..9c0a3a3 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/TeaVMEnterBootMenuException.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/internal/teavm/TeaVMEnterBootMenuException.java @@ -17,4 +17,10 @@ package net.lax1dude.eaglercraft.v1_8.internal.teavm; */ public class TeaVMEnterBootMenuException extends RuntimeException { + public final boolean isManual; + + public TeaVMEnterBootMenuException(boolean manual) { + this.isManual = manual; + } + } diff --git a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/ServerPlatformSingleplayer.java b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/ServerPlatformSingleplayer.java index df865ff..de2646e 100644 --- a/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/ServerPlatformSingleplayer.java +++ b/sources/teavm/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/ServerPlatformSingleplayer.java @@ -35,6 +35,7 @@ import net.lax1dude.eaglercraft.v1_8.internal.teavm.TeaVMUtils; import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFile2; import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; import net.lax1dude.eaglercraft.v1_8.log4j.Logger; +import net.lax1dude.eaglercraft.v1_8.sp.server.IWASMCrashCallback; /** * Copyright (c) 2022-2024 lax1dude. All Rights Reserved. @@ -291,4 +292,8 @@ public class ServerPlatformSingleplayer { } } + public static void setCrashCallbackWASM(IWASMCrashCallback callback) { + + } + } diff --git a/sources/wasm-gc-teavm-bootstrap/js/externs.js b/sources/wasm-gc-teavm-bootstrap/js/externs.js new file mode 100644 index 0000000..3947ba6 --- /dev/null +++ b/sources/wasm-gc-teavm-bootstrap/js/externs.js @@ -0,0 +1,45 @@ +/** + * @fileoverview loader bootstrap 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. + * + */ + + +window.main = function() {}; + +window.eaglercraftXOpts = {}; + +window.eaglercraftXOpts.assetsURI = ""; + +window.eaglercraftXOpts.container = ""; + +window.__eaglercraftXLoaderContextPre = {}; + +/** @type {!HTMLElement} */ +window.__eaglercraftXLoaderContextPre.rootElement; + +/** @type {!Object} */ +window.__eaglercraftXLoaderContextPre.eaglercraftXOpts; + +/** @type {!ArrayBuffer} */ +window.__eaglercraftXLoaderContextPre.theEPWFileBuffer; + +/** @type {string} */ +window.__eaglercraftXLoaderContextPre.loaderWASMURL; + +/** @type {string} */ +window.__eaglercraftXLoaderContextPre.splashURL; diff --git a/sources/wasm-gc-teavm-bootstrap/js/main.js b/sources/wasm-gc-teavm-bootstrap/js/main.js new file mode 100644 index 0000000..3770af0 --- /dev/null +++ b/sources/wasm-gc-teavm-bootstrap/js/main.js @@ -0,0 +1,377 @@ +/* + * 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. + * + */ + +/** + * @param {*} msg + */ +function logInfo(msg) { + console.log("LoaderBootstrap: [INFO] " + msg); +} + +/** + * @param {*} msg + */ +function logWarn(msg) { + console.log("LoaderBootstrap: [WARN] " + msg); +} + +/** + * @param {*} msg + */ +function logError(msg) { + console.error("LoaderBootstrap: [ERROR] " + msg); +} + +/** @type {function(string,number):ArrayBuffer|null} */ +var decodeBase64Impl = null; + +/** + * @return {function(string,number):ArrayBuffer} + */ +function createBase64Decoder() { + const revLookup = []; + const code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + for (var i = 0, len = code.length; i < len; ++i) { + revLookup[code.charCodeAt(i)] = i; + } + + revLookup["-".charCodeAt(0)] = 62; + revLookup["_".charCodeAt(0)] = 63; + + /** + * @param {string} b64 + * @param {number} start + * @return {!Array} + */ + function getLens(b64, start) { + const len = b64.length - start; + if (len % 4 > 0) { + throw new Error("Invalid string. Length must be a multiple of 4"); + } + var validLen = b64.indexOf("=", start); + if (validLen === -1) { + validLen = len; + }else { + validLen -= start; + } + const placeHoldersLen = validLen === len ? 0 : 4 - (validLen % 4); + return [validLen, placeHoldersLen]; + } + + /** + * @param {string} b64 + * @param {number} start + * @return {ArrayBuffer} + */ + function decodeImpl(b64, start) { + var tmp; + const lens = getLens(b64, start); + const validLen = lens[0]; + const placeHoldersLen = lens[1]; + const arr = new Uint8Array(((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen); + var curByte = 0; + const len = (placeHoldersLen > 0 ? validLen - 4 : validLen) + start; + var i; + for (i = start; i < len; i += 4) { + tmp = (revLookup[b64.charCodeAt(i)] << 18) | + (revLookup[b64.charCodeAt(i + 1)] << 12) | + (revLookup[b64.charCodeAt(i + 2)] << 6) | + revLookup[b64.charCodeAt(i + 3)] + arr[curByte++] = (tmp >> 16) & 0xFF + arr[curByte++] = (tmp >> 8) & 0xFF + arr[curByte++] = tmp & 0xFF + } + if (placeHoldersLen === 2) { + tmp = (revLookup[b64.charCodeAt(i)] << 2) | + (revLookup[b64.charCodeAt(i + 1)] >> 4) + arr[curByte++] = tmp & 0xFF + }else if (placeHoldersLen === 1) { + tmp = (revLookup[b64.charCodeAt(i)] << 10) | + (revLookup[b64.charCodeAt(i + 1)] << 4) | + (revLookup[b64.charCodeAt(i + 2)] >> 2) + arr[curByte++] = (tmp >> 8) & 0xFF + arr[curByte++] = tmp & 0xFF + } + return arr.buffer; + } + + return decodeImpl; +} + +/** + * @param {string} url + * @param {number} start + * @return {ArrayBuffer} + */ +function decodeBase64(url, start) { + if(!decodeBase64Impl) { + decodeBase64Impl = createBase64Decoder(); + } + return decodeBase64Impl(url, start); +} + +/** + * @param {number} ms + * @return {!Promise} + */ +function asyncSleep(ms) { + return new Promise(function(resolve) { + setTimeout(resolve, ms); + }); +} + +/** + * @param {string} url + * @return {!Promise} + */ +function downloadURL(url) { + return new Promise(function(resolve) { + fetch(url, { "cache": "force-cache" }) + .then(function(res) { + return res.arrayBuffer(); + }) + .then(resolve) + .catch(function(ex) { + logError("Failed to fetch URL! " + ex); + resolve(null); + }); + }); +} + +/** + * @param {string} url + * @return {!Promise} + */ +function downloadDataURL(url) { + if(!url.startsWith("data:application/octet-stream;base64,")) { + return downloadURL(url); + }else { + return new Promise(function(resolve) { + downloadURL(url).then(function(res) { + if(res) { + resolve(res); + }else { + logWarn("Failed to decode base64 via fetch, doing it the slow way instead..."); + try { + resolve(decodeBase64(url, 37)); + }catch(ex) { + logError("Failed to decode base64! " + ex); + resolve(null); + } + } + }); + }); + } +} + +/** + * @param {HTMLElement} rootElement + * @param {string} msg + */ +function displayInvalidEPW(rootElement, msg) { + const downloadFailureMsg = /** @type {HTMLElement} */ (document.createElement("h2")); + downloadFailureMsg.style.color = "#AA0000"; + downloadFailureMsg.style.padding = "25px"; + downloadFailureMsg.style.fontFamily = "sans-serif"; + downloadFailureMsg.style["marginBlock"] = "0px"; + downloadFailureMsg.appendChild(document.createTextNode(msg)); + rootElement.appendChild(downloadFailureMsg); + const downloadFailureMsg2 = /** @type {HTMLElement} */ (document.createElement("h4")); + downloadFailureMsg2.style.color = "#AA0000"; + downloadFailureMsg2.style.padding = "25px"; + downloadFailureMsg2.style.fontFamily = "sans-serif"; + downloadFailureMsg2.style["marginBlock"] = "0px"; + downloadFailureMsg2.appendChild(document.createTextNode("Try again later")); + rootElement.style.backgroundColor = "white"; + rootElement.appendChild(downloadFailureMsg2); +} + +window.main = async function() { + if(typeof window.eaglercraftXOpts === "undefined") { + const msg = "window.eaglercraftXOpts is not defined!"; + logError(msg); + alert(msg); + return; + } + + const containerId = window.eaglercraftXOpts.container; + if(typeof containerId !== "string") { + const msg = "window.eaglercraftXOpts.container is not a string!"; + logError(msg); + alert(msg); + return; + } + + var assetsURI = window.eaglercraftXOpts.assetsURI; + if(typeof assetsURI !== "string") { + if((typeof assetsURI === "object") && (typeof assetsURI[0] === "object") && (typeof assetsURI[0]["url"] === "string")) { + assetsURI = assetsURI[0]["url"]; + }else { + const msg = "window.eaglercraftXOpts.assetsURI is not a string!"; + logError(msg); + alert(msg); + return; + } + } + + const rootElement = /** @type {HTMLElement} */ (document.getElementById(containerId)); + + if(!rootElement) { + const msg = "window.eaglercraftXOpts.container \"" + containerId + "\" is not a known element id!"; + logError(msg); + alert(msg); + return; + } + + var node; + while(node = rootElement.lastChild) { + rootElement.removeChild(node); + } + + const splashElement = /** @type {HTMLElement} */ (document.createElement("div")); + splashElement.style.width = "100%"; + splashElement.style.height = "100%"; + splashElement.style.setProperty("image-rendering", "pixelated"); + splashElement.style.background = "center / contain no-repeat url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAAAAAB3tzPbAAAACXBIWXMAAC4jAAAuIwF4pT92AAAG+UlEQVR42u2cy23jOhRATwbTwGwFvAJoF6BFGjColcGkASNuIPA6C68DN+BADZiCVxLSQBYqIGYBAbSdEvwWkvUzZWfymwlwCQwQUZeXPOT9URPkYs/3bj8QAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAEQAAH4x9vPvzFpAhAzM98UILmqfjDf1YT0N/cBk+71v+wDSczHmDeJ6TqO+SIfyD7IvC9g33Yc7dP6CQDxB+q62Hc2xnyJD2Sf5vuzL3Hi5MM0WbCN51u/Y/30ryEGmDVHlhwsY9Y7xlq0CuzVc4lh2n7NkGsnQ1nB7IefmrY/araJcbrq6Ryk9YqW4l3J/dHww1jdej+8kte042EW0Nba1hyWdl+9irq/FNXaD6BbQoexuvf+tQC2vX1+AFvP0kxiuyidfWwEbOtQtK0n0r6xbYCKsLcM21+pLZX3u4984Kq2xlnWDimllRudAXEpkGSHfqMzsmxfWnLWNf9aQznW4wMZWOMJxvGs/Ff5X+yPcD0g3dqZesdsI2f7Z2/73W2JSok9Gqu7P1q/I2qtj0qn/ZkTaCPWO2a0VyjrxY7sNUG1LxRlaE90MpDpGVeAxpaGobN2XPWH0aQVE1stfXPAj0+XzUmcob3aTRdVZ2+tRv+gMNBDaTkZ4k6uhtYPaK7iUkUcx9lgij92gZ6aXmxoDeK8D1hPfm18oBvTfPGwXoVG+4VfXcwl8dEOtCJS7De9M0VTqTA2p081O3kJ+uk5cU/RVN8C262Ms9HMlLHSmhNFTcc9u1uQRX4jMhqyNIk1GRk69a6hb0IDZ3pITnbfNqFuJWE9gbYrfmSqen/SiKy27G0VS20VWc+UEn59/YDPkc+0EunrAXQ/JXucYL+3VutyAqvP5wFvtEoyQPsMJMpKc3v7/Su9ALLkhAJDPCObGTDmonfNHAij3sg5866fmTHGnFt/crroh6vEv/Rq6vhEoP7hWWb2ylSQZP5zOVrDqVxSZnm/xL6OFnZwF3/4JoyGjyXu1X3n0rEFyE5Jzc5KEDfT7s2ZYs52s5e1HU88hB17nKTqAroXWPpXiHbN7R3Q8fVDbjzU6vb8hUbX67FWN8Xo4U5SIWjbukr1knY9XrcwS30aOuTatqa0vkA6cI05dyPrzWBbj7ZZrPUT2O7pdpKFtp4rph0E0AxtfN0u9kNVg25d4BPiDF0+R83dPol7/l4m4yQmQzdX+ISewqTnc8ngp94yaCan4vT+Hc228q8/T35+e8+XueSqCaPmEz9ofdbX6eSqE5iN/m4A8Qd9w/1bAEl2fPmafT3Axdv/ytlFeXUwTZyyf+NA3hWDGPrm+HXtHSdQ7nrz7fvv+MPFe/9Q3nAS+iYA3zcKCYAACIAACIAACIAACIAACIAACIAA1C2Komh++r9cogdv90M0+GoZAVHkSiGSaFmOmJdTRdESiKJ5Je4eovnSldoGNJ44gTBNbx+XH7tDYxwOniAPgEdygGWxTm/jBCAHV0u7xa90PV64IW0uOWdCapK7t600vfF2j4Ad5FCE4IopCSWMSg0Q4NgRVNKrwIBJ1ZDGxXO/5+fxhDvFQ87EsHxZMy9Sli/raMbjf9eqMpiciQG3yYOJwW1eQoBoesNBzG3yKdvqNwie1HMwiXFcwo7L7aMBtlSrC7c79RzyUm5w0f66Gk1vcJs8vFYHxUvy/u8leJz4N8t8vX5ccl04Chz5BOLR+mVVWXX5lsU4ncSOFevL7WFsJbYiPfQpcvJwhNsBxKiwcHDPNnoojzp8Jh8PnusiSMcLd1B8R5i+Igq5/BZKU3IEO8cIpoqw6L5NR8kjuOIaFR6GlmKdvmnhuFTsfqNwTBnzBOo+ZFua+jh3jAZtnksMu/b850wIfh1sVwVPhMEzKK9lz/+7Hi3Kx8CjOajVbVCEz3kIT1wyYnsD6s5t8tUaGLFpTfC7q2TH4rjzHMCoGgqTOJiMFi/TY5kduOJWHfzdtzdFrS4PYBwzhi0LAKcAdTcvKhur+VWQ3/TWcq/+LJG5VahUsILHUDGiGCmKy26cOrxlxwZUsMHlvVDW7lMQwghGOGZpmt6zcdFD47EhtQVyWySQRHUgVDzhmkeClyZFlGmiA5BH0WpyB+twPp/cgQpQBH0Lqt6qaTwfs+OW6Kl/RrdET/WqQi5BgWLDqNxmdV/Mo1X1QX5Ms0Pq/jmaP7d2/b6IVq3HW+a9qT7v6/TDNv2+tVA0hzz8klroc07AbXKmN98YQMppARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARCAD2//A2iD9ZsgY5XpAAAAAElFTkSuQmCC\") white"; + rootElement.appendChild(splashElement); + + /** @type {ArrayBuffer} */ + var theEPWFileBuffer; + if(assetsURI.startsWith("data:")) { + logInfo("Downloading EPW file \"\"..."); + theEPWFileBuffer = await downloadDataURL(assetsURI); + }else { + logInfo("Downloading EPW file \"" + assetsURI + "\"..."); + theEPWFileBuffer = await downloadURL(assetsURI); + } + + var isInvalid = false; + if(!theEPWFileBuffer) { + isInvalid = true; + }else if(theEPWFileBuffer.byteLength < 384) { + logError("The EPW file is too short"); + isInvalid = true; + } + + if(isInvalid) { + rootElement.removeChild(splashElement); + const msg = "Failed to download EPW file!"; + displayInvalidEPW(rootElement, msg); + logError(msg); + return; + } + + const dataView = new DataView(theEPWFileBuffer); + + if(dataView.getUint32(0, true) !== 608649541 || dataView.getUint32(4, true) !== 1297301847) { + logError("The file is not an EPW file"); + isInvalid = true; + } + + const phileLength = theEPWFileBuffer.byteLength; + if(dataView.getUint32(8, true) !== phileLength) { + logError("The EPW file is the wrong length"); + isInvalid = true; + } + + if(isInvalid) { + rootElement.removeChild(splashElement); + const msg = "EPW file is invalid!"; + displayInvalidEPW(rootElement, msg); + logError(msg); + return; + } + + const textDecoder = new TextDecoder("utf-8"); + + const splashDataOffset = dataView.getUint32(100, true); + const splashDataLength = dataView.getUint32(104, true); + const splashMIMEOffset = dataView.getUint32(108, true); + const splashMIMELength = dataView.getUint32(112, true); + + if(splashDataOffset < 0 || splashDataOffset + splashDataLength > phileLength + || splashMIMEOffset < 0 || splashMIMEOffset + splashMIMELength > phileLength) { + logError("The EPW file contains an invalid offset (component: splash)"); + isInvalid = true; + } + + if(isInvalid) { + rootElement.removeChild(splashElement); + const msg = "EPW file is invalid!"; + displayInvalidEPW(rootElement, msg); + logError(msg); + return; + } + + const splashBinSlice = new Uint8Array(theEPWFileBuffer, splashDataOffset, splashDataLength); + const splashMIMESlice = new Uint8Array(theEPWFileBuffer, splashMIMEOffset, splashMIMELength); + const splashURL = URL.createObjectURL(new Blob([ splashBinSlice ], { "type": textDecoder.decode(splashMIMESlice) })); + logInfo("Loaded splash img: " + splashURL); + splashElement.style.background = "center / contain no-repeat url(\"" + splashURL + "\"), 0px 0px / 1000000% 1000000% no-repeat url(\"" + splashURL + "\") white"; + + // allow the screen to update + await asyncSleep(20); + + const loaderJSOffset = dataView.getUint32(164, true); + const loaderJSLength = dataView.getUint32(168, true); + const loaderWASMOffset = dataView.getUint32(180, true); + const loaderWASMLength = dataView.getUint32(184, true); + + if(loaderJSOffset < 0 || loaderJSOffset + loaderJSLength > phileLength + || loaderWASMOffset < 0 || loaderWASMOffset + loaderWASMLength > phileLength) { + logError("The EPW file contains an invalid offset (component: loader)"); + isInvalid = true; + } + + if(isInvalid) { + rootElement.removeChild(splashElement); + const msg = "EPW file is invalid!"; + displayInvalidEPW(rootElement, msg); + logError(msg); + return; + } + + const loaderJSSlice = new Uint8Array(theEPWFileBuffer, loaderJSOffset, loaderJSLength); + const loaderJSURL = URL.createObjectURL(new Blob([ loaderJSSlice ], { "type": "text/javascript;charset=utf-8" })); + logInfo("Loaded loader.js: " + splashURL); + const loaderWASMSlice = new Uint8Array(theEPWFileBuffer, loaderWASMOffset, loaderWASMLength); + const loaderWASMURL = URL.createObjectURL(new Blob([ loaderWASMSlice ], { "type": "application/wasm" })); + logInfo("Loaded loader.wasm: " + loaderWASMURL); + + const optsObj = {}; + for(const [key, value] of Object.entries(window.eaglercraftXOpts)) { + if(key !== "container" && key !== "assetsURI") { + optsObj[key] = value; + } + } + + window.__eaglercraftXLoaderContextPre = { + "rootElement": rootElement, + "eaglercraftXOpts": optsObj, + "theEPWFileBuffer": theEPWFileBuffer, + "loaderWASMURL": loaderWASMURL, + "splashURL": splashURL + }; + + logInfo("Appending loader.js to document..."); + + const scriptElement = /** @type {HTMLScriptElement} */ (document.createElement("script")); + scriptElement.type = "text/javascript"; + scriptElement.src = loaderJSURL; + document.head.appendChild(scriptElement); + +}; + diff --git a/sources/wasm-gc-teavm-loader/c/epw_header.h b/sources/wasm-gc-teavm-loader/c/epw_header.h new file mode 100644 index 0000000..d513e37 --- /dev/null +++ b/sources/wasm-gc-teavm-loader/c/epw_header.h @@ -0,0 +1,88 @@ +/* + * 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. + * + */ + +#ifndef _INCLUDED_EPW_HEADER_H +#define _INCLUDED_EPW_HEADER_H + +#include "stdint.h" + +struct epw_slice { + uint32_t sliceOffset; + uint32_t sliceLength; +}; + +struct epw_slice_compressed { + uint32_t sliceOffset; + uint32_t sliceCompressedLength; + uint32_t sliceDecompressedLength; + uint32_t _reserved; +}; + +struct epw_assets_epk_file { + struct epw_slice filePath; + struct epw_slice loadPath; + struct epw_slice_compressed fileData; +}; + +struct epw_header { + uint8_t magic[8]; + + uint32_t fileLength; + uint32_t fileCRC32; + uint16_t versionMajor; + uint16_t versionMinor; + + uint32_t clientVersionInt; + + struct epw_slice clientPackageName; + struct epw_slice clientOriginName; + struct epw_slice clientOriginVersion; + struct epw_slice clientOriginVendor; + struct epw_slice clientForkName; + struct epw_slice clientForkVersion; + struct epw_slice clientForkVendor; + struct epw_slice metadataSegment; + + uint64_t creationTime; + uint32_t numEPKs; + + struct epw_slice splashImageData; + struct epw_slice splashImageMIME; + struct epw_slice pressAnyKeyImageData; + struct epw_slice pressAnyKeyImageMIME; + struct epw_slice crashImageData; + struct epw_slice crashImageMIME; + struct epw_slice faviconImageData; + struct epw_slice faviconImageMIME; + + struct epw_slice loaderJSData; + uint32_t _reserved_0; + uint32_t _reserved_1; + + struct epw_slice loaderWASMData; + uint32_t _reserved_2; + uint32_t _reserved_3; + + struct epw_slice_compressed JSPIUnavailableData; + struct epw_slice_compressed eagruntimeJSData; + struct epw_slice_compressed classesWASMData; + struct epw_slice_compressed classesDeobfTEADBGData; + struct epw_slice_compressed classesDeobfWASMData; + + struct epw_assets_epk_file assetsEPKs[]; +}; + +#endif diff --git a/sources/wasm-gc-teavm-loader/c/imports.h b/sources/wasm-gc-teavm-loader/c/imports.h new file mode 100644 index 0000000..6b8dd6c --- /dev/null +++ b/sources/wasm-gc-teavm-loader/c/imports.h @@ -0,0 +1,67 @@ +/* + * 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. + * + */ + +#ifndef _INCLUDED_IMPORTS_H +#define _INCLUDED_IMPORTS_H + +#include "stdint.h" + +struct epw_load_result_epk { + uint32_t epkData; + uint32_t epkName; + uint32_t epkPath; +}; + +struct epw_load_result { + uint32_t eagruntimeJSData; + uint32_t classesWASMData; + uint32_t classesDeobfTEADBGData; + uint32_t classesDeobfWASMData; + uint32_t pressAnyKeyImgData; + uint32_t pressAnyKeyImgMIME; + uint32_t crashImgData; + uint32_t crashImgMIME; + uint32_t faviconImgData; + uint32_t faviconImgMIME; + uint32_t numEPKs; + struct epw_load_result_epk epkData[]; +}; + +struct jspi_unsupported_load_result { + uint32_t crashImgData; + uint32_t crashImgMIME; + uint32_t markup; +}; + +#define LOAD_RESULT_SIZE(numEPKs) (sizeof(struct epw_load_result) + sizeof(struct epw_load_result_epk) * (numEPKs)) + +extern uint8_t getJSPISupported(); + +extern uint32_t getEPWLength(); +extern void memcpyFromEPW(void* dest, uint32_t off, uint32_t len); +extern uint32_t initResult(uint32_t bufLen); +extern void memcpyToResult(uint32_t bufId, const void* src, uint32_t off, uint32_t len); +extern void memcpyFromEPWToResult(uint32_t bufId, uint32_t dest, uint32_t off, uint32_t len); +extern uint32_t initEPWStringResult(uint32_t off, uint32_t len); + +extern void resultFailed(const char* msg); +extern void resultSuccess(const struct epw_load_result* result); +extern void resultJSPIUnsupported(struct jspi_unsupported_load_result* result); + +extern void dbgLog(const char* msg); +extern void dbgErr(const char* msg); + +#endif diff --git a/sources/wasm-gc-teavm-loader/c/main.c b/sources/wasm-gc-teavm-loader/c/main.c new file mode 100644 index 0000000..3ed46c2 --- /dev/null +++ b/sources/wasm-gc-teavm-loader/c/main.c @@ -0,0 +1,355 @@ +/* + * 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. + * + */ + +#include +#include +#include + +#include "epw_header.h" +#include "imports.h" +#include "xz/xz.h" + +static uint32_t initEPWBinaryCompressedHelper(struct epw_slice_compressed* sliceIn, uint32_t epwLen); +static uint32_t initEPWBinaryHelper(struct epw_slice* sliceIn, uint32_t epwLen); +static uint32_t initEPWStringHelper(struct epw_slice* sliceIn, uint32_t epwLen); + +#define SLICE_IN_BOUNDS(pSlice, epwLen) (((struct epw_slice*)(pSlice))->sliceOffset + ((struct epw_slice*)(pSlice))->sliceLength <= (epwLen)) + +// Note: Linux kernel uses 4096 +#define DEC_CHUNK_SIZE 16384 + +static char sprintfBuffer[65]; +static uint8_t inputBuffer[DEC_CHUNK_SIZE]; +static uint8_t outputBuffer[DEC_CHUNK_SIZE]; + +const char *const BAD_ALLOC = "Memory allocation failed"; +const char *const EPW_INCOMPLETE = "EPW file is incomplete"; +const char *const EPW_INVALID = "EPW file is invalid"; + +int main(int argc, char** argv) { + dbgLog("Executing loader WASM binary..."); + + uint32_t epwLen = getEPWLength(); + + snprintf(sprintfBuffer, sizeof(sprintfBuffer), "(Loading a %u byte EPW file)", epwLen); + dbgLog(sprintfBuffer); + + if(epwLen < 384) { + resultFailed(EPW_INCOMPLETE); + return -1; + } + + struct epw_header* headerPtr = (struct epw_header*)malloc(384); + if(!headerPtr) { + resultFailed(BAD_ALLOC); + return -1; + } + + memcpyFromEPW(headerPtr, 0, 384); + + // hehehe + if(*(uint64_t*)&headerPtr->magic != *(const uint64_t*)"EAG$WASM") { + resultFailed("The data provided is not an EPW file"); + return -1; + } + + dbgLog("Checking primary CRC32 checksum..."); + + uint32_t crc32Val = 0; + uint32_t epwRem = epwLen - 16; + uint32_t j; + + xz_crc32_init(); + + while(epwRem > 0) { + j = epwRem < DEC_CHUNK_SIZE ? epwRem : DEC_CHUNK_SIZE; + memcpyFromEPW(inputBuffer, epwLen - epwRem, j); + epwRem -= j; + crc32Val = xz_crc32(inputBuffer, (size_t)j, crc32Val); + } + + if(crc32Val != headerPtr->fileCRC32) { + resultFailed("EPW file has an invalid checksum"); + return -1; + } + + uint32_t numEPKs = headerPtr->numEPKs; + uint32_t headerLen = ((276 + 32 * numEPKs) + 127) & ~127; + + if(headerLen > 384) { + snprintf(sprintfBuffer, sizeof(sprintfBuffer), "Note: Has %u EPK files, extending header to %u bytes", numEPKs, headerLen); + dbgLog(sprintfBuffer); + free(headerPtr); + if(headerLen > epwLen) { + resultFailed(EPW_INCOMPLETE); + return -1; + } + headerPtr = (struct epw_header*)malloc((size_t)headerLen); + if(!headerPtr) { + resultFailed("Memory allocation failed"); + return -1; + } + memcpyFromEPW(headerPtr, 0, headerLen); + } + + if(!getJSPISupported()) { + dbgErr("JSPI is not supported! The client cannot start"); + + struct jspi_unsupported_load_result result; + + dbgLog("Copying crash image..."); + + result.crashImgData = initEPWBinaryHelper(&headerPtr->crashImageData, epwLen); + result.crashImgMIME = initEPWStringHelper(&headerPtr->crashImageMIME, epwLen); + + dbgLog("Decompressing error screen..."); + + result.markup = initEPWBinaryCompressedHelper(&headerPtr->JSPIUnavailableData, epwLen); + if(!result.markup) { + resultFailed(EPW_INVALID); + return -1; + } + + dbgLog("Displaying error screen..."); + + resultJSPIUnsupported(&result); + + return 0; + } + + struct epw_load_result* result = (struct epw_load_result*)malloc(sizeof(struct epw_load_result) + sizeof(struct epw_load_result_epk) * numEPKs); + + dbgLog("Copying non-compressed segments..."); + + result->pressAnyKeyImgData = initEPWBinaryHelper(&headerPtr->pressAnyKeyImageData, epwLen); + if(!result->pressAnyKeyImgData) { + resultFailed(EPW_INVALID); + return -1; + } + + result->pressAnyKeyImgMIME = initEPWStringHelper(&headerPtr->pressAnyKeyImageMIME, epwLen); + if(!result->pressAnyKeyImgMIME) { + resultFailed(EPW_INVALID); + return -1; + } + + result->crashImgData = initEPWBinaryHelper(&headerPtr->crashImageData, epwLen); + if(!result->crashImgData) { + resultFailed(EPW_INVALID); + return -1; + } + + result->crashImgMIME = initEPWStringHelper(&headerPtr->crashImageMIME, epwLen); + if(!result->crashImgMIME) { + resultFailed(EPW_INVALID); + return -1; + } + + result->faviconImgData = initEPWBinaryHelper(&headerPtr->faviconImageData, epwLen); + if(!result->faviconImgData) { + resultFailed(EPW_INVALID); + return -1; + } + + result->faviconImgMIME = initEPWStringHelper(&headerPtr->faviconImageMIME, epwLen); + if(!result->faviconImgMIME) { + resultFailed(EPW_INVALID); + return -1; + } + + dbgLog("Decompressing eagruntime.js..."); + + result->eagruntimeJSData = initEPWBinaryCompressedHelper(&headerPtr->eagruntimeJSData, epwLen); + if(!result->eagruntimeJSData) { + resultFailed(EPW_INVALID); + return -1; + } + + dbgLog("Decompressing classes.wasm..."); + + result->classesWASMData = initEPWBinaryCompressedHelper(&headerPtr->classesWASMData, epwLen); + if(!result->classesWASMData) { + resultFailed(EPW_INVALID); + return -1; + } + + dbgLog("Decompressing classes.wasm.teadbg..."); + + result->classesDeobfTEADBGData = initEPWBinaryCompressedHelper(&headerPtr->classesDeobfTEADBGData, epwLen); + if(!result->classesDeobfTEADBGData) { + resultFailed(EPW_INVALID); + return -1; + } + + dbgLog("Decompressing deobfuscator..."); + + result->classesDeobfWASMData = initEPWBinaryCompressedHelper(&headerPtr->classesDeobfWASMData, epwLen); + if(!result->classesDeobfWASMData) { + resultFailed(EPW_INVALID); + return -1; + } + + result->numEPKs = numEPKs; + + for(uint32_t i = 0; i < numEPKs; ++i) { + struct epw_assets_epk_file* epkFile = &headerPtr->assetsEPKs[i]; + + if(!SLICE_IN_BOUNDS(&epkFile->filePath, epwLen)) { + resultFailed("EPW file contains an invalid offset"); + return -1; + } + + char nameBuffer[33]; + + uint32_t nameStrLen = epkFile->filePath.sliceLength; + if(nameStrLen > 32) { + nameStrLen = 32; + } + + memcpyFromEPW(nameBuffer, epkFile->filePath.sliceOffset, nameStrLen); + nameBuffer[nameStrLen] = 0; + + snprintf(sprintfBuffer, sizeof(sprintfBuffer), "Decompressing assets EPK \"%s\"...", nameBuffer); + dbgLog(sprintfBuffer); + + struct epw_load_result_epk* epkId = &result->epkData[i]; + + epkId->epkData = initEPWBinaryCompressedHelper(&epkFile->fileData, epwLen); + epkId->epkName = initEPWStringHelper(&epkFile->filePath, epwLen); + epkId->epkPath = initEPWStringHelper(&epkFile->loadPath, epwLen); + + if(!epkId->epkData || !epkId->epkName || !epkId->epkPath) { + resultFailed(EPW_INVALID); + return -1; + } + } + + dbgLog("Loader WASM binary executed successfully!"); + + resultSuccess(result); + + return 0; +} + +static uint32_t initEPWBinaryCompressedHelper(struct epw_slice_compressed* sliceIn, uint32_t epwLen) { + if(!SLICE_IN_BOUNDS(sliceIn, epwLen)) { + dbgErr("EPW file contains an invalid compressed offset"); + return 0; + } + + uint32_t bufId = initResult(sliceIn->sliceDecompressedLength); + + struct xz_buf b; + + b.in = inputBuffer; + b.in_pos = 0; + b.in_size = 0; + b.out = outputBuffer; + b.out_pos = 0; + b.out_size = DEC_CHUNK_SIZE; + + struct xz_dec* s; + enum xz_ret ret; + + s = xz_dec_init(XZ_DYNALLOC, (uint32_t)33554432); + if(!s) { + dbgErr("Failed to initialize XZ decompression stream"); + return 0; + } + + uint32_t bufInPos = 0; + uint32_t bufOutPos = 0; + uint32_t remainingIn = sliceIn->sliceCompressedLength; + uint32_t remainingOut = sliceIn->sliceDecompressedLength; + uint32_t toRead = 0; + uint32_t i; + + do { + if(b.in_pos == b.in_size) { + i = (uint32_t)b.in_pos; + if(i > remainingIn) { + dbgErr("Decompression input buffer overflowed"); + xz_dec_end(s); + return 0; + } + remainingIn -= i; + + toRead = remainingIn < DEC_CHUNK_SIZE ? remainingIn : DEC_CHUNK_SIZE; + b.in_pos = 0; + b.in_size = (size_t)toRead; + + memcpyFromEPW(inputBuffer, sliceIn->sliceOffset + bufInPos, toRead); + + bufInPos += toRead; + } + + ret = xz_dec_run(s, &b); + + if(b.out_pos == b.out_size || (ret == XZ_STREAM_END && b.out_pos > 0)) { + i = (uint32_t)b.out_pos; + if(i > remainingOut) { + dbgErr("Decompression output buffer overflowed"); + xz_dec_end(s); + return 0; + } + memcpyToResult(bufId, outputBuffer, bufOutPos, i); + remainingOut -= i; + bufOutPos += i; + b.out_pos = 0; + } + }while(ret == XZ_OK); + + xz_dec_end(s); + + if(ret != XZ_STREAM_END) { + snprintf(sprintfBuffer, sizeof(sprintfBuffer), "Decompression failed, code %u!", (uint32_t)ret); + dbgErr(sprintfBuffer); + return 0; + } + + if(b.in_pos > remainingIn) { + dbgErr("Decompression input buffer overflowed"); + return 0; + } + remainingIn -= (uint32_t)b.in_pos; + + if(remainingIn > 0) { + dbgErr("Decompression completed, but there is still some input data remaining"); + return 0; + } + + return bufId; +} + +static uint32_t initEPWBinaryHelper(struct epw_slice* sliceIn, uint32_t epwLen) { + if(!SLICE_IN_BOUNDS(sliceIn, epwLen)) { + dbgErr("EPW file contains an invalid offset"); + return 0; + }else { + uint32_t ret = initResult(sliceIn->sliceLength); + memcpyFromEPWToResult(ret, 0, sliceIn->sliceOffset, sliceIn->sliceLength); + return ret; + } +} + +static uint32_t initEPWStringHelper(struct epw_slice* sliceIn, uint32_t epwLen) { + if(!SLICE_IN_BOUNDS(sliceIn, epwLen)) { + dbgErr("EPW file contains an invalid offset"); + return 0; + }else { + return initEPWStringResult(sliceIn->sliceOffset, sliceIn->sliceLength); + } +} diff --git a/sources/wasm-gc-teavm-loader/c/xz/xz.h b/sources/wasm-gc-teavm-loader/c/xz/xz.h new file mode 100644 index 0000000..d7628ba --- /dev/null +++ b/sources/wasm-gc-teavm-loader/c/xz/xz.h @@ -0,0 +1,452 @@ +/* SPDX-License-Identifier: 0BSD */ + +/* + * XZ decompressor + * + * Authors: Lasse Collin + * Igor Pavlov + */ + +#ifndef XZ_H +#define XZ_H + +#ifdef __KERNEL__ +# include +# include +#else +# include +# include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* In Linux, this is used to make extern functions static when needed. */ +#ifndef XZ_EXTERN +# define XZ_EXTERN extern +#endif + +/** + * enum xz_mode - Operation mode + * + * @XZ_SINGLE: Single-call mode. This uses less RAM than + * multi-call modes, because the LZMA2 + * dictionary doesn't need to be allocated as + * part of the decoder state. All required data + * structures are allocated at initialization, + * so xz_dec_run() cannot return XZ_MEM_ERROR. + * @XZ_PREALLOC: Multi-call mode with preallocated LZMA2 + * dictionary buffer. All data structures are + * allocated at initialization, so xz_dec_run() + * cannot return XZ_MEM_ERROR. + * @XZ_DYNALLOC: Multi-call mode. The LZMA2 dictionary is + * allocated once the required size has been + * parsed from the stream headers. If the + * allocation fails, xz_dec_run() will return + * XZ_MEM_ERROR. + * + * It is possible to enable support only for a subset of the above + * modes at compile time by defining XZ_DEC_SINGLE, XZ_DEC_PREALLOC, + * or XZ_DEC_DYNALLOC. The xz_dec kernel module is always compiled + * with support for all operation modes, but the preboot code may + * be built with fewer features to minimize code size. + */ +enum xz_mode { + XZ_SINGLE, + XZ_PREALLOC, + XZ_DYNALLOC +}; + +/** + * enum xz_ret - Return codes + * @XZ_OK: Everything is OK so far. More input or more + * output space is required to continue. This + * return code is possible only in multi-call mode + * (XZ_PREALLOC or XZ_DYNALLOC). + * @XZ_STREAM_END: Operation finished successfully. + * @XZ_UNSUPPORTED_CHECK: Integrity check type is not supported. Decoding + * is still possible in multi-call mode by simply + * calling xz_dec_run() again. + * Note that this return value is used only if + * XZ_DEC_ANY_CHECK was defined at build time, + * which is not used in the kernel. Unsupported + * check types return XZ_OPTIONS_ERROR if + * XZ_DEC_ANY_CHECK was not defined at build time. + * @XZ_MEM_ERROR: Allocating memory failed. This return code is + * possible only if the decoder was initialized + * with XZ_DYNALLOC. The amount of memory that was + * tried to be allocated was no more than the + * dict_max argument given to xz_dec_init(). + * @XZ_MEMLIMIT_ERROR: A bigger LZMA2 dictionary would be needed than + * allowed by the dict_max argument given to + * xz_dec_init(). This return value is possible + * only in multi-call mode (XZ_PREALLOC or + * XZ_DYNALLOC); the single-call mode (XZ_SINGLE) + * ignores the dict_max argument. + * @XZ_FORMAT_ERROR: File format was not recognized (wrong magic + * bytes). + * @XZ_OPTIONS_ERROR: This implementation doesn't support the requested + * compression options. In the decoder this means + * that the header CRC32 matches, but the header + * itself specifies something that we don't support. + * @XZ_DATA_ERROR: Compressed data is corrupt. + * @XZ_BUF_ERROR: Cannot make any progress. Details are slightly + * different between multi-call and single-call + * mode; more information below. + * + * In multi-call mode, XZ_BUF_ERROR is returned when two consecutive calls + * to XZ code cannot consume any input and cannot produce any new output. + * This happens when there is no new input available, or the output buffer + * is full while at least one output byte is still pending. Assuming your + * code is not buggy, you can get this error only when decoding a compressed + * stream that is truncated or otherwise corrupt. + * + * In single-call mode, XZ_BUF_ERROR is returned only when the output buffer + * is too small or the compressed input is corrupt in a way that makes the + * decoder produce more output than the caller expected. When it is + * (relatively) clear that the compressed input is truncated, XZ_DATA_ERROR + * is used instead of XZ_BUF_ERROR. + */ +enum xz_ret { + XZ_OK, + XZ_STREAM_END, + XZ_UNSUPPORTED_CHECK, + XZ_MEM_ERROR, + XZ_MEMLIMIT_ERROR, + XZ_FORMAT_ERROR, + XZ_OPTIONS_ERROR, + XZ_DATA_ERROR, + XZ_BUF_ERROR +}; + +/** + * struct xz_buf - Passing input and output buffers to XZ code + * @in: Beginning of the input buffer. This may be NULL if and only + * if in_pos is equal to in_size. + * @in_pos: Current position in the input buffer. This must not exceed + * in_size. + * @in_size: Size of the input buffer + * @out: Beginning of the output buffer. This may be NULL if and only + * if out_pos is equal to out_size. + * @out_pos: Current position in the output buffer. This must not exceed + * out_size. + * @out_size: Size of the output buffer + * + * Only the contents of the output buffer from out[out_pos] onward, and + * the variables in_pos and out_pos are modified by the XZ code. + */ +struct xz_buf { + const uint8_t *in; + size_t in_pos; + size_t in_size; + + uint8_t *out; + size_t out_pos; + size_t out_size; +}; + +/* + * struct xz_dec - Opaque type to hold the XZ decoder state + */ +struct xz_dec; + +/** + * xz_dec_init() - Allocate and initialize a XZ decoder state + * @mode: Operation mode + * @dict_max: Maximum size of the LZMA2 dictionary (history buffer) for + * multi-call decoding. This is ignored in single-call mode + * (mode == XZ_SINGLE). LZMA2 dictionary is always 2^n bytes + * or 2^n + 2^(n-1) bytes (the latter sizes are less common + * in practice), so other values for dict_max don't make sense. + * In the kernel, dictionary sizes of 64 KiB, 128 KiB, 256 KiB, + * 512 KiB, and 1 MiB are probably the only reasonable values, + * except for kernel and initramfs images where a bigger + * dictionary can be fine and useful. + * + * Single-call mode (XZ_SINGLE): xz_dec_run() decodes the whole stream at + * once. The caller must provide enough output space or the decoding will + * fail. The output space is used as the dictionary buffer, which is why + * there is no need to allocate the dictionary as part of the decoder's + * internal state. + * + * Because the output buffer is used as the workspace, streams encoded using + * a big dictionary are not a problem in single-call mode. It is enough that + * the output buffer is big enough to hold the actual uncompressed data; it + * can be smaller than the dictionary size stored in the stream headers. + * + * Multi-call mode with preallocated dictionary (XZ_PREALLOC): dict_max bytes + * of memory is preallocated for the LZMA2 dictionary. This way there is no + * risk that xz_dec_run() could run out of memory, since xz_dec_run() will + * never allocate any memory. Instead, if the preallocated dictionary is too + * small for decoding the given input stream, xz_dec_run() will return + * XZ_MEMLIMIT_ERROR. Thus, it is important to know what kind of data will be + * decoded to avoid allocating excessive amount of memory for the dictionary. + * + * Multi-call mode with dynamically allocated dictionary (XZ_DYNALLOC): + * dict_max specifies the maximum allowed dictionary size that xz_dec_run() + * may allocate once it has parsed the dictionary size from the stream + * headers. This way excessive allocations can be avoided while still + * limiting the maximum memory usage to a sane value to prevent running the + * system out of memory when decompressing streams from untrusted sources. + * + * On success, xz_dec_init() returns a pointer to struct xz_dec, which is + * ready to be used with xz_dec_run(). If memory allocation fails, + * xz_dec_init() returns NULL. + */ +XZ_EXTERN struct xz_dec *xz_dec_init(enum xz_mode mode, uint32_t dict_max); + +/** + * xz_dec_run() - Run the XZ decoder for a single XZ stream + * @s: Decoder state allocated using xz_dec_init() + * @b: Input and output buffers + * + * The possible return values depend on build options and operation mode. + * See enum xz_ret for details. + * + * Note that if an error occurs in single-call mode (return value is not + * XZ_STREAM_END), b->in_pos and b->out_pos are not modified and the + * contents of the output buffer from b->out[b->out_pos] onward are + * undefined. This is true even after XZ_BUF_ERROR, because with some filter + * chains, there may be a second pass over the output buffer, and this pass + * cannot be properly done if the output buffer is truncated. Thus, you + * cannot give the single-call decoder a too small buffer and then expect to + * get that amount valid data from the beginning of the stream. You must use + * the multi-call decoder if you don't want to uncompress the whole stream. + * + * Use xz_dec_run() when XZ data is stored inside some other file format. + * The decoding will stop after one XZ stream has been decompressed. To + * decompress regular .xz files which might have multiple concatenated + * streams, use xz_dec_catrun() instead. + */ +XZ_EXTERN enum xz_ret xz_dec_run(struct xz_dec *s, struct xz_buf *b); + +/** + * xz_dec_catrun() - Run the XZ decoder with support for concatenated streams + * @s: Decoder state allocated using xz_dec_init() + * @b: Input and output buffers + * @finish: This is an int instead of bool to avoid requiring stdbool.h. + * As long as more input might be coming, finish must be false. + * When the caller knows that it has provided all the input to + * the decoder (some possibly still in b->in), it must set finish + * to true. Only when finish is true can this function return + * XZ_STREAM_END to indicate successful decompression of the + * file. In single-call mode (XZ_SINGLE) finish is assumed to + * always be true; the caller-provided value is ignored. + * + * This is like xz_dec_run() except that this makes it easy to decode .xz + * files with multiple streams (multiple .xz files concatenated as is). + * The rarely-used Stream Padding feature is supported too, that is, there + * can be null bytes after or between the streams. The number of null bytes + * must be a multiple of four. + * + * When finish is false and b->in_pos == b->in_size, it is possible that + * XZ_BUF_ERROR isn't returned even when no progress is possible (XZ_OK is + * returned instead). This shouldn't matter because in this situation a + * reasonable caller will attempt to provide more input or set finish to + * true for the next xz_dec_catrun() call anyway. + * + * For any struct xz_dec that has been initialized for multi-call mode: + * Once decoding has been started with xz_dec_run() or xz_dec_catrun(), + * the same function must be used until xz_dec_reset() or xz_dec_end(). + * Switching between the two decoding functions without resetting results + * in undefined behavior. + * + * xz_dec_catrun() is only available if XZ_DEC_CONCATENATED was defined + * at compile time. + */ +XZ_EXTERN enum xz_ret xz_dec_catrun(struct xz_dec *s, struct xz_buf *b, + int finish); + +/** + * xz_dec_reset() - Reset an already allocated decoder state + * @s: Decoder state allocated using xz_dec_init() + * + * This function can be used to reset the multi-call decoder state without + * freeing and reallocating memory with xz_dec_end() and xz_dec_init(). + * + * In single-call mode, xz_dec_reset() is always called in the beginning of + * xz_dec_run(). Thus, explicit call to xz_dec_reset() is useful only in + * multi-call mode. + */ +XZ_EXTERN void xz_dec_reset(struct xz_dec *s); + +/** + * xz_dec_end() - Free the memory allocated for the decoder state + * @s: Decoder state allocated using xz_dec_init(). If s is NULL, + * this function does nothing. + */ +XZ_EXTERN void xz_dec_end(struct xz_dec *s); + +/** + * DOC: MicroLZMA decompressor + * + * This MicroLZMA header format was created for use in EROFS but may be used + * by others too. **In most cases one needs the XZ APIs above instead.** + * + * The compressed format supported by this decoder is a raw LZMA stream + * whose first byte (always 0x00) has been replaced with bitwise-negation + * of the LZMA properties (lc/lp/pb) byte. For example, if lc/lp/pb is + * 3/0/2, the first byte is 0xA2. This way the first byte can never be 0x00. + * Just like with LZMA2, lc + lp <= 4 must be true. The LZMA end-of-stream + * marker must not be used. The unused values are reserved for future use. + * + * These functions aren't used or available in preboot code and thus aren't + * marked with XZ_EXTERN. This avoids warnings about static functions that + * are never defined. + */ + +/* + * struct xz_dec_microlzma - Opaque type to hold the MicroLZMA decoder state + */ +struct xz_dec_microlzma; + +/** + * xz_dec_microlzma_alloc() - Allocate memory for the MicroLZMA decoder + * @mode: XZ_SINGLE or XZ_PREALLOC + * @dict_size: LZMA dictionary size. This must be at least 4 KiB and + * at most 3 GiB. + * + * In contrast to xz_dec_init(), this function only allocates the memory + * and remembers the dictionary size. xz_dec_microlzma_reset() must be used + * before calling xz_dec_microlzma_run(). + * + * The amount of allocated memory is a little less than 30 KiB with XZ_SINGLE. + * With XZ_PREALLOC also a dictionary buffer of dict_size bytes is allocated. + * + * On success, xz_dec_microlzma_alloc() returns a pointer to + * struct xz_dec_microlzma. If memory allocation fails or + * dict_size is invalid, NULL is returned. + */ +extern struct xz_dec_microlzma *xz_dec_microlzma_alloc(enum xz_mode mode, + uint32_t dict_size); + +/** + * xz_dec_microlzma_reset() - Reset the MicroLZMA decoder state + * @s: Decoder state allocated using xz_dec_microlzma_alloc() + * @comp_size: Compressed size of the input stream + * @uncomp_size: Uncompressed size of the input stream. A value smaller + * than the real uncompressed size of the input stream can + * be specified if uncomp_size_is_exact is set to false. + * uncomp_size can never be set to a value larger than the + * expected real uncompressed size because it would eventually + * result in XZ_DATA_ERROR. + * @uncomp_size_is_exact: This is an int instead of bool to avoid + * requiring stdbool.h. This should normally be set to true. + * When this is set to false, error detection is weaker. + */ +extern void xz_dec_microlzma_reset(struct xz_dec_microlzma *s, + uint32_t comp_size, uint32_t uncomp_size, + int uncomp_size_is_exact); + +/** + * xz_dec_microlzma_run() - Run the MicroLZMA decoder + * @s: Decoder state initialized using xz_dec_microlzma_reset() + * @b: Input and output buffers + * + * This works similarly to xz_dec_run() with a few important differences. + * Only the differences are documented here. + * + * The only possible return values are XZ_OK, XZ_STREAM_END, and + * XZ_DATA_ERROR. This function cannot return XZ_BUF_ERROR: if no progress + * is possible due to lack of input data or output space, this function will + * keep returning XZ_OK. Thus, the calling code must be written so that it + * will eventually provide input and output space matching (or exceeding) + * comp_size and uncomp_size arguments given to xz_dec_microlzma_reset(). + * If the caller cannot do this (for example, if the input file is truncated + * or otherwise corrupt), the caller must detect this error by itself to + * avoid an infinite loop. + * + * If the compressed data seems to be corrupt, XZ_DATA_ERROR is returned. + * This can happen also when incorrect dictionary, uncompressed, or + * compressed sizes have been specified. + * + * With XZ_PREALLOC only: As an extra feature, b->out may be NULL to skip over + * uncompressed data. This way the caller doesn't need to provide a temporary + * output buffer for the bytes that will be ignored. + * + * With XZ_SINGLE only: In contrast to xz_dec_run(), the return value XZ_OK + * is also possible and thus XZ_SINGLE is actually a limited multi-call mode. + * After XZ_OK the bytes decoded so far may be read from the output buffer. + * It is possible to continue decoding but the variables b->out and b->out_pos + * MUST NOT be changed by the caller. Increasing the value of b->out_size is + * allowed to make more output space available; one doesn't need to provide + * space for the whole uncompressed data on the first call. The input buffer + * may be changed normally like with XZ_PREALLOC. This way input data can be + * provided from non-contiguous memory. + */ +extern enum xz_ret xz_dec_microlzma_run(struct xz_dec_microlzma *s, + struct xz_buf *b); + +/** + * xz_dec_microlzma_end() - Free the memory allocated for the decoder state + * @s: Decoder state allocated using xz_dec_microlzma_alloc(). + * If s is NULL, this function does nothing. + */ +extern void xz_dec_microlzma_end(struct xz_dec_microlzma *s); + +/* + * Standalone build (userspace build or in-kernel build for boot time use) + * needs a CRC32 implementation. For normal in-kernel use, kernel's own + * CRC32 module is used instead, and users of this module don't need to + * care about the functions below. + */ +#ifndef XZ_INTERNAL_CRC32 +# ifdef __KERNEL__ +# define XZ_INTERNAL_CRC32 0 +# else +# define XZ_INTERNAL_CRC32 1 +# endif +#endif + +/* + * If CRC64 support has been enabled with XZ_USE_CRC64, a CRC64 + * implementation is needed too. + */ +#ifndef XZ_USE_CRC64 +# undef XZ_INTERNAL_CRC64 +# define XZ_INTERNAL_CRC64 0 +#endif +#ifndef XZ_INTERNAL_CRC64 +# ifdef __KERNEL__ +# error Using CRC64 in the kernel has not been implemented. +# else +# define XZ_INTERNAL_CRC64 1 +# endif +#endif + +#if XZ_INTERNAL_CRC32 +/* + * This must be called before any other xz_* function to initialize + * the CRC32 lookup table. + */ +XZ_EXTERN void xz_crc32_init(void); + +/* + * Update CRC32 value using the polynomial from IEEE-802.3. To start a new + * calculation, the third argument must be zero. To continue the calculation, + * the previously returned value is passed as the third argument. + */ +XZ_EXTERN uint32_t xz_crc32(const uint8_t *buf, size_t size, uint32_t crc); +#endif + +#if XZ_INTERNAL_CRC64 +/* + * This must be called before any other xz_* function (except xz_crc32_init()) + * to initialize the CRC64 lookup table. + */ +XZ_EXTERN void xz_crc64_init(void); + +/* + * Update CRC64 value using the polynomial from ECMA-182. To start a new + * calculation, the third argument must be zero. To continue the calculation, + * the previously returned value is passed as the third argument. + */ +XZ_EXTERN uint64_t xz_crc64(const uint8_t *buf, size_t size, uint64_t crc); +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/sources/wasm-gc-teavm-loader/c/xz/xz_config.h b/sources/wasm-gc-teavm-loader/c/xz/xz_config.h new file mode 100644 index 0000000..3245be0 --- /dev/null +++ b/sources/wasm-gc-teavm-loader/c/xz/xz_config.h @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: 0BSD */ + +/* + * Private includes and definitions for userspace use of XZ Embedded + * + * Author: Lasse Collin + */ + +#ifndef XZ_CONFIG_H +#define XZ_CONFIG_H + +/* Uncomment to enable building of xz_dec_catrun(). */ +/* #define XZ_DEC_CONCATENATED */ + +/* Uncomment to enable CRC64 support. */ +/* #define XZ_USE_CRC64 */ + +/* Uncomment as needed to enable BCJ filter decoders. */ +/* #define XZ_DEC_X86 */ +/* #define XZ_DEC_ARM */ +/* #define XZ_DEC_ARMTHUMB */ +/* #define XZ_DEC_ARM64 */ +/* #define XZ_DEC_RISCV */ +/* #define XZ_DEC_POWERPC */ +/* #define XZ_DEC_IA64 */ +/* #define XZ_DEC_SPARC */ + +/* + * Visual Studio 2013 update 2 supports only __inline, not inline. + * MSVC v19.0 / VS 2015 and newer support both. + */ +#if defined(_MSC_VER) && _MSC_VER < 1900 && !defined(inline) +# define inline __inline +#endif + +#include +#include +#include + +#include "xz.h" + +#define kmalloc(size, flags) malloc(size) +#define kfree(ptr) free(ptr) +#define vmalloc(size) malloc(size) +#define vfree(ptr) free(ptr) + +#define memeq(a, b, size) (memcmp(a, b, size) == 0) +#define memzero(buf, size) memset(buf, 0, size) + +#ifndef min +# define min(x, y) ((x) < (y) ? (x) : (y)) +#endif +#define min_t(type, x, y) min(x, y) + +#ifndef fallthrough +# if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202000 +# define fallthrough [[fallthrough]] +# elif defined(__GNUC__) && __GNUC__ >= 7 +# define fallthrough __attribute__((__fallthrough__)) +# else +# define fallthrough do {} while (0) +# endif +#endif + +/* + * Some functions have been marked with __always_inline to keep the + * performance reasonable even when the compiler is optimizing for + * small code size. You may be able to save a few bytes by #defining + * __always_inline to plain inline, but don't complain if the code + * becomes slow. + * + * NOTE: System headers on GNU/Linux may #define this macro already, + * so if you want to change it, you need to #undef it first. + */ +#ifndef __always_inline +# ifdef __GNUC__ +# define __always_inline \ + inline __attribute__((__always_inline__)) +# else +# define __always_inline inline +# endif +#endif + +/* Inline functions to access unaligned unsigned 32-bit integers */ +#ifndef get_unaligned_le32 +static inline uint32_t get_unaligned_le32(const uint8_t *buf) +{ + return (uint32_t)buf[0] + | ((uint32_t)buf[1] << 8) + | ((uint32_t)buf[2] << 16) + | ((uint32_t)buf[3] << 24); +} +#endif + +#ifndef get_unaligned_be32 +static inline uint32_t get_unaligned_be32(const uint8_t *buf) +{ + return (uint32_t)(buf[0] << 24) + | ((uint32_t)buf[1] << 16) + | ((uint32_t)buf[2] << 8) + | (uint32_t)buf[3]; +} +#endif + +#ifndef put_unaligned_le32 +static inline void put_unaligned_le32(uint32_t val, uint8_t *buf) +{ + buf[0] = (uint8_t)val; + buf[1] = (uint8_t)(val >> 8); + buf[2] = (uint8_t)(val >> 16); + buf[3] = (uint8_t)(val >> 24); +} +#endif + +#ifndef put_unaligned_be32 +static inline void put_unaligned_be32(uint32_t val, uint8_t *buf) +{ + buf[0] = (uint8_t)(val >> 24); + buf[1] = (uint8_t)(val >> 16); + buf[2] = (uint8_t)(val >> 8); + buf[3] = (uint8_t)val; +} +#endif + +/* + * Use get_unaligned_le32() also for aligned access for simplicity. On + * little endian systems, #define get_le32(ptr) (*(const uint32_t *)(ptr)) + * could save a few bytes in code size. + */ +#ifndef get_le32 +# define get_le32 get_unaligned_le32 +#endif + +#endif diff --git a/sources/wasm-gc-teavm-loader/c/xz/xz_crc32.c b/sources/wasm-gc-teavm-loader/c/xz/xz_crc32.c new file mode 100644 index 0000000..effdf34 --- /dev/null +++ b/sources/wasm-gc-teavm-loader/c/xz/xz_crc32.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: 0BSD + +/* + * CRC32 using the polynomial from IEEE-802.3 + * + * Authors: Lasse Collin + * Igor Pavlov + */ + +/* + * This is not the fastest implementation, but it is pretty compact. + * The fastest versions of xz_crc32() on modern CPUs without hardware + * accelerated CRC instruction are 3-5 times as fast as this version, + * but they are bigger and use more memory for the lookup table. + */ + +#include "xz_private.h" + +/* + * STATIC_RW_DATA is used in the pre-boot environment on some architectures. + * See for details. + */ +#ifndef STATIC_RW_DATA +# define STATIC_RW_DATA static +#endif + +STATIC_RW_DATA uint32_t xz_crc32_table[256]; + +XZ_EXTERN void xz_crc32_init(void) +{ + const uint32_t poly = 0xEDB88320; + + uint32_t i; + uint32_t j; + uint32_t r; + + for (i = 0; i < 256; ++i) { + r = i; + for (j = 0; j < 8; ++j) + r = (r >> 1) ^ (poly & ~((r & 1) - 1)); + + xz_crc32_table[i] = r; + } + + return; +} + +XZ_EXTERN uint32_t xz_crc32(const uint8_t *buf, size_t size, uint32_t crc) +{ + crc = ~crc; + + while (size != 0) { + crc = xz_crc32_table[*buf++ ^ (crc & 0xFF)] ^ (crc >> 8); + --size; + } + + return ~crc; +} diff --git a/sources/wasm-gc-teavm-loader/c/xz/xz_dec_lzma2.c b/sources/wasm-gc-teavm-loader/c/xz/xz_dec_lzma2.c new file mode 100644 index 0000000..613939f --- /dev/null +++ b/sources/wasm-gc-teavm-loader/c/xz/xz_dec_lzma2.c @@ -0,0 +1,1343 @@ +// SPDX-License-Identifier: 0BSD + +/* + * LZMA2 decoder + * + * Authors: Lasse Collin + * Igor Pavlov + */ + +#include "xz_private.h" +#include "xz_lzma2.h" + +/* + * Range decoder initialization eats the first five bytes of each LZMA chunk. + */ +#define RC_INIT_BYTES 5 + +/* + * Minimum number of usable input buffer to safely decode one LZMA symbol. + * The worst case is that we decode 22 bits using probabilities and 26 + * direct bits. This may decode at maximum of 20 bytes of input. However, + * lzma_main() does an extra normalization before returning, thus we + * need to put 21 here. + */ +#define LZMA_IN_REQUIRED 21 + +/* + * Dictionary (history buffer) + * + * These are always true: + * start <= pos <= full <= end + * pos <= limit <= end + * + * In multi-call mode, also these are true: + * end == size + * size <= size_max + * allocated <= size + * + * Most of these variables are size_t to support single-call mode, + * in which the dictionary variables address the actual output + * buffer directly. + */ +struct dictionary { + /* Beginning of the history buffer */ + uint8_t *buf; + + /* Old position in buf (before decoding more data) */ + size_t start; + + /* Position in buf */ + size_t pos; + + /* + * How full dictionary is. This is used to detect corrupt input that + * would read beyond the beginning of the uncompressed stream. + */ + size_t full; + + /* Write limit; we don't write to buf[limit] or later bytes. */ + size_t limit; + + /* + * End of the dictionary buffer. In multi-call mode, this is + * the same as the dictionary size. In single-call mode, this + * indicates the size of the output buffer. + */ + size_t end; + + /* + * Size of the dictionary as specified in Block Header. This is used + * together with "full" to detect corrupt input that would make us + * read beyond the beginning of the uncompressed stream. + */ + uint32_t size; + + /* + * Maximum allowed dictionary size in multi-call mode. + * This is ignored in single-call mode. + */ + uint32_t size_max; + + /* + * Amount of memory currently allocated for the dictionary. + * This is used only with XZ_DYNALLOC. (With XZ_PREALLOC, + * size_max is always the same as the allocated size.) + */ + uint32_t allocated; + + /* Operation mode */ + enum xz_mode mode; +}; + +/* Range decoder */ +struct rc_dec { + uint32_t range; + uint32_t code; + + /* + * Number of initializing bytes remaining to be read + * by rc_read_init(). + */ + uint32_t init_bytes_left; + + /* + * Buffer from which we read our input. It can be either + * temp.buf or the caller-provided input buffer. + */ + const uint8_t *in; + size_t in_pos; + size_t in_limit; +}; + +/* Probabilities for a length decoder. */ +struct lzma_len_dec { + /* Probability of match length being at least 10 */ + uint16_t choice; + + /* Probability of match length being at least 18 */ + uint16_t choice2; + + /* Probabilities for match lengths 2-9 */ + uint16_t low[POS_STATES_MAX][LEN_LOW_SYMBOLS]; + + /* Probabilities for match lengths 10-17 */ + uint16_t mid[POS_STATES_MAX][LEN_MID_SYMBOLS]; + + /* Probabilities for match lengths 18-273 */ + uint16_t high[LEN_HIGH_SYMBOLS]; +}; + +struct lzma_dec { + /* Distances of latest four matches */ + uint32_t rep0; + uint32_t rep1; + uint32_t rep2; + uint32_t rep3; + + /* Types of the most recently seen LZMA symbols */ + enum lzma_state state; + + /* + * Length of a match. This is updated so that dict_repeat can + * be called again to finish repeating the whole match. + */ + uint32_t len; + + /* + * LZMA properties or related bit masks (number of literal + * context bits, a mask derived from the number of literal + * position bits, and a mask derived from the number + * position bits) + */ + uint32_t lc; + uint32_t literal_pos_mask; /* (1 << lp) - 1 */ + uint32_t pos_mask; /* (1 << pb) - 1 */ + + /* If 1, it's a match. Otherwise it's a single 8-bit literal. */ + uint16_t is_match[STATES][POS_STATES_MAX]; + + /* If 1, it's a repeated match. The distance is one of rep0 .. rep3. */ + uint16_t is_rep[STATES]; + + /* + * If 0, distance of a repeated match is rep0. + * Otherwise check is_rep1. + */ + uint16_t is_rep0[STATES]; + + /* + * If 0, distance of a repeated match is rep1. + * Otherwise check is_rep2. + */ + uint16_t is_rep1[STATES]; + + /* If 0, distance of a repeated match is rep2. Otherwise it is rep3. */ + uint16_t is_rep2[STATES]; + + /* + * If 1, the repeated match has length of one byte. Otherwise + * the length is decoded from rep_len_decoder. + */ + uint16_t is_rep0_long[STATES][POS_STATES_MAX]; + + /* + * Probability tree for the highest two bits of the match + * distance. There is a separate probability tree for match + * lengths of 2 (i.e. MATCH_LEN_MIN), 3, 4, and [5, 273]. + */ + uint16_t dist_slot[DIST_STATES][DIST_SLOTS]; + + /* + * Probility trees for additional bits for match distance + * when the distance is in the range [4, 127]. + */ + uint16_t dist_special[FULL_DISTANCES - DIST_MODEL_END]; + + /* + * Probability tree for the lowest four bits of a match + * distance that is equal to or greater than 128. + */ + uint16_t dist_align[ALIGN_SIZE]; + + /* Length of a normal match */ + struct lzma_len_dec match_len_dec; + + /* Length of a repeated match */ + struct lzma_len_dec rep_len_dec; + + /* Probabilities of literals */ + uint16_t literal[LITERAL_CODERS_MAX][LITERAL_CODER_SIZE]; +}; + +struct lzma2_dec { + /* Position in xz_dec_lzma2_run(). */ + enum lzma2_seq { + SEQ_CONTROL, + SEQ_UNCOMPRESSED_1, + SEQ_UNCOMPRESSED_2, + SEQ_COMPRESSED_0, + SEQ_COMPRESSED_1, + SEQ_PROPERTIES, + SEQ_LZMA_PREPARE, + SEQ_LZMA_RUN, + SEQ_COPY + } sequence; + + /* Next position after decoding the compressed size of the chunk. */ + enum lzma2_seq next_sequence; + + /* Uncompressed size of LZMA chunk (2 MiB at maximum) */ + uint32_t uncompressed; + + /* + * Compressed size of LZMA chunk or compressed/uncompressed + * size of uncompressed chunk (64 KiB at maximum) + */ + uint32_t compressed; + + /* + * True if dictionary reset is needed. This is false before + * the first chunk (LZMA or uncompressed). + */ + bool need_dict_reset; + + /* + * True if new LZMA properties are needed. This is false + * before the first LZMA chunk. + */ + bool need_props; + +#ifdef XZ_DEC_MICROLZMA + bool pedantic_microlzma; +#endif +}; + +struct xz_dec_lzma2 { + /* + * The order below is important on x86 to reduce code size and + * it shouldn't hurt on other platforms. Everything up to and + * including lzma.pos_mask are in the first 128 bytes on x86-32, + * which allows using smaller instructions to access those + * variables. On x86-64, fewer variables fit into the first 128 + * bytes, but this is still the best order without sacrificing + * the readability by splitting the structures. + */ + struct rc_dec rc; + struct dictionary dict; + struct lzma2_dec lzma2; + struct lzma_dec lzma; + + /* + * Temporary buffer which holds small number of input bytes between + * decoder calls. See lzma2_lzma() for details. + */ + struct { + uint32_t size; + uint8_t buf[3 * LZMA_IN_REQUIRED]; + } temp; +}; + +/************** + * Dictionary * + **************/ + +/* + * Reset the dictionary state. When in single-call mode, set up the beginning + * of the dictionary to point to the actual output buffer. + */ +static void dict_reset(struct dictionary *dict, struct xz_buf *b) +{ + if (DEC_IS_SINGLE(dict->mode)) { + dict->buf = b->out + b->out_pos; + dict->end = b->out_size - b->out_pos; + } + + dict->start = 0; + dict->pos = 0; + dict->limit = 0; + dict->full = 0; +} + +/* Set dictionary write limit */ +static void dict_limit(struct dictionary *dict, size_t out_max) +{ + if (dict->end - dict->pos <= out_max) + dict->limit = dict->end; + else + dict->limit = dict->pos + out_max; +} + +/* Return true if at least one byte can be written into the dictionary. */ +static inline bool dict_has_space(const struct dictionary *dict) +{ + return dict->pos < dict->limit; +} + +/* + * Get a byte from the dictionary at the given distance. The distance is + * assumed to valid, or as a special case, zero when the dictionary is + * still empty. This special case is needed for single-call decoding to + * avoid writing a '\0' to the end of the destination buffer. + */ +static inline uint32_t dict_get(const struct dictionary *dict, uint32_t dist) +{ + size_t offset = dict->pos - dist - 1; + + if (dist >= dict->pos) + offset += dict->end; + + return dict->full > 0 ? dict->buf[offset] : 0; +} + +/* + * Put one byte into the dictionary. It is assumed that there is space for it. + */ +static inline void dict_put(struct dictionary *dict, uint8_t byte) +{ + dict->buf[dict->pos++] = byte; + + if (dict->full < dict->pos) + dict->full = dict->pos; +} + +/* + * Repeat given number of bytes from the given distance. If the distance is + * invalid, false is returned. On success, true is returned and *len is + * updated to indicate how many bytes were left to be repeated. + */ +static bool dict_repeat(struct dictionary *dict, uint32_t *len, uint32_t dist) +{ + size_t back; + uint32_t left; + + if (dist >= dict->full || dist >= dict->size) + return false; + + left = min_t(size_t, dict->limit - dict->pos, *len); + *len -= left; + + back = dict->pos - dist - 1; + if (dist >= dict->pos) + back += dict->end; + + do { + dict->buf[dict->pos++] = dict->buf[back++]; + if (back == dict->end) + back = 0; + } while (--left > 0); + + if (dict->full < dict->pos) + dict->full = dict->pos; + + return true; +} + +/* Copy uncompressed data as is from input to dictionary and output buffers. */ +static void dict_uncompressed(struct dictionary *dict, struct xz_buf *b, + uint32_t *left) +{ + size_t copy_size; + + while (*left > 0 && b->in_pos < b->in_size + && b->out_pos < b->out_size) { + copy_size = min(b->in_size - b->in_pos, + b->out_size - b->out_pos); + if (copy_size > dict->end - dict->pos) + copy_size = dict->end - dict->pos; + if (copy_size > *left) + copy_size = *left; + + *left -= copy_size; + + /* + * If doing in-place decompression in single-call mode and the + * uncompressed size of the file is larger than the caller + * thought (i.e. it is invalid input!), the buffers below may + * overlap and cause undefined behavior with memcpy(). + * With valid inputs memcpy() would be fine here. + */ + memmove(dict->buf + dict->pos, b->in + b->in_pos, copy_size); + dict->pos += copy_size; + + if (dict->full < dict->pos) + dict->full = dict->pos; + + if (DEC_IS_MULTI(dict->mode)) { + if (dict->pos == dict->end) + dict->pos = 0; + + /* + * Like above but for multi-call mode: use memmove() + * to avoid undefined behavior with invalid input. + */ + memmove(b->out + b->out_pos, b->in + b->in_pos, + copy_size); + } + + dict->start = dict->pos; + + b->out_pos += copy_size; + b->in_pos += copy_size; + } +} + +#ifdef XZ_DEC_MICROLZMA +# define DICT_FLUSH_SUPPORTS_SKIPPING true +#else +# define DICT_FLUSH_SUPPORTS_SKIPPING false +#endif + +/* + * Flush pending data from dictionary to b->out. It is assumed that there is + * enough space in b->out. This is guaranteed because caller uses dict_limit() + * before decoding data into the dictionary. + */ +static uint32_t dict_flush(struct dictionary *dict, struct xz_buf *b) +{ + size_t copy_size = dict->pos - dict->start; + + if (DEC_IS_MULTI(dict->mode)) { + if (dict->pos == dict->end) + dict->pos = 0; + + /* + * These buffers cannot overlap even if doing in-place + * decompression because in multi-call mode dict->buf + * has been allocated by us in this file; it's not + * provided by the caller like in single-call mode. + * + * With MicroLZMA, b->out can be NULL to skip bytes that + * the caller doesn't need. This cannot be done with XZ + * because it would break BCJ filters. + */ + if (!DICT_FLUSH_SUPPORTS_SKIPPING || b->out != NULL) + memcpy(b->out + b->out_pos, dict->buf + dict->start, + copy_size); + } + + dict->start = dict->pos; + b->out_pos += copy_size; + return copy_size; +} + +/***************** + * Range decoder * + *****************/ + +/* Reset the range decoder. */ +static void rc_reset(struct rc_dec *rc) +{ + rc->range = (uint32_t)-1; + rc->code = 0; + rc->init_bytes_left = RC_INIT_BYTES; +} + +/* + * Read the first five initial bytes into rc->code if they haven't been + * read already. (Yes, the first byte gets completely ignored.) + */ +static bool rc_read_init(struct rc_dec *rc, struct xz_buf *b) +{ + while (rc->init_bytes_left > 0) { + if (b->in_pos == b->in_size) + return false; + + rc->code = (rc->code << 8) + b->in[b->in_pos++]; + --rc->init_bytes_left; + } + + return true; +} + +/* Return true if there may not be enough input for the next decoding loop. */ +static inline bool rc_limit_exceeded(const struct rc_dec *rc) +{ + return rc->in_pos > rc->in_limit; +} + +/* + * Return true if it is possible (from point of view of range decoder) that + * we have reached the end of the LZMA chunk. + */ +static inline bool rc_is_finished(const struct rc_dec *rc) +{ + return rc->code == 0; +} + +/* Read the next input byte if needed. */ +static __always_inline void rc_normalize(struct rc_dec *rc) +{ + if (rc->range < RC_TOP_VALUE) { + rc->range <<= RC_SHIFT_BITS; + rc->code = (rc->code << RC_SHIFT_BITS) + rc->in[rc->in_pos++]; + } +} + +/* + * Decode one bit. In some versions, this function has been split in three + * functions so that the compiler is supposed to be able to more easily avoid + * an extra branch. In this particular version of the LZMA decoder, this + * doesn't seem to be a good idea (tested with GCC 3.3.6, 3.4.6, and 4.3.3 + * on x86). Using a non-split version results in nicer looking code too. + * + * NOTE: This must return an int. Do not make it return a bool or the speed + * of the code generated by GCC 3.x decreases 10-15 %. (GCC 4.3 doesn't care, + * and it generates 10-20 % faster code than GCC 3.x from this file anyway.) + */ +static __always_inline int rc_bit(struct rc_dec *rc, uint16_t *prob) +{ + uint32_t bound; + int bit; + + rc_normalize(rc); + bound = (rc->range >> RC_BIT_MODEL_TOTAL_BITS) * *prob; + if (rc->code < bound) { + rc->range = bound; + *prob += (RC_BIT_MODEL_TOTAL - *prob) >> RC_MOVE_BITS; + bit = 0; + } else { + rc->range -= bound; + rc->code -= bound; + *prob -= *prob >> RC_MOVE_BITS; + bit = 1; + } + + return bit; +} + +/* Decode a bittree starting from the most significant bit. */ +static __always_inline uint32_t rc_bittree(struct rc_dec *rc, + uint16_t *probs, uint32_t limit) +{ + uint32_t symbol = 1; + + do { + if (rc_bit(rc, &probs[symbol])) + symbol = (symbol << 1) + 1; + else + symbol <<= 1; + } while (symbol < limit); + + return symbol; +} + +/* Decode a bittree starting from the least significant bit. */ +static __always_inline void rc_bittree_reverse(struct rc_dec *rc, + uint16_t *probs, + uint32_t *dest, uint32_t limit) +{ + uint32_t symbol = 1; + uint32_t i = 0; + + do { + if (rc_bit(rc, &probs[symbol])) { + symbol = (symbol << 1) + 1; + *dest += 1 << i; + } else { + symbol <<= 1; + } + } while (++i < limit); +} + +/* Decode direct bits (fixed fifty-fifty probability) */ +static inline void rc_direct(struct rc_dec *rc, uint32_t *dest, uint32_t limit) +{ + uint32_t mask; + + do { + rc_normalize(rc); + rc->range >>= 1; + rc->code -= rc->range; + mask = (uint32_t)0 - (rc->code >> 31); + rc->code += rc->range & mask; + *dest = (*dest << 1) + (mask + 1); + } while (--limit > 0); +} + +/******** + * LZMA * + ********/ + +/* Get pointer to literal coder probability array. */ +static uint16_t *lzma_literal_probs(struct xz_dec_lzma2 *s) +{ + uint32_t prev_byte = dict_get(&s->dict, 0); + uint32_t low = prev_byte >> (8 - s->lzma.lc); + uint32_t high = (s->dict.pos & s->lzma.literal_pos_mask) << s->lzma.lc; + return s->lzma.literal[low + high]; +} + +/* Decode a literal (one 8-bit byte) */ +static void lzma_literal(struct xz_dec_lzma2 *s) +{ + uint16_t *probs; + uint32_t symbol; + uint32_t match_byte; + uint32_t match_bit; + uint32_t offset; + uint32_t i; + + probs = lzma_literal_probs(s); + + if (lzma_state_is_literal(s->lzma.state)) { + symbol = rc_bittree(&s->rc, probs, 0x100); + } else { + symbol = 1; + match_byte = dict_get(&s->dict, s->lzma.rep0) << 1; + offset = 0x100; + + do { + match_bit = match_byte & offset; + match_byte <<= 1; + i = offset + match_bit + symbol; + + if (rc_bit(&s->rc, &probs[i])) { + symbol = (symbol << 1) + 1; + offset &= match_bit; + } else { + symbol <<= 1; + offset &= ~match_bit; + } + } while (symbol < 0x100); + } + + dict_put(&s->dict, (uint8_t)symbol); + lzma_state_literal(&s->lzma.state); +} + +/* Decode the length of the match into s->lzma.len. */ +static void lzma_len(struct xz_dec_lzma2 *s, struct lzma_len_dec *l, + uint32_t pos_state) +{ + uint16_t *probs; + uint32_t limit; + + if (!rc_bit(&s->rc, &l->choice)) { + probs = l->low[pos_state]; + limit = LEN_LOW_SYMBOLS; + s->lzma.len = MATCH_LEN_MIN; + } else { + if (!rc_bit(&s->rc, &l->choice2)) { + probs = l->mid[pos_state]; + limit = LEN_MID_SYMBOLS; + s->lzma.len = MATCH_LEN_MIN + LEN_LOW_SYMBOLS; + } else { + probs = l->high; + limit = LEN_HIGH_SYMBOLS; + s->lzma.len = MATCH_LEN_MIN + LEN_LOW_SYMBOLS + + LEN_MID_SYMBOLS; + } + } + + s->lzma.len += rc_bittree(&s->rc, probs, limit) - limit; +} + +/* Decode a match. The distance will be stored in s->lzma.rep0. */ +static void lzma_match(struct xz_dec_lzma2 *s, uint32_t pos_state) +{ + uint16_t *probs; + uint32_t dist_slot; + uint32_t limit; + + lzma_state_match(&s->lzma.state); + + s->lzma.rep3 = s->lzma.rep2; + s->lzma.rep2 = s->lzma.rep1; + s->lzma.rep1 = s->lzma.rep0; + + lzma_len(s, &s->lzma.match_len_dec, pos_state); + + probs = s->lzma.dist_slot[lzma_get_dist_state(s->lzma.len)]; + dist_slot = rc_bittree(&s->rc, probs, DIST_SLOTS) - DIST_SLOTS; + + if (dist_slot < DIST_MODEL_START) { + s->lzma.rep0 = dist_slot; + } else { + limit = (dist_slot >> 1) - 1; + s->lzma.rep0 = 2 + (dist_slot & 1); + + if (dist_slot < DIST_MODEL_END) { + s->lzma.rep0 <<= limit; + probs = s->lzma.dist_special + s->lzma.rep0 + - dist_slot - 1; + rc_bittree_reverse(&s->rc, probs, + &s->lzma.rep0, limit); + } else { + rc_direct(&s->rc, &s->lzma.rep0, limit - ALIGN_BITS); + s->lzma.rep0 <<= ALIGN_BITS; + rc_bittree_reverse(&s->rc, s->lzma.dist_align, + &s->lzma.rep0, ALIGN_BITS); + } + } +} + +/* + * Decode a repeated match. The distance is one of the four most recently + * seen matches. The distance will be stored in s->lzma.rep0. + */ +static void lzma_rep_match(struct xz_dec_lzma2 *s, uint32_t pos_state) +{ + uint32_t tmp; + + if (!rc_bit(&s->rc, &s->lzma.is_rep0[s->lzma.state])) { + if (!rc_bit(&s->rc, &s->lzma.is_rep0_long[ + s->lzma.state][pos_state])) { + lzma_state_short_rep(&s->lzma.state); + s->lzma.len = 1; + return; + } + } else { + if (!rc_bit(&s->rc, &s->lzma.is_rep1[s->lzma.state])) { + tmp = s->lzma.rep1; + } else { + if (!rc_bit(&s->rc, &s->lzma.is_rep2[s->lzma.state])) { + tmp = s->lzma.rep2; + } else { + tmp = s->lzma.rep3; + s->lzma.rep3 = s->lzma.rep2; + } + + s->lzma.rep2 = s->lzma.rep1; + } + + s->lzma.rep1 = s->lzma.rep0; + s->lzma.rep0 = tmp; + } + + lzma_state_long_rep(&s->lzma.state); + lzma_len(s, &s->lzma.rep_len_dec, pos_state); +} + +/* LZMA decoder core */ +static bool lzma_main(struct xz_dec_lzma2 *s) +{ + uint32_t pos_state; + + /* + * If the dictionary was reached during the previous call, try to + * finish the possibly pending repeat in the dictionary. + */ + if (dict_has_space(&s->dict) && s->lzma.len > 0) + dict_repeat(&s->dict, &s->lzma.len, s->lzma.rep0); + + /* + * Decode more LZMA symbols. One iteration may consume up to + * LZMA_IN_REQUIRED - 1 bytes. + */ + while (dict_has_space(&s->dict) && !rc_limit_exceeded(&s->rc)) { + pos_state = s->dict.pos & s->lzma.pos_mask; + + if (!rc_bit(&s->rc, &s->lzma.is_match[ + s->lzma.state][pos_state])) { + lzma_literal(s); + } else { + if (rc_bit(&s->rc, &s->lzma.is_rep[s->lzma.state])) + lzma_rep_match(s, pos_state); + else + lzma_match(s, pos_state); + + if (!dict_repeat(&s->dict, &s->lzma.len, s->lzma.rep0)) + return false; + } + } + + /* + * Having the range decoder always normalized when we are outside + * this function makes it easier to correctly handle end of the chunk. + */ + rc_normalize(&s->rc); + + return true; +} + +/* + * Reset the LZMA decoder and range decoder state. Dictionary is not reset + * here, because LZMA state may be reset without resetting the dictionary. + */ +static void lzma_reset(struct xz_dec_lzma2 *s) +{ + uint16_t *probs; + size_t i; + + s->lzma.state = STATE_LIT_LIT; + s->lzma.rep0 = 0; + s->lzma.rep1 = 0; + s->lzma.rep2 = 0; + s->lzma.rep3 = 0; + s->lzma.len = 0; + + /* + * All probabilities are initialized to the same value. This hack + * makes the code smaller by avoiding a separate loop for each + * probability array. + * + * This could be optimized so that only that part of literal + * probabilities that are actually required. In the common case + * we would write 12 KiB less. + */ + probs = s->lzma.is_match[0]; + for (i = 0; i < PROBS_TOTAL; ++i) + probs[i] = RC_BIT_MODEL_TOTAL / 2; + + rc_reset(&s->rc); +} + +/* + * Decode and validate LZMA properties (lc/lp/pb) and calculate the bit masks + * from the decoded lp and pb values. On success, the LZMA decoder state is + * reset and true is returned. + */ +static bool lzma_props(struct xz_dec_lzma2 *s, uint8_t props) +{ + if (props > (4 * 5 + 4) * 9 + 8) + return false; + + s->lzma.pos_mask = 0; + while (props >= 9 * 5) { + props -= 9 * 5; + ++s->lzma.pos_mask; + } + + s->lzma.pos_mask = (1 << s->lzma.pos_mask) - 1; + + s->lzma.literal_pos_mask = 0; + while (props >= 9) { + props -= 9; + ++s->lzma.literal_pos_mask; + } + + s->lzma.lc = props; + + if (s->lzma.lc + s->lzma.literal_pos_mask > 4) + return false; + + s->lzma.literal_pos_mask = (1 << s->lzma.literal_pos_mask) - 1; + + lzma_reset(s); + + return true; +} + +/********* + * LZMA2 * + *********/ + +/* + * The LZMA decoder assumes that if the input limit (s->rc.in_limit) hasn't + * been exceeded, it is safe to read up to LZMA_IN_REQUIRED bytes. This + * wrapper function takes care of making the LZMA decoder's assumption safe. + * + * As long as there is plenty of input left to be decoded in the current LZMA + * chunk, we decode directly from the caller-supplied input buffer until + * there's LZMA_IN_REQUIRED bytes left. Those remaining bytes are copied into + * s->temp.buf, which (hopefully) gets filled on the next call to this + * function. We decode a few bytes from the temporary buffer so that we can + * continue decoding from the caller-supplied input buffer again. + */ +static bool lzma2_lzma(struct xz_dec_lzma2 *s, struct xz_buf *b) +{ + size_t in_avail; + uint32_t tmp; + + in_avail = b->in_size - b->in_pos; + if (s->temp.size > 0 || s->lzma2.compressed == 0) { + tmp = 2 * LZMA_IN_REQUIRED - s->temp.size; + if (tmp > s->lzma2.compressed - s->temp.size) + tmp = s->lzma2.compressed - s->temp.size; + if (tmp > in_avail) + tmp = in_avail; + + memcpy(s->temp.buf + s->temp.size, b->in + b->in_pos, tmp); + + if (s->temp.size + tmp == s->lzma2.compressed) { + memzero(s->temp.buf + s->temp.size + tmp, + sizeof(s->temp.buf) + - s->temp.size - tmp); + s->rc.in_limit = s->temp.size + tmp; + } else if (s->temp.size + tmp < LZMA_IN_REQUIRED) { + s->temp.size += tmp; + b->in_pos += tmp; + return true; + } else { + s->rc.in_limit = s->temp.size + tmp - LZMA_IN_REQUIRED; + } + + s->rc.in = s->temp.buf; + s->rc.in_pos = 0; + + if (!lzma_main(s) || s->rc.in_pos > s->temp.size + tmp) + return false; + + s->lzma2.compressed -= s->rc.in_pos; + + if (s->rc.in_pos < s->temp.size) { + s->temp.size -= s->rc.in_pos; + memmove(s->temp.buf, s->temp.buf + s->rc.in_pos, + s->temp.size); + return true; + } + + b->in_pos += s->rc.in_pos - s->temp.size; + s->temp.size = 0; + } + + in_avail = b->in_size - b->in_pos; + if (in_avail >= LZMA_IN_REQUIRED) { + s->rc.in = b->in; + s->rc.in_pos = b->in_pos; + + if (in_avail >= s->lzma2.compressed + LZMA_IN_REQUIRED) + s->rc.in_limit = b->in_pos + s->lzma2.compressed; + else + s->rc.in_limit = b->in_size - LZMA_IN_REQUIRED; + + if (!lzma_main(s)) + return false; + + in_avail = s->rc.in_pos - b->in_pos; + if (in_avail > s->lzma2.compressed) + return false; + + s->lzma2.compressed -= in_avail; + b->in_pos = s->rc.in_pos; + } + + in_avail = b->in_size - b->in_pos; + if (in_avail < LZMA_IN_REQUIRED) { + if (in_avail > s->lzma2.compressed) + in_avail = s->lzma2.compressed; + + memcpy(s->temp.buf, b->in + b->in_pos, in_avail); + s->temp.size = in_avail; + b->in_pos += in_avail; + } + + return true; +} + +/* + * Take care of the LZMA2 control layer, and forward the job of actual LZMA + * decoding or copying of uncompressed chunks to other functions. + */ +XZ_EXTERN enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, + struct xz_buf *b) +{ + uint32_t tmp; + + while (b->in_pos < b->in_size || s->lzma2.sequence == SEQ_LZMA_RUN) { + switch (s->lzma2.sequence) { + case SEQ_CONTROL: + /* + * LZMA2 control byte + * + * Exact values: + * 0x00 End marker + * 0x01 Dictionary reset followed by + * an uncompressed chunk + * 0x02 Uncompressed chunk (no dictionary reset) + * + * Highest three bits (s->control & 0xE0): + * 0xE0 Dictionary reset, new properties and state + * reset, followed by LZMA compressed chunk + * 0xC0 New properties and state reset, followed + * by LZMA compressed chunk (no dictionary + * reset) + * 0xA0 State reset using old properties, + * followed by LZMA compressed chunk (no + * dictionary reset) + * 0x80 LZMA chunk (no dictionary or state reset) + * + * For LZMA compressed chunks, the lowest five bits + * (s->control & 1F) are the highest bits of the + * uncompressed size (bits 16-20). + * + * A new LZMA2 stream must begin with a dictionary + * reset. The first LZMA chunk must set new + * properties and reset the LZMA state. + * + * Values that don't match anything described above + * are invalid and we return XZ_DATA_ERROR. + */ + tmp = b->in[b->in_pos++]; + + if (tmp == 0x00) + return XZ_STREAM_END; + + if (tmp >= 0xE0 || tmp == 0x01) { + s->lzma2.need_props = true; + s->lzma2.need_dict_reset = false; + dict_reset(&s->dict, b); + } else if (s->lzma2.need_dict_reset) { + return XZ_DATA_ERROR; + } + + if (tmp >= 0x80) { + s->lzma2.uncompressed = (tmp & 0x1F) << 16; + s->lzma2.sequence = SEQ_UNCOMPRESSED_1; + + if (tmp >= 0xC0) { + /* + * When there are new properties, + * state reset is done at + * SEQ_PROPERTIES. + */ + s->lzma2.need_props = false; + s->lzma2.next_sequence + = SEQ_PROPERTIES; + + } else if (s->lzma2.need_props) { + return XZ_DATA_ERROR; + + } else { + s->lzma2.next_sequence + = SEQ_LZMA_PREPARE; + if (tmp >= 0xA0) + lzma_reset(s); + } + } else { + if (tmp > 0x02) + return XZ_DATA_ERROR; + + s->lzma2.sequence = SEQ_COMPRESSED_0; + s->lzma2.next_sequence = SEQ_COPY; + } + + break; + + case SEQ_UNCOMPRESSED_1: + s->lzma2.uncompressed + += (uint32_t)b->in[b->in_pos++] << 8; + s->lzma2.sequence = SEQ_UNCOMPRESSED_2; + break; + + case SEQ_UNCOMPRESSED_2: + s->lzma2.uncompressed + += (uint32_t)b->in[b->in_pos++] + 1; + s->lzma2.sequence = SEQ_COMPRESSED_0; + break; + + case SEQ_COMPRESSED_0: + s->lzma2.compressed + = (uint32_t)b->in[b->in_pos++] << 8; + s->lzma2.sequence = SEQ_COMPRESSED_1; + break; + + case SEQ_COMPRESSED_1: + s->lzma2.compressed + += (uint32_t)b->in[b->in_pos++] + 1; + s->lzma2.sequence = s->lzma2.next_sequence; + break; + + case SEQ_PROPERTIES: + if (!lzma_props(s, b->in[b->in_pos++])) + return XZ_DATA_ERROR; + + s->lzma2.sequence = SEQ_LZMA_PREPARE; + + fallthrough; + + case SEQ_LZMA_PREPARE: + if (s->lzma2.compressed < RC_INIT_BYTES) + return XZ_DATA_ERROR; + + if (!rc_read_init(&s->rc, b)) + return XZ_OK; + + s->lzma2.compressed -= RC_INIT_BYTES; + s->lzma2.sequence = SEQ_LZMA_RUN; + + fallthrough; + + case SEQ_LZMA_RUN: + /* + * Set dictionary limit to indicate how much we want + * to be encoded at maximum. Decode new data into the + * dictionary. Flush the new data from dictionary to + * b->out. Check if we finished decoding this chunk. + * In case the dictionary got full but we didn't fill + * the output buffer yet, we may run this loop + * multiple times without changing s->lzma2.sequence. + */ + dict_limit(&s->dict, min_t(size_t, + b->out_size - b->out_pos, + s->lzma2.uncompressed)); + if (!lzma2_lzma(s, b)) + return XZ_DATA_ERROR; + + s->lzma2.uncompressed -= dict_flush(&s->dict, b); + + if (s->lzma2.uncompressed == 0) { + if (s->lzma2.compressed > 0 || s->lzma.len > 0 + || !rc_is_finished(&s->rc)) + return XZ_DATA_ERROR; + + rc_reset(&s->rc); + s->lzma2.sequence = SEQ_CONTROL; + + } else if (b->out_pos == b->out_size + || (b->in_pos == b->in_size + && s->temp.size + < s->lzma2.compressed)) { + return XZ_OK; + } + + break; + + case SEQ_COPY: + dict_uncompressed(&s->dict, b, &s->lzma2.compressed); + if (s->lzma2.compressed > 0) + return XZ_OK; + + s->lzma2.sequence = SEQ_CONTROL; + break; + } + } + + return XZ_OK; +} + +XZ_EXTERN struct xz_dec_lzma2 *xz_dec_lzma2_create(enum xz_mode mode, + uint32_t dict_max) +{ + struct xz_dec_lzma2 *s = kmalloc(sizeof(*s), GFP_KERNEL); + if (s == NULL) + return NULL; + + s->dict.mode = mode; + s->dict.size_max = dict_max; + + if (DEC_IS_PREALLOC(mode)) { + s->dict.buf = vmalloc(dict_max); + if (s->dict.buf == NULL) { + kfree(s); + return NULL; + } + } else if (DEC_IS_DYNALLOC(mode)) { + s->dict.buf = NULL; + s->dict.allocated = 0; + } + + return s; +} + +XZ_EXTERN enum xz_ret xz_dec_lzma2_reset(struct xz_dec_lzma2 *s, uint8_t props) +{ + /* This limits dictionary size to 3 GiB to keep parsing simpler. */ + if (props > 39) + return XZ_OPTIONS_ERROR; + + s->dict.size = 2 + (props & 1); + s->dict.size <<= (props >> 1) + 11; + + if (DEC_IS_MULTI(s->dict.mode)) { + if (s->dict.size > s->dict.size_max) + return XZ_MEMLIMIT_ERROR; + + s->dict.end = s->dict.size; + + if (DEC_IS_DYNALLOC(s->dict.mode)) { + if (s->dict.allocated < s->dict.size) { + s->dict.allocated = s->dict.size; + vfree(s->dict.buf); + s->dict.buf = vmalloc(s->dict.size); + if (s->dict.buf == NULL) { + s->dict.allocated = 0; + return XZ_MEM_ERROR; + } + } + } + } + + s->lzma2.sequence = SEQ_CONTROL; + s->lzma2.need_dict_reset = true; + + s->temp.size = 0; + + return XZ_OK; +} + +XZ_EXTERN void xz_dec_lzma2_end(struct xz_dec_lzma2 *s) +{ + if (DEC_IS_MULTI(s->dict.mode)) + vfree(s->dict.buf); + + kfree(s); +} + +#ifdef XZ_DEC_MICROLZMA +/* This is a wrapper struct to have a nice struct name in the public API. */ +struct xz_dec_microlzma { + struct xz_dec_lzma2 s; +}; + +enum xz_ret xz_dec_microlzma_run(struct xz_dec_microlzma *s_ptr, + struct xz_buf *b) +{ + struct xz_dec_lzma2 *s = &s_ptr->s; + + /* + * sequence is SEQ_PROPERTIES before the first input byte, + * SEQ_LZMA_PREPARE until a total of five bytes have been read, + * and SEQ_LZMA_RUN for the rest of the input stream. + */ + if (s->lzma2.sequence != SEQ_LZMA_RUN) { + if (s->lzma2.sequence == SEQ_PROPERTIES) { + /* One byte is needed for the props. */ + if (b->in_pos >= b->in_size) + return XZ_OK; + + /* + * Don't increment b->in_pos here. The same byte is + * also passed to rc_read_init() which will ignore it. + */ + if (!lzma_props(s, ~b->in[b->in_pos])) + return XZ_DATA_ERROR; + + s->lzma2.sequence = SEQ_LZMA_PREPARE; + } + + /* + * xz_dec_microlzma_reset() doesn't validate the compressed + * size so we do it here. We have to limit the maximum size + * to avoid integer overflows in lzma2_lzma(). 3 GiB is a nice + * round number and much more than users of this code should + * ever need. + */ + if (s->lzma2.compressed < RC_INIT_BYTES + || s->lzma2.compressed > (3U << 30)) + return XZ_DATA_ERROR; + + if (!rc_read_init(&s->rc, b)) + return XZ_OK; + + s->lzma2.compressed -= RC_INIT_BYTES; + s->lzma2.sequence = SEQ_LZMA_RUN; + + dict_reset(&s->dict, b); + } + + /* This is to allow increasing b->out_size between calls. */ + if (DEC_IS_SINGLE(s->dict.mode)) + s->dict.end = b->out_size - b->out_pos; + + while (true) { + dict_limit(&s->dict, min_t(size_t, b->out_size - b->out_pos, + s->lzma2.uncompressed)); + + if (!lzma2_lzma(s, b)) + return XZ_DATA_ERROR; + + s->lzma2.uncompressed -= dict_flush(&s->dict, b); + + if (s->lzma2.uncompressed == 0) { + if (s->lzma2.pedantic_microlzma) { + if (s->lzma2.compressed > 0 || s->lzma.len > 0 + || !rc_is_finished(&s->rc)) + return XZ_DATA_ERROR; + } + + return XZ_STREAM_END; + } + + if (b->out_pos == b->out_size) + return XZ_OK; + + if (b->in_pos == b->in_size + && s->temp.size < s->lzma2.compressed) + return XZ_OK; + } +} + +struct xz_dec_microlzma *xz_dec_microlzma_alloc(enum xz_mode mode, + uint32_t dict_size) +{ + struct xz_dec_microlzma *s; + + /* Restrict dict_size to the same range as in the LZMA2 code. */ + if (dict_size < 4096 || dict_size > (3U << 30)) + return NULL; + + s = kmalloc(sizeof(*s), GFP_KERNEL); + if (s == NULL) + return NULL; + + s->s.dict.mode = mode; + s->s.dict.size = dict_size; + + if (DEC_IS_MULTI(mode)) { + s->s.dict.end = dict_size; + + s->s.dict.buf = vmalloc(dict_size); + if (s->s.dict.buf == NULL) { + kfree(s); + return NULL; + } + } + + return s; +} + +void xz_dec_microlzma_reset(struct xz_dec_microlzma *s, uint32_t comp_size, + uint32_t uncomp_size, int uncomp_size_is_exact) +{ + /* + * comp_size is validated in xz_dec_microlzma_run(). + * uncomp_size can safely be anything. + */ + s->s.lzma2.compressed = comp_size; + s->s.lzma2.uncompressed = uncomp_size; + s->s.lzma2.pedantic_microlzma = uncomp_size_is_exact; + + s->s.lzma2.sequence = SEQ_PROPERTIES; + s->s.temp.size = 0; +} + +void xz_dec_microlzma_end(struct xz_dec_microlzma *s) +{ + if (DEC_IS_MULTI(s->s.dict.mode)) + vfree(s->s.dict.buf); + + kfree(s); +} +#endif diff --git a/sources/wasm-gc-teavm-loader/c/xz/xz_dec_stream.c b/sources/wasm-gc-teavm-loader/c/xz/xz_dec_stream.c new file mode 100644 index 0000000..2d5fbe1 --- /dev/null +++ b/sources/wasm-gc-teavm-loader/c/xz/xz_dec_stream.c @@ -0,0 +1,940 @@ +// SPDX-License-Identifier: 0BSD + +/* + * .xz Stream decoder + * + * Author: Lasse Collin + */ + +#include "xz_private.h" +#include "xz_stream.h" + +#ifdef XZ_USE_CRC64 +# define IS_CRC64(check_type) ((check_type) == XZ_CHECK_CRC64) +#else +# define IS_CRC64(check_type) false +#endif + +/* Hash used to validate the Index field */ +struct xz_dec_hash { + vli_type unpadded; + vli_type uncompressed; + uint32_t crc32; +}; + +struct xz_dec { + /* Position in dec_main() */ + enum { + SEQ_STREAM_HEADER, + SEQ_BLOCK_START, + SEQ_BLOCK_HEADER, + SEQ_BLOCK_UNCOMPRESS, + SEQ_BLOCK_PADDING, + SEQ_BLOCK_CHECK, + SEQ_INDEX, + SEQ_INDEX_PADDING, + SEQ_INDEX_CRC32, + SEQ_STREAM_FOOTER, + SEQ_STREAM_PADDING + } sequence; + + /* Position in variable-length integers and Check fields */ + uint32_t pos; + + /* Variable-length integer decoded by dec_vli() */ + vli_type vli; + + /* Saved in_pos and out_pos */ + size_t in_start; + size_t out_start; + +#ifdef XZ_USE_CRC64 + /* CRC32 or CRC64 value in Block or CRC32 value in Index */ + uint64_t crc; +#else + /* CRC32 value in Block or Index */ + uint32_t crc; +#endif + + /* Type of the integrity check calculated from uncompressed data */ + enum xz_check check_type; + + /* Operation mode */ + enum xz_mode mode; + + /* + * True if the next call to xz_dec_run() is allowed to return + * XZ_BUF_ERROR. + */ + bool allow_buf_error; + + /* Information stored in Block Header */ + struct { + /* + * Value stored in the Compressed Size field, or + * VLI_UNKNOWN if Compressed Size is not present. + */ + vli_type compressed; + + /* + * Value stored in the Uncompressed Size field, or + * VLI_UNKNOWN if Uncompressed Size is not present. + */ + vli_type uncompressed; + + /* Size of the Block Header field */ + uint32_t size; + } block_header; + + /* Information collected when decoding Blocks */ + struct { + /* Observed compressed size of the current Block */ + vli_type compressed; + + /* Observed uncompressed size of the current Block */ + vli_type uncompressed; + + /* Number of Blocks decoded so far */ + vli_type count; + + /* + * Hash calculated from the Block sizes. This is used to + * validate the Index field. + */ + struct xz_dec_hash hash; + } block; + + /* Variables needed when verifying the Index field */ + struct { + /* Position in dec_index() */ + enum { + SEQ_INDEX_COUNT, + SEQ_INDEX_UNPADDED, + SEQ_INDEX_UNCOMPRESSED + } sequence; + + /* Size of the Index in bytes */ + vli_type size; + + /* Number of Records (matches block.count in valid files) */ + vli_type count; + + /* + * Hash calculated from the Records (matches block.hash in + * valid files). + */ + struct xz_dec_hash hash; + } index; + + /* + * Temporary buffer needed to hold Stream Header, Block Header, + * and Stream Footer. The Block Header is the biggest (1 KiB) + * so we reserve space according to that. buf[] has to be aligned + * to a multiple of four bytes; the size_t variables before it + * should guarantee this. + */ + struct { + size_t pos; + size_t size; + uint8_t buf[1024]; + } temp; + + struct xz_dec_lzma2 *lzma2; + +#ifdef XZ_DEC_BCJ + struct xz_dec_bcj *bcj; + bool bcj_active; +#endif +}; + +#ifdef XZ_DEC_ANY_CHECK +/* Sizes of the Check field with different Check IDs */ +static const uint8_t check_sizes[16] = { + 0, + 4, 4, 4, + 8, 8, 8, + 16, 16, 16, + 32, 32, 32, + 64, 64, 64 +}; +#endif + +/* + * Fill s->temp by copying data starting from b->in[b->in_pos]. Caller + * must have set s->temp.pos to indicate how much data we are supposed + * to copy into s->temp.buf. Return true once s->temp.pos has reached + * s->temp.size. + */ +static bool fill_temp(struct xz_dec *s, struct xz_buf *b) +{ + size_t copy_size = min_t(size_t, + b->in_size - b->in_pos, s->temp.size - s->temp.pos); + + memcpy(s->temp.buf + s->temp.pos, b->in + b->in_pos, copy_size); + b->in_pos += copy_size; + s->temp.pos += copy_size; + + if (s->temp.pos == s->temp.size) { + s->temp.pos = 0; + return true; + } + + return false; +} + +/* Decode a variable-length integer (little-endian base-128 encoding) */ +static enum xz_ret dec_vli(struct xz_dec *s, const uint8_t *in, + size_t *in_pos, size_t in_size) +{ + uint8_t byte; + + if (s->pos == 0) + s->vli = 0; + + while (*in_pos < in_size) { + byte = in[*in_pos]; + ++*in_pos; + + s->vli |= (vli_type)(byte & 0x7F) << s->pos; + + if ((byte & 0x80) == 0) { + /* Don't allow non-minimal encodings. */ + if (byte == 0 && s->pos != 0) + return XZ_DATA_ERROR; + + s->pos = 0; + return XZ_STREAM_END; + } + + s->pos += 7; + if (s->pos == 7 * VLI_BYTES_MAX) + return XZ_DATA_ERROR; + } + + return XZ_OK; +} + +/* + * Decode the Compressed Data field from a Block. Update and validate + * the observed compressed and uncompressed sizes of the Block so that + * they don't exceed the values possibly stored in the Block Header + * (validation assumes that no integer overflow occurs, since vli_type + * is normally uint64_t). Update the CRC32 or CRC64 value if presence of + * the CRC32 or CRC64 field was indicated in Stream Header. + * + * Once the decoding is finished, validate that the observed sizes match + * the sizes possibly stored in the Block Header. Update the hash and + * Block count, which are later used to validate the Index field. + */ +static enum xz_ret dec_block(struct xz_dec *s, struct xz_buf *b) +{ + enum xz_ret ret; + + s->in_start = b->in_pos; + s->out_start = b->out_pos; + +#ifdef XZ_DEC_BCJ + if (s->bcj_active) + ret = xz_dec_bcj_run(s->bcj, s->lzma2, b); + else +#endif + ret = xz_dec_lzma2_run(s->lzma2, b); + + s->block.compressed += b->in_pos - s->in_start; + s->block.uncompressed += b->out_pos - s->out_start; + + /* + * There is no need to separately check for VLI_UNKNOWN, since + * the observed sizes are always smaller than VLI_UNKNOWN. + */ + if (s->block.compressed > s->block_header.compressed + || s->block.uncompressed + > s->block_header.uncompressed) + return XZ_DATA_ERROR; + + if (s->check_type == XZ_CHECK_CRC32) + s->crc = xz_crc32(b->out + s->out_start, + b->out_pos - s->out_start, s->crc); +#ifdef XZ_USE_CRC64 + else if (s->check_type == XZ_CHECK_CRC64) + s->crc = xz_crc64(b->out + s->out_start, + b->out_pos - s->out_start, s->crc); +#endif + + if (ret == XZ_STREAM_END) { + if (s->block_header.compressed != VLI_UNKNOWN + && s->block_header.compressed + != s->block.compressed) + return XZ_DATA_ERROR; + + if (s->block_header.uncompressed != VLI_UNKNOWN + && s->block_header.uncompressed + != s->block.uncompressed) + return XZ_DATA_ERROR; + + s->block.hash.unpadded += s->block_header.size + + s->block.compressed; + +#ifdef XZ_DEC_ANY_CHECK + s->block.hash.unpadded += check_sizes[s->check_type]; +#else + if (s->check_type == XZ_CHECK_CRC32) + s->block.hash.unpadded += 4; + else if (IS_CRC64(s->check_type)) + s->block.hash.unpadded += 8; +#endif + + s->block.hash.uncompressed += s->block.uncompressed; + s->block.hash.crc32 = xz_crc32( + (const uint8_t *)&s->block.hash, + sizeof(s->block.hash), s->block.hash.crc32); + + ++s->block.count; + } + + return ret; +} + +/* Update the Index size and the CRC32 value. */ +static void index_update(struct xz_dec *s, const struct xz_buf *b) +{ + size_t in_used = b->in_pos - s->in_start; + s->index.size += in_used; + s->crc = xz_crc32(b->in + s->in_start, in_used, s->crc); +} + +/* + * Decode the Number of Records, Unpadded Size, and Uncompressed Size + * fields from the Index field. That is, Index Padding and CRC32 are not + * decoded by this function. + * + * This can return XZ_OK (more input needed), XZ_STREAM_END (everything + * successfully decoded), or XZ_DATA_ERROR (input is corrupt). + */ +static enum xz_ret dec_index(struct xz_dec *s, struct xz_buf *b) +{ + enum xz_ret ret; + + do { + ret = dec_vli(s, b->in, &b->in_pos, b->in_size); + if (ret != XZ_STREAM_END) { + index_update(s, b); + return ret; + } + + switch (s->index.sequence) { + case SEQ_INDEX_COUNT: + s->index.count = s->vli; + + /* + * Validate that the Number of Records field + * indicates the same number of Records as + * there were Blocks in the Stream. + */ + if (s->index.count != s->block.count) + return XZ_DATA_ERROR; + + s->index.sequence = SEQ_INDEX_UNPADDED; + break; + + case SEQ_INDEX_UNPADDED: + s->index.hash.unpadded += s->vli; + s->index.sequence = SEQ_INDEX_UNCOMPRESSED; + break; + + case SEQ_INDEX_UNCOMPRESSED: + s->index.hash.uncompressed += s->vli; + s->index.hash.crc32 = xz_crc32( + (const uint8_t *)&s->index.hash, + sizeof(s->index.hash), + s->index.hash.crc32); + --s->index.count; + s->index.sequence = SEQ_INDEX_UNPADDED; + break; + } + } while (s->index.count > 0); + + return XZ_STREAM_END; +} + +/* + * Validate that the next four or eight input bytes match the value + * of s->crc. s->pos must be zero when starting to validate the first byte. + * The "bits" argument allows using the same code for both CRC32 and CRC64. + */ +static enum xz_ret crc_validate(struct xz_dec *s, struct xz_buf *b, + uint32_t bits) +{ + do { + if (b->in_pos == b->in_size) + return XZ_OK; + + if (((s->crc >> s->pos) & 0xFF) != b->in[b->in_pos++]) + return XZ_DATA_ERROR; + + s->pos += 8; + + } while (s->pos < bits); + + s->crc = 0; + s->pos = 0; + + return XZ_STREAM_END; +} + +#ifdef XZ_DEC_ANY_CHECK +/* + * Skip over the Check field when the Check ID is not supported. + * Returns true once the whole Check field has been skipped over. + */ +static bool check_skip(struct xz_dec *s, struct xz_buf *b) +{ + while (s->pos < check_sizes[s->check_type]) { + if (b->in_pos == b->in_size) + return false; + + ++b->in_pos; + ++s->pos; + } + + s->pos = 0; + + return true; +} +#endif + +/* Decode the Stream Header field (the first 12 bytes of the .xz Stream). */ +static enum xz_ret dec_stream_header(struct xz_dec *s) +{ + if (!memeq(s->temp.buf, HEADER_MAGIC, HEADER_MAGIC_SIZE)) + return XZ_FORMAT_ERROR; + + if (xz_crc32(s->temp.buf + HEADER_MAGIC_SIZE, 2, 0) + != get_le32(s->temp.buf + HEADER_MAGIC_SIZE + 2)) + return XZ_DATA_ERROR; + + if (s->temp.buf[HEADER_MAGIC_SIZE] != 0) + return XZ_OPTIONS_ERROR; + + /* + * Of integrity checks, we support none (Check ID = 0), + * CRC32 (Check ID = 1), and optionally CRC64 (Check ID = 4). + * However, if XZ_DEC_ANY_CHECK is defined, we will accept other + * check types too, but then the check won't be verified and + * a warning (XZ_UNSUPPORTED_CHECK) will be given. + */ + if (s->temp.buf[HEADER_MAGIC_SIZE + 1] > XZ_CHECK_MAX) + return XZ_OPTIONS_ERROR; + + s->check_type = s->temp.buf[HEADER_MAGIC_SIZE + 1]; + +#ifdef XZ_DEC_ANY_CHECK + if (s->check_type > XZ_CHECK_CRC32 && !IS_CRC64(s->check_type)) + return XZ_UNSUPPORTED_CHECK; +#else + if (s->check_type > XZ_CHECK_CRC32 && !IS_CRC64(s->check_type)) + return XZ_OPTIONS_ERROR; +#endif + + return XZ_OK; +} + +/* Decode the Stream Footer field (the last 12 bytes of the .xz Stream) */ +static enum xz_ret dec_stream_footer(struct xz_dec *s) +{ + if (!memeq(s->temp.buf + 10, FOOTER_MAGIC, FOOTER_MAGIC_SIZE)) + return XZ_DATA_ERROR; + + if (xz_crc32(s->temp.buf + 4, 6, 0) != get_le32(s->temp.buf)) + return XZ_DATA_ERROR; + + /* + * Validate Backward Size. Note that we never added the size of the + * Index CRC32 field to s->index.size, thus we use s->index.size / 4 + * instead of s->index.size / 4 - 1. + */ + if ((s->index.size >> 2) != get_le32(s->temp.buf + 4)) + return XZ_DATA_ERROR; + + if (s->temp.buf[8] != 0 || s->temp.buf[9] != s->check_type) + return XZ_DATA_ERROR; + + /* + * Use XZ_STREAM_END instead of XZ_OK to be more convenient + * for the caller. + */ + return XZ_STREAM_END; +} + +/* Decode the Block Header and initialize the filter chain. */ +static enum xz_ret dec_block_header(struct xz_dec *s) +{ + enum xz_ret ret; + + /* + * Validate the CRC32. We know that the temp buffer is at least + * eight bytes so this is safe. + */ + s->temp.size -= 4; + if (xz_crc32(s->temp.buf, s->temp.size, 0) + != get_le32(s->temp.buf + s->temp.size)) + return XZ_DATA_ERROR; + + s->temp.pos = 2; + + /* + * Catch unsupported Block Flags. We support only one or two filters + * in the chain, so we catch that with the same test. + */ +#ifdef XZ_DEC_BCJ + if (s->temp.buf[1] & 0x3E) +#else + if (s->temp.buf[1] & 0x3F) +#endif + return XZ_OPTIONS_ERROR; + + /* Compressed Size */ + if (s->temp.buf[1] & 0x40) { + if (dec_vli(s, s->temp.buf, &s->temp.pos, s->temp.size) + != XZ_STREAM_END) + return XZ_DATA_ERROR; + + s->block_header.compressed = s->vli; + } else { + s->block_header.compressed = VLI_UNKNOWN; + } + + /* Uncompressed Size */ + if (s->temp.buf[1] & 0x80) { + if (dec_vli(s, s->temp.buf, &s->temp.pos, s->temp.size) + != XZ_STREAM_END) + return XZ_DATA_ERROR; + + s->block_header.uncompressed = s->vli; + } else { + s->block_header.uncompressed = VLI_UNKNOWN; + } + +#ifdef XZ_DEC_BCJ + /* If there are two filters, the first one must be a BCJ filter. */ + s->bcj_active = s->temp.buf[1] & 0x01; + if (s->bcj_active) { + if (s->temp.size - s->temp.pos < 2) + return XZ_OPTIONS_ERROR; + + ret = xz_dec_bcj_reset(s->bcj, s->temp.buf[s->temp.pos++]); + if (ret != XZ_OK) + return ret; + + /* + * We don't support custom start offset, + * so Size of Properties must be zero. + */ + if (s->temp.buf[s->temp.pos++] != 0x00) + return XZ_OPTIONS_ERROR; + } +#endif + + /* Valid Filter Flags always take at least two bytes. */ + if (s->temp.size - s->temp.pos < 2) + return XZ_DATA_ERROR; + + /* Filter ID = LZMA2 */ + if (s->temp.buf[s->temp.pos++] != 0x21) + return XZ_OPTIONS_ERROR; + + /* Size of Properties = 1-byte Filter Properties */ + if (s->temp.buf[s->temp.pos++] != 0x01) + return XZ_OPTIONS_ERROR; + + /* Filter Properties contains LZMA2 dictionary size. */ + if (s->temp.size - s->temp.pos < 1) + return XZ_DATA_ERROR; + + ret = xz_dec_lzma2_reset(s->lzma2, s->temp.buf[s->temp.pos++]); + if (ret != XZ_OK) + return ret; + + /* The rest must be Header Padding. */ + while (s->temp.pos < s->temp.size) + if (s->temp.buf[s->temp.pos++] != 0x00) + return XZ_OPTIONS_ERROR; + + s->temp.pos = 0; + s->block.compressed = 0; + s->block.uncompressed = 0; + + return XZ_OK; +} + +static enum xz_ret dec_main(struct xz_dec *s, struct xz_buf *b) +{ + enum xz_ret ret; + + /* + * Store the start position for the case when we are in the middle + * of the Index field. + */ + s->in_start = b->in_pos; + + while (true) { + switch (s->sequence) { + case SEQ_STREAM_HEADER: + /* + * Stream Header is copied to s->temp, and then + * decoded from there. This way if the caller + * gives us only little input at a time, we can + * still keep the Stream Header decoding code + * simple. Similar approach is used in many places + * in this file. + */ + if (!fill_temp(s, b)) + return XZ_OK; + + /* + * If dec_stream_header() returns + * XZ_UNSUPPORTED_CHECK, it is still possible + * to continue decoding if working in multi-call + * mode. Thus, update s->sequence before calling + * dec_stream_header(). + */ + s->sequence = SEQ_BLOCK_START; + + ret = dec_stream_header(s); + if (ret != XZ_OK) + return ret; + + fallthrough; + + case SEQ_BLOCK_START: + /* We need one byte of input to continue. */ + if (b->in_pos == b->in_size) + return XZ_OK; + + /* See if this is the beginning of the Index field. */ + if (b->in[b->in_pos] == 0) { + s->in_start = b->in_pos++; + s->sequence = SEQ_INDEX; + break; + } + + /* + * Calculate the size of the Block Header and + * prepare to decode it. + */ + s->block_header.size + = ((uint32_t)b->in[b->in_pos] + 1) * 4; + + s->temp.size = s->block_header.size; + s->temp.pos = 0; + s->sequence = SEQ_BLOCK_HEADER; + + fallthrough; + + case SEQ_BLOCK_HEADER: + if (!fill_temp(s, b)) + return XZ_OK; + + ret = dec_block_header(s); + if (ret != XZ_OK) + return ret; + + s->sequence = SEQ_BLOCK_UNCOMPRESS; + + fallthrough; + + case SEQ_BLOCK_UNCOMPRESS: + ret = dec_block(s, b); + if (ret != XZ_STREAM_END) + return ret; + + s->sequence = SEQ_BLOCK_PADDING; + + fallthrough; + + case SEQ_BLOCK_PADDING: + /* + * Size of Compressed Data + Block Padding + * must be a multiple of four. We don't need + * s->block.compressed for anything else + * anymore, so we use it here to test the size + * of the Block Padding field. + */ + while (s->block.compressed & 3) { + if (b->in_pos == b->in_size) + return XZ_OK; + + if (b->in[b->in_pos++] != 0) + return XZ_DATA_ERROR; + + ++s->block.compressed; + } + + s->sequence = SEQ_BLOCK_CHECK; + + fallthrough; + + case SEQ_BLOCK_CHECK: + if (s->check_type == XZ_CHECK_CRC32) { + ret = crc_validate(s, b, 32); + if (ret != XZ_STREAM_END) + return ret; + } + else if (IS_CRC64(s->check_type)) { + ret = crc_validate(s, b, 64); + if (ret != XZ_STREAM_END) + return ret; + } +#ifdef XZ_DEC_ANY_CHECK + else if (!check_skip(s, b)) { + return XZ_OK; + } +#endif + + s->sequence = SEQ_BLOCK_START; + break; + + case SEQ_INDEX: + ret = dec_index(s, b); + if (ret != XZ_STREAM_END) + return ret; + + s->sequence = SEQ_INDEX_PADDING; + + fallthrough; + + case SEQ_INDEX_PADDING: + while ((s->index.size + (b->in_pos - s->in_start)) + & 3) { + if (b->in_pos == b->in_size) { + index_update(s, b); + return XZ_OK; + } + + if (b->in[b->in_pos++] != 0) + return XZ_DATA_ERROR; + } + + /* Finish the CRC32 value and Index size. */ + index_update(s, b); + + /* Compare the hashes to validate the Index field. */ + if (!memeq(&s->block.hash, &s->index.hash, + sizeof(s->block.hash))) + return XZ_DATA_ERROR; + + s->sequence = SEQ_INDEX_CRC32; + + fallthrough; + + case SEQ_INDEX_CRC32: + ret = crc_validate(s, b, 32); + if (ret != XZ_STREAM_END) + return ret; + + s->temp.size = STREAM_HEADER_SIZE; + s->sequence = SEQ_STREAM_FOOTER; + + fallthrough; + + case SEQ_STREAM_FOOTER: + if (!fill_temp(s, b)) + return XZ_OK; + + return dec_stream_footer(s); + + case SEQ_STREAM_PADDING: + /* Never reached, only silencing a warning */ + break; + } + } + + /* Never reached */ +} + +/* + * xz_dec_run() is a wrapper for dec_main() to handle some special cases in + * multi-call and single-call decoding. + * + * In multi-call mode, we must return XZ_BUF_ERROR when it seems clear that we + * are not going to make any progress anymore. This is to prevent the caller + * from calling us infinitely when the input file is truncated or otherwise + * corrupt. Since zlib-style API allows that the caller fills the input buffer + * only when the decoder doesn't produce any new output, we have to be careful + * to avoid returning XZ_BUF_ERROR too easily: XZ_BUF_ERROR is returned only + * after the second consecutive call to xz_dec_run() that makes no progress. + * + * In single-call mode, if we couldn't decode everything and no error + * occurred, either the input is truncated or the output buffer is too small. + * Since we know that the last input byte never produces any output, we know + * that if all the input was consumed and decoding wasn't finished, the file + * must be corrupt. Otherwise the output buffer has to be too small or the + * file is corrupt in a way that decoding it produces too big output. + * + * If single-call decoding fails, we reset b->in_pos and b->out_pos back to + * their original values. This is because with some filter chains there won't + * be any valid uncompressed data in the output buffer unless the decoding + * actually succeeds (that's the price to pay of using the output buffer as + * the workspace). + */ +XZ_EXTERN enum xz_ret xz_dec_run(struct xz_dec *s, struct xz_buf *b) +{ + size_t in_start; + size_t out_start; + enum xz_ret ret; + + if (DEC_IS_SINGLE(s->mode)) + xz_dec_reset(s); + + in_start = b->in_pos; + out_start = b->out_pos; + ret = dec_main(s, b); + + if (DEC_IS_SINGLE(s->mode)) { + if (ret == XZ_OK) + ret = b->in_pos == b->in_size + ? XZ_DATA_ERROR : XZ_BUF_ERROR; + + if (ret != XZ_STREAM_END) { + b->in_pos = in_start; + b->out_pos = out_start; + } + + } else if (ret == XZ_OK && in_start == b->in_pos + && out_start == b->out_pos) { + if (s->allow_buf_error) + ret = XZ_BUF_ERROR; + + s->allow_buf_error = true; + } else { + s->allow_buf_error = false; + } + + return ret; +} + +#ifdef XZ_DEC_CONCATENATED +XZ_EXTERN enum xz_ret xz_dec_catrun(struct xz_dec *s, struct xz_buf *b, + int finish) +{ + enum xz_ret ret; + + if (DEC_IS_SINGLE(s->mode)) { + xz_dec_reset(s); + finish = true; + } + + while (true) { + if (s->sequence == SEQ_STREAM_PADDING) { + /* + * Skip Stream Padding. Its size must be a multiple + * of four bytes which is tracked with s->pos. + */ + while (true) { + if (b->in_pos == b->in_size) { + /* + * Note that if we are repeatedly + * given no input and finish is false, + * we will keep returning XZ_OK even + * though no progress is being made. + * The lack of XZ_BUF_ERROR support + * isn't a problem here because a + * reasonable caller will eventually + * provide more input or set finish + * to true. + */ + if (!finish) + return XZ_OK; + + if (s->pos != 0) + return XZ_DATA_ERROR; + + return XZ_STREAM_END; + } + + if (b->in[b->in_pos] != 0x00) { + if (s->pos != 0) + return XZ_DATA_ERROR; + + break; + } + + ++b->in_pos; + s->pos = (s->pos + 1) & 3; + } + + /* + * More input remains. It should be a new Stream. + * + * In single-call mode xz_dec_run() will always call + * xz_dec_reset(). Thus, we need to do it here only + * in multi-call mode. + */ + if (DEC_IS_MULTI(s->mode)) + xz_dec_reset(s); + } + + ret = xz_dec_run(s, b); + + if (ret != XZ_STREAM_END) + break; + + s->sequence = SEQ_STREAM_PADDING; + } + + return ret; +} +#endif + +XZ_EXTERN struct xz_dec *xz_dec_init(enum xz_mode mode, uint32_t dict_max) +{ + struct xz_dec *s = kmalloc(sizeof(*s), GFP_KERNEL); + if (s == NULL) + return NULL; + + s->mode = mode; + +#ifdef XZ_DEC_BCJ + s->bcj = xz_dec_bcj_create(DEC_IS_SINGLE(mode)); + if (s->bcj == NULL) + goto error_bcj; +#endif + + s->lzma2 = xz_dec_lzma2_create(mode, dict_max); + if (s->lzma2 == NULL) + goto error_lzma2; + + xz_dec_reset(s); + return s; + +error_lzma2: +#ifdef XZ_DEC_BCJ + xz_dec_bcj_end(s->bcj); +error_bcj: +#endif + kfree(s); + return NULL; +} + +XZ_EXTERN void xz_dec_reset(struct xz_dec *s) +{ + s->sequence = SEQ_STREAM_HEADER; + s->allow_buf_error = false; + s->pos = 0; + s->crc = 0; + memzero(&s->block, sizeof(s->block)); + memzero(&s->index, sizeof(s->index)); + s->temp.pos = 0; + s->temp.size = STREAM_HEADER_SIZE; +} + +XZ_EXTERN void xz_dec_end(struct xz_dec *s) +{ + if (s != NULL) { + xz_dec_lzma2_end(s->lzma2); +#ifdef XZ_DEC_BCJ + xz_dec_bcj_end(s->bcj); +#endif + kfree(s); + } +} diff --git a/sources/wasm-gc-teavm-loader/c/xz/xz_lzma2.h b/sources/wasm-gc-teavm-loader/c/xz/xz_lzma2.h new file mode 100644 index 0000000..d2632b7 --- /dev/null +++ b/sources/wasm-gc-teavm-loader/c/xz/xz_lzma2.h @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: 0BSD */ + +/* + * LZMA2 definitions + * + * Authors: Lasse Collin + * Igor Pavlov + */ + +#ifndef XZ_LZMA2_H +#define XZ_LZMA2_H + +/* Range coder constants */ +#define RC_SHIFT_BITS 8 +#define RC_TOP_BITS 24 +#define RC_TOP_VALUE (1 << RC_TOP_BITS) +#define RC_BIT_MODEL_TOTAL_BITS 11 +#define RC_BIT_MODEL_TOTAL (1 << RC_BIT_MODEL_TOTAL_BITS) +#define RC_MOVE_BITS 5 + +/* + * Maximum number of position states. A position state is the lowest pb + * number of bits of the current uncompressed offset. In some places there + * are different sets of probabilities for different position states. + */ +#define POS_STATES_MAX (1 << 4) + +/* + * This enum is used to track which LZMA symbols have occurred most recently + * and in which order. This information is used to predict the next symbol. + * + * Symbols: + * - Literal: One 8-bit byte + * - Match: Repeat a chunk of data at some distance + * - Long repeat: Multi-byte match at a recently seen distance + * - Short repeat: One-byte repeat at a recently seen distance + * + * The symbol names are in from STATE_oldest_older_previous. REP means + * either short or long repeated match, and NONLIT means any non-literal. + */ +enum lzma_state { + STATE_LIT_LIT, + STATE_MATCH_LIT_LIT, + STATE_REP_LIT_LIT, + STATE_SHORTREP_LIT_LIT, + STATE_MATCH_LIT, + STATE_REP_LIT, + STATE_SHORTREP_LIT, + STATE_LIT_MATCH, + STATE_LIT_LONGREP, + STATE_LIT_SHORTREP, + STATE_NONLIT_MATCH, + STATE_NONLIT_REP +}; + +/* Total number of states */ +#define STATES 12 + +/* The lowest 7 states indicate that the previous state was a literal. */ +#define LIT_STATES 7 + +/* Indicate that the latest symbol was a literal. */ +static inline void lzma_state_literal(enum lzma_state *state) +{ + if (*state <= STATE_SHORTREP_LIT_LIT) + *state = STATE_LIT_LIT; + else if (*state <= STATE_LIT_SHORTREP) + *state -= 3; + else + *state -= 6; +} + +/* Indicate that the latest symbol was a match. */ +static inline void lzma_state_match(enum lzma_state *state) +{ + *state = *state < LIT_STATES ? STATE_LIT_MATCH : STATE_NONLIT_MATCH; +} + +/* Indicate that the latest state was a long repeated match. */ +static inline void lzma_state_long_rep(enum lzma_state *state) +{ + *state = *state < LIT_STATES ? STATE_LIT_LONGREP : STATE_NONLIT_REP; +} + +/* Indicate that the latest symbol was a short match. */ +static inline void lzma_state_short_rep(enum lzma_state *state) +{ + *state = *state < LIT_STATES ? STATE_LIT_SHORTREP : STATE_NONLIT_REP; +} + +/* Test if the previous symbol was a literal. */ +static inline bool lzma_state_is_literal(enum lzma_state state) +{ + return state < LIT_STATES; +} + +/* Each literal coder is divided in three sections: + * - 0x001-0x0FF: Without match byte + * - 0x101-0x1FF: With match byte; match bit is 0 + * - 0x201-0x2FF: With match byte; match bit is 1 + * + * Match byte is used when the previous LZMA symbol was something else than + * a literal (that is, it was some kind of match). + */ +#define LITERAL_CODER_SIZE 0x300 + +/* Maximum number of literal coders */ +#define LITERAL_CODERS_MAX (1 << 4) + +/* Minimum length of a match is two bytes. */ +#define MATCH_LEN_MIN 2 + +/* Match length is encoded with 4, 5, or 10 bits. + * + * Length Bits + * 2-9 4 = Choice=0 + 3 bits + * 10-17 5 = Choice=1 + Choice2=0 + 3 bits + * 18-273 10 = Choice=1 + Choice2=1 + 8 bits + */ +#define LEN_LOW_BITS 3 +#define LEN_LOW_SYMBOLS (1 << LEN_LOW_BITS) +#define LEN_MID_BITS 3 +#define LEN_MID_SYMBOLS (1 << LEN_MID_BITS) +#define LEN_HIGH_BITS 8 +#define LEN_HIGH_SYMBOLS (1 << LEN_HIGH_BITS) +#define LEN_SYMBOLS (LEN_LOW_SYMBOLS + LEN_MID_SYMBOLS + LEN_HIGH_SYMBOLS) + +/* + * Maximum length of a match is 273 which is a result of the encoding + * described above. + */ +#define MATCH_LEN_MAX (MATCH_LEN_MIN + LEN_SYMBOLS - 1) + +/* + * Different sets of probabilities are used for match distances that have + * very short match length: Lengths of 2, 3, and 4 bytes have a separate + * set of probabilities for each length. The matches with longer length + * use a shared set of probabilities. + */ +#define DIST_STATES 4 + +/* + * Get the index of the appropriate probability array for decoding + * the distance slot. + */ +static inline uint32_t lzma_get_dist_state(uint32_t len) +{ + return len < DIST_STATES + MATCH_LEN_MIN + ? len - MATCH_LEN_MIN : DIST_STATES - 1; +} + +/* + * The highest two bits of a 32-bit match distance are encoded using six bits. + * This six-bit value is called a distance slot. This way encoding a 32-bit + * value takes 6-36 bits, larger values taking more bits. + */ +#define DIST_SLOT_BITS 6 +#define DIST_SLOTS (1 << DIST_SLOT_BITS) + +/* Match distances up to 127 are fully encoded using probabilities. Since + * the highest two bits (distance slot) are always encoded using six bits, + * the distances 0-3 don't need any additional bits to encode, since the + * distance slot itself is the same as the actual distance. DIST_MODEL_START + * indicates the first distance slot where at least one additional bit is + * needed. + */ +#define DIST_MODEL_START 4 + +/* + * Match distances greater than 127 are encoded in three pieces: + * - distance slot: the highest two bits + * - direct bits: 2-26 bits below the highest two bits + * - alignment bits: four lowest bits + * + * Direct bits don't use any probabilities. + * + * The distance slot value of 14 is for distances 128-191. + */ +#define DIST_MODEL_END 14 + +/* Distance slots that indicate a distance <= 127. */ +#define FULL_DISTANCES_BITS (DIST_MODEL_END / 2) +#define FULL_DISTANCES (1 << FULL_DISTANCES_BITS) + +/* + * For match distances greater than 127, only the highest two bits and the + * lowest four bits (alignment) is encoded using probabilities. + */ +#define ALIGN_BITS 4 +#define ALIGN_SIZE (1 << ALIGN_BITS) +#define ALIGN_MASK (ALIGN_SIZE - 1) + +/* Total number of all probability variables */ +#define PROBS_TOTAL (1846 + LITERAL_CODERS_MAX * LITERAL_CODER_SIZE) + +/* + * LZMA remembers the four most recent match distances. Reusing these + * distances tends to take less space than re-encoding the actual + * distance value. + */ +#define REPS 4 + +#endif diff --git a/sources/wasm-gc-teavm-loader/c/xz/xz_private.h b/sources/wasm-gc-teavm-loader/c/xz/xz_private.h new file mode 100644 index 0000000..a8b1cbe --- /dev/null +++ b/sources/wasm-gc-teavm-loader/c/xz/xz_private.h @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: 0BSD */ + +/* + * Private includes and definitions + * + * Author: Lasse Collin + */ + +#ifndef XZ_PRIVATE_H +#define XZ_PRIVATE_H + +#ifdef __KERNEL__ +# include +# include +# include + /* XZ_PREBOOT may be defined only via decompress_unxz.c. */ +# ifndef XZ_PREBOOT +# include +# include +# include +# ifdef CONFIG_XZ_DEC_X86 +# define XZ_DEC_X86 +# endif +# ifdef CONFIG_XZ_DEC_POWERPC +# define XZ_DEC_POWERPC +# endif +# ifdef CONFIG_XZ_DEC_IA64 +# define XZ_DEC_IA64 +# endif +# ifdef CONFIG_XZ_DEC_ARM +# define XZ_DEC_ARM +# endif +# ifdef CONFIG_XZ_DEC_ARMTHUMB +# define XZ_DEC_ARMTHUMB +# endif +# ifdef CONFIG_XZ_DEC_SPARC +# define XZ_DEC_SPARC +# endif +# ifdef CONFIG_XZ_DEC_ARM64 +# define XZ_DEC_ARM64 +# endif +# ifdef CONFIG_XZ_DEC_RISCV +# define XZ_DEC_RISCV +# endif +# ifdef CONFIG_XZ_DEC_MICROLZMA +# define XZ_DEC_MICROLZMA +# endif +# define memeq(a, b, size) (memcmp(a, b, size) == 0) +# define memzero(buf, size) memset(buf, 0, size) +# endif +# define get_le32(p) le32_to_cpup((const uint32_t *)(p)) +#else + /* + * For userspace builds, use a separate header to define the required + * macros and functions. This makes it easier to adapt the code into + * different environments and avoids clutter in the Linux kernel tree. + */ +# include "xz_config.h" +#endif + +/* If no specific decoding mode is requested, enable support for all modes. */ +#if !defined(XZ_DEC_SINGLE) && !defined(XZ_DEC_PREALLOC) \ + && !defined(XZ_DEC_DYNALLOC) +# define XZ_DEC_SINGLE +# define XZ_DEC_PREALLOC +# define XZ_DEC_DYNALLOC +#endif + +/* + * The DEC_IS_foo(mode) macros are used in "if" statements. If only some + * of the supported modes are enabled, these macros will evaluate to true or + * false at compile time and thus allow the compiler to omit unneeded code. + */ +#ifdef XZ_DEC_SINGLE +# define DEC_IS_SINGLE(mode) ((mode) == XZ_SINGLE) +#else +# define DEC_IS_SINGLE(mode) (false) +#endif + +#ifdef XZ_DEC_PREALLOC +# define DEC_IS_PREALLOC(mode) ((mode) == XZ_PREALLOC) +#else +# define DEC_IS_PREALLOC(mode) (false) +#endif + +#ifdef XZ_DEC_DYNALLOC +# define DEC_IS_DYNALLOC(mode) ((mode) == XZ_DYNALLOC) +#else +# define DEC_IS_DYNALLOC(mode) (false) +#endif + +#if !defined(XZ_DEC_SINGLE) +# define DEC_IS_MULTI(mode) (true) +#elif defined(XZ_DEC_PREALLOC) || defined(XZ_DEC_DYNALLOC) +# define DEC_IS_MULTI(mode) ((mode) != XZ_SINGLE) +#else +# define DEC_IS_MULTI(mode) (false) +#endif + +/* + * If any of the BCJ filter decoders are wanted, define XZ_DEC_BCJ. + * XZ_DEC_BCJ is used to enable generic support for BCJ decoders. + */ +#ifndef XZ_DEC_BCJ +# if defined(XZ_DEC_X86) || defined(XZ_DEC_POWERPC) \ + || defined(XZ_DEC_IA64) \ + || defined(XZ_DEC_ARM) || defined(XZ_DEC_ARMTHUMB) \ + || defined(XZ_DEC_SPARC) || defined(XZ_DEC_ARM64) \ + || defined(XZ_DEC_RISCV) +# define XZ_DEC_BCJ +# endif +#endif + +/* + * Allocate memory for LZMA2 decoder. xz_dec_lzma2_reset() must be used + * before calling xz_dec_lzma2_run(). + */ +XZ_EXTERN struct xz_dec_lzma2 *xz_dec_lzma2_create(enum xz_mode mode, + uint32_t dict_max); + +/* + * Decode the LZMA2 properties (one byte) and reset the decoder. Return + * XZ_OK on success, XZ_MEMLIMIT_ERROR if the preallocated dictionary is not + * big enough, and XZ_OPTIONS_ERROR if props indicates something that this + * decoder doesn't support. + */ +XZ_EXTERN enum xz_ret xz_dec_lzma2_reset(struct xz_dec_lzma2 *s, + uint8_t props); + +/* Decode raw LZMA2 stream from b->in to b->out. */ +XZ_EXTERN enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, + struct xz_buf *b); + +/* Free the memory allocated for the LZMA2 decoder. */ +XZ_EXTERN void xz_dec_lzma2_end(struct xz_dec_lzma2 *s); + +#ifdef XZ_DEC_BCJ +/* + * Allocate memory for BCJ decoders. xz_dec_bcj_reset() must be used before + * calling xz_dec_bcj_run(). + */ +XZ_EXTERN struct xz_dec_bcj *xz_dec_bcj_create(bool single_call); + +/* + * Decode the Filter ID of a BCJ filter. This implementation doesn't + * support custom start offsets, so no decoding of Filter Properties + * is needed. Returns XZ_OK if the given Filter ID is supported. + * Otherwise XZ_OPTIONS_ERROR is returned. + */ +XZ_EXTERN enum xz_ret xz_dec_bcj_reset(struct xz_dec_bcj *s, uint8_t id); + +/* + * Decode raw BCJ + LZMA2 stream. This must be used only if there actually is + * a BCJ filter in the chain. If the chain has only LZMA2, xz_dec_lzma2_run() + * must be called directly. + */ +XZ_EXTERN enum xz_ret xz_dec_bcj_run(struct xz_dec_bcj *s, + struct xz_dec_lzma2 *lzma2, + struct xz_buf *b); + +/* Free the memory allocated for the BCJ filters. */ +#define xz_dec_bcj_end(s) kfree(s) +#endif + +#endif diff --git a/sources/wasm-gc-teavm-loader/c/xz/xz_stream.h b/sources/wasm-gc-teavm-loader/c/xz/xz_stream.h new file mode 100644 index 0000000..55f9f6f --- /dev/null +++ b/sources/wasm-gc-teavm-loader/c/xz/xz_stream.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: 0BSD */ + +/* + * Definitions for handling the .xz file format + * + * Author: Lasse Collin + */ + +#ifndef XZ_STREAM_H +#define XZ_STREAM_H + +#if defined(__KERNEL__) && !XZ_INTERNAL_CRC32 +# include +# undef crc32 +# define xz_crc32(buf, size, crc) \ + (~crc32_le(~(uint32_t)(crc), buf, size)) +#endif + +/* + * See the .xz file format specification at + * https://tukaani.org/xz/xz-file-format.txt + * to understand the container format. + */ + +#define STREAM_HEADER_SIZE 12 + +#define HEADER_MAGIC "\3757zXZ" +#define HEADER_MAGIC_SIZE 6 + +#define FOOTER_MAGIC "YZ" +#define FOOTER_MAGIC_SIZE 2 + +/* + * Variable-length integer can hold a 63-bit unsigned integer or a special + * value indicating that the value is unknown. + * + * Experimental: vli_type can be defined to uint32_t to save a few bytes + * in code size (no effect on speed). Doing so limits the uncompressed and + * compressed size of the file to less than 256 MiB and may also weaken + * error detection slightly. + */ +typedef uint64_t vli_type; + +#define VLI_MAX ((vli_type)-1 / 2) +#define VLI_UNKNOWN ((vli_type)-1) + +/* Maximum encoded size of a VLI */ +#define VLI_BYTES_MAX (sizeof(vli_type) * 8 / 7) + +/* Integrity Check types */ +enum xz_check { + XZ_CHECK_NONE = 0, + XZ_CHECK_CRC32 = 1, + XZ_CHECK_CRC64 = 4, + XZ_CHECK_SHA256 = 10 +}; + +/* Maximum possible Check ID */ +#define XZ_CHECK_MAX 15 + +#endif diff --git a/sources/wasm-gc-teavm-loader/js/library.js b/sources/wasm-gc-teavm-loader/js/library.js new file mode 100644 index 0000000..e00cc69 --- /dev/null +++ b/sources/wasm-gc-teavm-loader/js/library.js @@ -0,0 +1,180 @@ +/* + * 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. + * + */ + +addToLibrary({ + getJSPISupported: function() { + return (typeof WebAssembly.Suspending !== "undefined"); + }, + getEPWLength: function() { + return epwFile.byteLength; + }, + memcpyFromEPW: function(dest, off, len) { + HEAPU8.set(new Uint8Array(epwFile, off, len), dest); + }, + initResult: function(bufLen) { + const id = results.length; + results.push(new Uint8Array(bufLen)); + return id; + }, + memcpyToResult: function(bufId, src, off, len) { + results[bufId].set(new Uint8Array(HEAPU8.buffer, src, len), off); + }, + memcpyFromEPWToResult: function(bufId, dest, off, len) { + results[bufId].set(new Uint8Array(epwFile, off, len), dest); + }, + initEPWStringResult: function(off, len) { + const id = results.length; + results.push(UTF8Decoder.decode(new Uint8Array(epwFile, off, len))); + return id; + }, + resultFailed: function(msg) { + const parentElement = createCrashParentElement(); + + const messageContainer = document.createElement("div"); + messageContainer.setAttribute("style", "z-index:100;position:absolute;top:10%;left:10%;right:10%;bottom:10%;background-color:white;border:2px solid #cccccc;overflow-x:hidden;overflow-y:scroll;"); + messageContainer.classList.add("_eaglercraftX_loader_failed_container"); + + const failMsg = UTF8ToString(msg); + console.error("LoaderMain: [FAILED] " + failMsg); + + const failureMsgElement = document.createElement("h2"); + failureMsgElement.style.color = "#AA0000"; + failureMsgElement.style.padding = "25px"; + failureMsgElement.style.fontFamily = "sans-serif"; + failureMsgElement.style["marginBlock"] = "0px"; + failureMsgElement.appendChild(document.createTextNode(failMsg)); + messageContainer.appendChild(failureMsgElement); + + const failureMsgElement2 = document.createElement("h4"); + failureMsgElement2.style.color = "#AA0000"; + failureMsgElement2.style.padding = "25px"; + failureMsgElement2.style.fontFamily = "sans-serif"; + failureMsgElement2.style["marginBlock"] = "0px"; + failureMsgElement2.appendChild(document.createTextNode("Try again later")); + messageContainer.appendChild(failureMsgElement2); + + parentElement.appendChild(messageContainer); + }, + resultSuccess: function(result) { + const idx = result >> 2; + + const eagRuntimeJSURL = URL.createObjectURL(new Blob([results[HEAP32[idx]]], { type: "text/javascript;charset=utf-8" })); + const classesWASMURL = URL.createObjectURL(new Blob([results[HEAP32[idx + 1]]], { type: "application/wasm" })); + const classesDeobfTEADBGURL = URL.createObjectURL(new Blob([results[HEAP32[idx + 2]]], { type: "application/octet-stream" })); + const classesDeobfWASMURL = URL.createObjectURL(new Blob([results[HEAP32[idx + 3]]], { type: "application/wasm" })); + + const pressAnyKey = URL.createObjectURL(new Blob([results[HEAP32[idx + 4]]], { type: results[HEAP32[idx + 5]] })); + const crashImg = URL.createObjectURL(new Blob([results[HEAP32[idx + 6]]], { type: results[HEAP32[idx + 7]] })); + const faviconImg = URL.createObjectURL(new Blob([results[HEAP32[idx + 8]]], { type: results[HEAP32[idx + 9]] })); + + const numEPKs = HEAP32[idx + 10]; + const epkFiles = new Array(numEPKs); + for(var i = 0, j; i < numEPKs; ++i) { + j = idx + 11 + i * 3; + epkFiles[i] = { + data: results[HEAP32[j]], + name: results[HEAP32[j + 1]], + path: results[HEAP32[j + 2]] + }; + } + + results.length = 0; + + window.__eaglercraftXLoaderContext = { + getEaglercraftXOpts: function() { + return optsObj; + }, + getEagRuntimeJSURL: function() { + return eagRuntimeJSURL; + }, + getClassesWASMURL: function() { + return classesWASMURL; + }, + getClassesDeobfWASMURL: function() { + return classesDeobfWASMURL; + }, + getClassesTEADBGURL: function() { + return classesDeobfTEADBGURL; + }, + getEPKFiles: function() { + return epkFiles; + }, + getRootElement: function() { + return rootElement; + }, + getMainArgs: function() { + return []; + }, + getImageURL: function(idx) { + switch(idx) { + case 0: + return splashURL; + case 1: + return pressAnyKey; + case 2: + return crashImg; + case 3: + return faviconImg; + default: + return null; + } + }, + runMain: function(fn) { + setTimeout(fn, 10); + } + }; + + const scriptElement = document.createElement("script"); + scriptElement.type = "text/javascript"; + scriptElement.src = eagRuntimeJSURL; + document.head.appendChild(scriptElement); + }, + resultJSPIUnsupported: function(result) { + const idx = result >> 2; + + const crashImgData = results[HEAP32[idx]]; + const crashImgMIME = results[HEAP32[idx + 1]]; + const crashImg = crashImgData ? URL.createObjectURL(new Blob([crashImgData], { type: (crashImgMIME || "image/png") })) : null; + + const markupData = results[HEAP32[idx + 2]]; + const markup = markupData ? UTF8Decoder.decode(markupData) : "

Failed to load error screen

"; + + const parentElement = createCrashParentElement(); + + const img = document.createElement("img"); + img.setAttribute("style", "z-index:100;position:absolute;top:10px;left:calc(50% - 151px);"); + img.src = crashImg; + parentElement.appendChild(img); + + const iframeContainer = document.createElement("div"); + iframeContainer.setAttribute("style", "z-index:100;position:absolute;top:135px;left:10%;right:10%;bottom:50px;background-color:white;border:2px solid #cccccc;"); + iframeContainer.classList.add("_eaglercraftX_jspi_unsupported_container"); + + const iframe = document.createElement("iframe"); + iframe.classList.add("_eaglercraftX_jspi_unsupported_frame"); + iframe.setAttribute("style", "border:none;width:100%;height:100%;"); + iframe.srcdoc = markup; + + iframeContainer.appendChild(iframe); + parentElement.appendChild(iframeContainer); + }, + dbgLog: function(msg) { + console.log("LoaderMain: [INFO] " + UTF8ToString(msg)); + }, + dbgErr: function(msg) { + console.error("LoaderMain: [ERROR] " + UTF8ToString(msg)); + } +}); diff --git a/sources/wasm-gc-teavm-loader/js/pre.js b/sources/wasm-gc-teavm-loader/js/pre.js new file mode 100644 index 0000000..33e4915 --- /dev/null +++ b/sources/wasm-gc-teavm-loader/js/pre.js @@ -0,0 +1,61 @@ +/* + * 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. + * + */ + +Module["locateFile"] = function(path) { + if(path === "loader.wasm") { + return window.__eaglercraftXLoaderContextPre.loaderWASMURL; + }else { + return path; + } +}; + +const rootElement = window.__eaglercraftXLoaderContextPre.rootElement; +const optsObj = window.__eaglercraftXLoaderContextPre.eaglercraftXOpts; +const epwFile = window.__eaglercraftXLoaderContextPre.theEPWFileBuffer; +const splashURL = window.__eaglercraftXLoaderContextPre.splashURL; + +const results = [ null ]; + +function createCrashParentElement() { + var oldSplash = null; + + var node; + while(node = rootElement.lastChild) { + if(!oldSplash) { + oldSplash = node; + } + rootElement.removeChild(node); + } + + const parentElement = document.createElement("div"); + parentElement.classList.add("_eaglercraftX_wrapper_element"); + parentElement.setAttribute("style", "position:relative;width:100%;height:100%;overflow:hidden;"); + + if(oldSplash) { + oldSplash.classList.add("_eaglercraftX_early_splash_element"); + oldSplash.style.position = "absolute"; + oldSplash.style.top = "0px"; + oldSplash.style.left = "0px"; + oldSplash.style.right = "0px"; + oldSplash.style.bottom = "0px"; + oldSplash.style.zIndex = "2"; + parentElement.appendChild(oldSplash); + } + + rootElement.appendChild(parentElement); + + return parentElement; +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jogg/Buffer.java b/sources/wasm-gc-teavm/java/com/jcraft/jogg/Buffer.java new file mode 100644 index 0000000..dde9158 --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jogg/Buffer.java @@ -0,0 +1,293 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jogg; + +public class Buffer { + private static final int BUFFER_INCREMENT = 256; + + private static final int[] mask = { 0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000f, 0x0000001f, + 0x0000003f, 0x0000007f, 0x000000ff, 0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff, 0x00001fff, 0x00003fff, + 0x00007fff, 0x0000ffff, 0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff, 0x001fffff, 0x003fffff, 0x007fffff, + 0x00ffffff, 0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff, + 0xffffffff }; + + int ptr = 0; + byte[] buffer = null; + int endbit = 0; + int endbyte = 0; + int storage = 0; + + public void writeinit() { + buffer = new byte[BUFFER_INCREMENT]; + ptr = 0; + buffer[0] = (byte) '\0'; + storage = BUFFER_INCREMENT; + } + + public void write(byte[] s) { + for (int i = 0; i < s.length; i++) { + if (s[i] == 0) + break; + write(s[i], 8); + } + } + + public void read(byte[] s, int bytes) { + int i = 0; + while (bytes-- != 0) { + s[i++] = (byte) (read(8)); + } + } + + void reset() { + ptr = 0; + buffer[0] = (byte) '\0'; + endbit = endbyte = 0; + } + + public void writeclear() { + buffer = null; + } + + public void readinit(byte[] buf, int bytes) { + readinit(buf, 0, bytes); + } + + public void readinit(byte[] buf, int start, int bytes) { + ptr = start; + buffer = buf; + endbit = endbyte = 0; + storage = bytes; + } + + public void write(int value, int bits) { + if (endbyte + 4 >= storage) { + byte[] foo = new byte[storage + BUFFER_INCREMENT]; + System.arraycopy(buffer, 0, foo, 0, storage); + buffer = foo; + storage += BUFFER_INCREMENT; + } + + value &= mask[bits]; + bits += endbit; + buffer[ptr] |= (byte) (value << endbit); + + if (bits >= 8) { + buffer[ptr + 1] = (byte) (value >>> (8 - endbit)); + if (bits >= 16) { + buffer[ptr + 2] = (byte) (value >>> (16 - endbit)); + if (bits >= 24) { + buffer[ptr + 3] = (byte) (value >>> (24 - endbit)); + if (bits >= 32) { + if (endbit > 0) + buffer[ptr + 4] = (byte) (value >>> (32 - endbit)); + else + buffer[ptr + 4] = 0; + } + } + } + } + + endbyte += bits / 8; + ptr += bits / 8; + endbit = bits & 7; + } + + public int look(int bits) { + int ret; + int m = mask[bits]; + + bits += endbit; + + if (endbyte + 4 >= storage) { + if (endbyte + (bits - 1) / 8 >= storage) + return (-1); + } + + ret = ((buffer[ptr]) & 0xff) >>> endbit; + if (bits > 8) { + ret |= ((buffer[ptr + 1]) & 0xff) << (8 - endbit); + if (bits > 16) { + ret |= ((buffer[ptr + 2]) & 0xff) << (16 - endbit); + if (bits > 24) { + ret |= ((buffer[ptr + 3]) & 0xff) << (24 - endbit); + if (bits > 32 && endbit != 0) { + ret |= ((buffer[ptr + 4]) & 0xff) << (32 - endbit); + } + } + } + } + return (m & ret); + } + + public int look1() { + if (endbyte >= storage) + return (-1); + return ((buffer[ptr] >> endbit) & 1); + } + + public void adv(int bits) { + bits += endbit; + ptr += bits / 8; + endbyte += bits / 8; + endbit = bits & 7; + } + + public void adv1() { + ++endbit; + if (endbit > 7) { + endbit = 0; + ptr++; + endbyte++; + } + } + + public int read(int bits) { + int ret; + int m = mask[bits]; + + bits += endbit; + + if (endbyte + 4 >= storage) { + ret = -1; + if (endbyte + (bits - 1) / 8 >= storage) { + ptr += bits / 8; + endbyte += bits / 8; + endbit = bits & 7; + return (ret); + } + } + + ret = ((buffer[ptr]) & 0xff) >>> endbit; + if (bits > 8) { + ret |= ((buffer[ptr + 1]) & 0xff) << (8 - endbit); + if (bits > 16) { + ret |= ((buffer[ptr + 2]) & 0xff) << (16 - endbit); + if (bits > 24) { + ret |= ((buffer[ptr + 3]) & 0xff) << (24 - endbit); + if (bits > 32 && endbit != 0) { + ret |= ((buffer[ptr + 4]) & 0xff) << (32 - endbit); + } + } + } + } + + ret &= m; + + ptr += bits / 8; + endbyte += bits / 8; + endbit = bits & 7; + return (ret); + } + + public int readB(int bits) { + int ret; + int m = 32 - bits; + + bits += endbit; + + if (endbyte + 4 >= storage) { + /* not the main path */ + ret = -1; + if (endbyte * 8 + bits > storage * 8) { + ptr += bits / 8; + endbyte += bits / 8; + endbit = bits & 7; + return (ret); + } + } + + ret = (buffer[ptr] & 0xff) << (24 + endbit); + if (bits > 8) { + ret |= (buffer[ptr + 1] & 0xff) << (16 + endbit); + if (bits > 16) { + ret |= (buffer[ptr + 2] & 0xff) << (8 + endbit); + if (bits > 24) { + ret |= (buffer[ptr + 3] & 0xff) << (endbit); + if (bits > 32 && (endbit != 0)) + ret |= (buffer[ptr + 4] & 0xff) >> (8 - endbit); + } + } + } + ret = (ret >>> (m >> 1)) >>> ((m + 1) >> 1); + + ptr += bits / 8; + endbyte += bits / 8; + endbit = bits & 7; + return (ret); + } + + public int read1() { + int ret; + if (endbyte >= storage) { + ret = -1; + endbit++; + if (endbit > 7) { + endbit = 0; + ptr++; + endbyte++; + } + return (ret); + } + + ret = (buffer[ptr] >> endbit) & 1; + + endbit++; + if (endbit > 7) { + endbit = 0; + ptr++; + endbyte++; + } + return (ret); + } + + public int bytes() { + return (endbyte + (endbit + 7) / 8); + } + + public int bits() { + return (endbyte * 8 + endbit); + } + + public byte[] buffer() { + return (buffer); + } + + public static int ilog(int v) { + int ret = 0; + while (v > 0) { + ret++; + v >>>= 1; + } + return (ret); + } + + public static void report(String in) { + System.err.println(in); + System.exit(1); + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jogg/Packet.java b/sources/wasm-gc-teavm/java/com/jcraft/jogg/Packet.java new file mode 100644 index 0000000..d78e6f5 --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jogg/Packet.java @@ -0,0 +1,45 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jogg; + +public class Packet { + public byte[] packet_base; + public int packet; + public int bytes; + public int b_o_s; + public int e_o_s; + + public long granulepos; + + /** + * sequence number for decode; the framing knows where there's a hole in the + * data, but we need coupling so that the codec (which is in a seperate + * abstraction layer) also knows about the gap + */ + public long packetno; + +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jogg/Page.java b/sources/wasm-gc-teavm/java/com/jcraft/jogg/Page.java new file mode 100644 index 0000000..f9d29fe --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jogg/Page.java @@ -0,0 +1,130 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jogg; + +public class Page { + private static int[] crc_lookup = new int[256]; + static { + for (int i = 0; i < crc_lookup.length; i++) { + crc_lookup[i] = crc_entry(i); + } + } + + private static int crc_entry(int index) { + int r = index << 24; + for (int i = 0; i < 8; i++) { + if ((r & 0x80000000) != 0) { + r = (r << 1) ^ 0x04c11db7; /* + * The same as the ethernet generator polynomial, although we use an + * unreflected alg and an init/final of 0, not 0xffffffff + */ + } else { + r <<= 1; + } + } + return (r & 0xffffffff); + } + + public byte[] header_base; + public int header; + public int header_len; + public byte[] body_base; + public int body; + public int body_len; + + int version() { + return header_base[header + 4] & 0xff; + } + + int continued() { + return (header_base[header + 5] & 0x01); + } + + public int bos() { + return (header_base[header + 5] & 0x02); + } + + public int eos() { + return (header_base[header + 5] & 0x04); + } + + public long granulepos() { + long foo = header_base[header + 13] & 0xff; + foo = (foo << 8) | (header_base[header + 12] & 0xff); + foo = (foo << 8) | (header_base[header + 11] & 0xff); + foo = (foo << 8) | (header_base[header + 10] & 0xff); + foo = (foo << 8) | (header_base[header + 9] & 0xff); + foo = (foo << 8) | (header_base[header + 8] & 0xff); + foo = (foo << 8) | (header_base[header + 7] & 0xff); + foo = (foo << 8) | (header_base[header + 6] & 0xff); + return (foo); + } + + public int serialno() { + return (header_base[header + 14] & 0xff) | ((header_base[header + 15] & 0xff) << 8) + | ((header_base[header + 16] & 0xff) << 16) | ((header_base[header + 17] & 0xff) << 24); + } + + int pageno() { + return (header_base[header + 18] & 0xff) | ((header_base[header + 19] & 0xff) << 8) + | ((header_base[header + 20] & 0xff) << 16) | ((header_base[header + 21] & 0xff) << 24); + } + + void checksum() { + int crc_reg = 0; + + for (int i = 0; i < header_len; i++) { + crc_reg = (crc_reg << 8) ^ crc_lookup[((crc_reg >>> 24) & 0xff) ^ (header_base[header + i] & 0xff)]; + } + for (int i = 0; i < body_len; i++) { + crc_reg = (crc_reg << 8) ^ crc_lookup[((crc_reg >>> 24) & 0xff) ^ (body_base[body + i] & 0xff)]; + } + header_base[header + 22] = (byte) crc_reg; + header_base[header + 23] = (byte) (crc_reg >>> 8); + header_base[header + 24] = (byte) (crc_reg >>> 16); + header_base[header + 25] = (byte) (crc_reg >>> 24); + } + + public Page copy() { + return copy(new Page()); + } + + public Page copy(Page p) { + byte[] tmp = new byte[header_len]; + System.arraycopy(header_base, header, tmp, 0, header_len); + p.header_len = header_len; + p.header_base = tmp; + p.header = 0; + tmp = new byte[body_len]; + System.arraycopy(body_base, body, tmp, 0, body_len); + p.body_len = body_len; + p.body_base = tmp; + p.body = 0; + return p; + } + +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jogg/StreamState.java b/sources/wasm-gc-teavm/java/com/jcraft/jogg/StreamState.java new file mode 100644 index 0000000..e9c3663 --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jogg/StreamState.java @@ -0,0 +1,538 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jogg; + +public class StreamState { + byte[] body_data; /* bytes from packet bodies */ + int body_storage; /* storage elements allocated */ + int body_fill; /* elements stored; fill mark */ + private int body_returned; /* elements of fill returned */ + + int[] lacing_vals; /* The values that will go to the segment table */ + long[] granule_vals; /* + * pcm_pos values for headers. Not compact this way, but it is simple coupled to + * the lacing fifo + */ + int lacing_storage; + int lacing_fill; + int lacing_packet; + int lacing_returned; + + byte[] header = new byte[282]; /* working space for header encode */ + int header_fill; + + public int e_o_s; /* + * set when we have buffered the last packet in the logical bitstream + */ + int b_o_s; /* + * set after we've written the initial page of a logical bitstream + */ + int serialno; + int pageno; + long packetno; /* + * sequence number for decode; the framing knows where there's a hole in the + * data, but we need coupling so that the codec (which is in a seperate + * abstraction layer) also knows about the gap + */ + long granulepos; + + public StreamState() { + init(); + } + + StreamState(int serialno) { + this(); + init(serialno); + } + + void init() { + body_storage = 16 * 1024; + body_data = new byte[body_storage]; + lacing_storage = 1024; + lacing_vals = new int[lacing_storage]; + granule_vals = new long[lacing_storage]; + } + + public void init(int serialno) { + if (body_data == null) { + init(); + } else { + for (int i = 0; i < body_data.length; i++) + body_data[i] = 0; + for (int i = 0; i < lacing_vals.length; i++) + lacing_vals[i] = 0; + for (int i = 0; i < granule_vals.length; i++) + granule_vals[i] = 0; + } + this.serialno = serialno; + } + + public void clear() { + body_data = null; + lacing_vals = null; + granule_vals = null; + } + + void destroy() { + clear(); + } + + void body_expand(int needed) { + if (body_storage <= body_fill + needed) { + body_storage += (needed + 1024); + byte[] foo = new byte[body_storage]; + System.arraycopy(body_data, 0, foo, 0, body_data.length); + body_data = foo; + } + } + + void lacing_expand(int needed) { + if (lacing_storage <= lacing_fill + needed) { + lacing_storage += (needed + 32); + int[] foo = new int[lacing_storage]; + System.arraycopy(lacing_vals, 0, foo, 0, lacing_vals.length); + lacing_vals = foo; + + long[] bar = new long[lacing_storage]; + System.arraycopy(granule_vals, 0, bar, 0, granule_vals.length); + granule_vals = bar; + } + } + + /* submit data to the internal buffer of the framing engine */ + public int packetin(Packet op) { + int lacing_val = op.bytes / 255 + 1; + + if (body_returned != 0) { + /* + * advance packet data according to the body_returned pointer. We had to keep it + * around to return a pointer into the buffer last call + */ + + body_fill -= body_returned; + if (body_fill != 0) { + System.arraycopy(body_data, body_returned, body_data, 0, body_fill); + } + body_returned = 0; + } + + /* make sure we have the buffer storage */ + body_expand(op.bytes); + lacing_expand(lacing_val); + + /* + * Copy in the submitted packet. Yes, the copy is a waste; this is the liability + * of overly clean abstraction for the time being. It will actually be fairly + * easy to eliminate the extra copy in the future + */ + + System.arraycopy(op.packet_base, op.packet, body_data, body_fill, op.bytes); + body_fill += op.bytes; + + /* Store lacing vals for this packet */ + int j; + for (j = 0; j < lacing_val - 1; j++) { + lacing_vals[lacing_fill + j] = 255; + granule_vals[lacing_fill + j] = granulepos; + } + lacing_vals[lacing_fill + j] = (op.bytes) % 255; + granulepos = granule_vals[lacing_fill + j] = op.granulepos; + + /* flag the first segment as the beginning of the packet */ + lacing_vals[lacing_fill] |= 0x100; + + lacing_fill += lacing_val; + + /* for the sake of completeness */ + packetno++; + + if (op.e_o_s != 0) + e_o_s = 1; + return (0); + } + + public int packetout(Packet op) { + + /* + * The last part of decode. We have the stream broken into packet segments. Now + * we need to group them into packets (or return the out of sync markers) + */ + + int ptr = lacing_returned; + + if (lacing_packet <= ptr) { + return (0); + } + + if ((lacing_vals[ptr] & 0x400) != 0) { + /* We lost sync here; let the app know */ + lacing_returned++; + + /* + * we need to tell the codec there's a gap; it might need to handle previous + * packet dependencies. + */ + packetno++; + return (-1); + } + + /* Gather the whole packet. We'll have no holes or a partial packet */ + { + int size = lacing_vals[ptr] & 0xff; + int bytes = 0; + + op.packet_base = body_data; + op.packet = body_returned; + op.e_o_s = lacing_vals[ptr] & 0x200; /* last packet of the stream? */ + op.b_o_s = lacing_vals[ptr] & 0x100; /* first packet of the stream? */ + bytes += size; + + while (size == 255) { + int val = lacing_vals[++ptr]; + size = val & 0xff; + if ((val & 0x200) != 0) + op.e_o_s = 0x200; + bytes += size; + } + + op.packetno = packetno; + op.granulepos = granule_vals[ptr]; + op.bytes = bytes; + + body_returned += bytes; + + lacing_returned = ptr + 1; + } + packetno++; + return (1); + } + + // add the incoming page to the stream state; we decompose the page + // into packet segments here as well. + + public int pagein(Page og) { + byte[] header_base = og.header_base; + int header = og.header; + byte[] body_base = og.body_base; + int body = og.body; + int bodysize = og.body_len; + int segptr = 0; + + int version = og.version(); + int continued = og.continued(); + int bos = og.bos(); + int eos = og.eos(); + long granulepos = og.granulepos(); + int _serialno = og.serialno(); + int _pageno = og.pageno(); + int segments = header_base[header + 26] & 0xff; + + // clean up 'returned data' + { + int lr = lacing_returned; + int br = body_returned; + + // body data + if (br != 0) { + body_fill -= br; + if (body_fill != 0) { + System.arraycopy(body_data, br, body_data, 0, body_fill); + } + body_returned = 0; + } + + if (lr != 0) { + // segment table + if ((lacing_fill - lr) != 0) { + System.arraycopy(lacing_vals, lr, lacing_vals, 0, lacing_fill - lr); + System.arraycopy(granule_vals, lr, granule_vals, 0, lacing_fill - lr); + } + lacing_fill -= lr; + lacing_packet -= lr; + lacing_returned = 0; + } + } + + // check the serial number + if (_serialno != serialno) + return (-1); + if (version > 0) + return (-1); + + lacing_expand(segments + 1); + + // are we in sequence? + if (_pageno != pageno) { + int i; + + // unroll previous partial packet (if any) + for (i = lacing_packet; i < lacing_fill; i++) { + body_fill -= lacing_vals[i] & 0xff; + // System.out.println("??"); + } + lacing_fill = lacing_packet; + + // make a note of dropped data in segment table + if (pageno != -1) { + lacing_vals[lacing_fill++] = 0x400; + lacing_packet++; + } + + // are we a 'continued packet' page? If so, we'll need to skip + // some segments + if (continued != 0) { + bos = 0; + for (; segptr < segments; segptr++) { + int val = (header_base[header + 27 + segptr] & 0xff); + body += val; + bodysize -= val; + if (val < 255) { + segptr++; + break; + } + } + } + } + + if (bodysize != 0) { + body_expand(bodysize); + System.arraycopy(body_base, body, body_data, body_fill, bodysize); + body_fill += bodysize; + } + + { + int saved = -1; + while (segptr < segments) { + int val = (header_base[header + 27 + segptr] & 0xff); + lacing_vals[lacing_fill] = val; + granule_vals[lacing_fill] = -1; + + if (bos != 0) { + lacing_vals[lacing_fill] |= 0x100; + bos = 0; + } + + if (val < 255) + saved = lacing_fill; + + lacing_fill++; + segptr++; + + if (val < 255) + lacing_packet = lacing_fill; + } + + /* set the granulepos on the last pcmval of the last full packet */ + if (saved != -1) { + granule_vals[saved] = granulepos; + } + } + + if (eos != 0) { + e_o_s = 1; + if (lacing_fill > 0) + lacing_vals[lacing_fill - 1] |= 0x200; + } + + pageno = _pageno + 1; + return (0); + } + + /* + * This will flush remaining packets into a page (returning nonzero), even if + * there is not enough data to trigger a flush normally (undersized page). If + * there are no packets or partial packets to flush, ogg_stream_flush returns 0. + * Note that ogg_stream_flush will try to flush a normal sized page like + * ogg_stream_pageout; a call to ogg_stream_flush does not gurantee that all + * packets have flushed. Only a return value of 0 from ogg_stream_flush + * indicates all packet data is flushed into pages. + * + * ogg_stream_page will flush the last page in a stream even if it's undersized; + * you almost certainly want to use ogg_stream_pageout (and *not* + * ogg_stream_flush) unless you need to flush an undersized page in the middle + * of a stream for some reason. + */ + + public int flush(Page og) { + + int i; + int vals = 0; + int maxvals = (lacing_fill > 255 ? 255 : lacing_fill); + int bytes = 0; + int acc = 0; + long granule_pos = granule_vals[0]; + + if (maxvals == 0) + return (0); + + /* construct a page */ + /* decide how many segments to include */ + + /* + * If this is the initial header case, the first page must only include the + * initial header packet + */ + if (b_o_s == 0) { /* 'initial header page' case */ + granule_pos = 0; + for (vals = 0; vals < maxvals; vals++) { + if ((lacing_vals[vals] & 0x0ff) < 255) { + vals++; + break; + } + } + } else { + for (vals = 0; vals < maxvals; vals++) { + if (acc > 4096) + break; + acc += (lacing_vals[vals] & 0x0ff); + granule_pos = granule_vals[vals]; + } + } + + /* construct the header in temp storage */ + System.arraycopy("OggS".getBytes(), 0, header, 0, 4); + + /* stream structure version */ + header[4] = 0x00; + + /* continued packet flag? */ + header[5] = 0x00; + if ((lacing_vals[0] & 0x100) == 0) + header[5] |= 0x01; + /* first page flag? */ + if (b_o_s == 0) + header[5] |= 0x02; + /* last page flag? */ + if (e_o_s != 0 && lacing_fill == vals) + header[5] |= 0x04; + b_o_s = 1; + + /* 64 bits of PCM position */ + for (i = 6; i < 14; i++) { + header[i] = (byte) granule_pos; + granule_pos >>>= 8; + } + + /* 32 bits of stream serial number */ + { + int _serialno = serialno; + for (i = 14; i < 18; i++) { + header[i] = (byte) _serialno; + _serialno >>>= 8; + } + } + + /* + * 32 bits of page counter (we have both counter and page header because this + * val can roll over) + */ + if (pageno == -1) + pageno = 0; /* + * because someone called stream_reset; this would be a strange thing to do in + * an encode stream, but it has plausible uses + */ + { + int _pageno = pageno++; + for (i = 18; i < 22; i++) { + header[i] = (byte) _pageno; + _pageno >>>= 8; + } + } + + /* zero for computation; filled in later */ + header[22] = 0; + header[23] = 0; + header[24] = 0; + header[25] = 0; + + /* segment table */ + header[26] = (byte) vals; + for (i = 0; i < vals; i++) { + header[i + 27] = (byte) lacing_vals[i]; + bytes += (header[i + 27] & 0xff); + } + + /* set pointers in the ogg_page struct */ + og.header_base = header; + og.header = 0; + og.header_len = header_fill = vals + 27; + og.body_base = body_data; + og.body = body_returned; + og.body_len = bytes; + + /* advance the lacing data and set the body_returned pointer */ + + lacing_fill -= vals; + System.arraycopy(lacing_vals, vals, lacing_vals, 0, lacing_fill * 4); + System.arraycopy(granule_vals, vals, granule_vals, 0, lacing_fill * 8); + body_returned += bytes; + + /* calculate the checksum */ + + og.checksum(); + + /* done */ + return (1); + } + + /* + * This constructs pages from buffered packet segments. The pointers returned + * are to static buffers; do not free. The returned buffers are good only until + * the next call (using the same ogg_stream_state) + */ + public int pageout(Page og) { + if ((e_o_s != 0 && lacing_fill != 0) || /* 'were done, now flush' case */ + body_fill - body_returned > 4096 || /* 'page nominal size' case */ + lacing_fill >= 255 || /* 'segment table full' case */ + (lacing_fill != 0 && b_o_s == 0)) { /* 'initial header page' case */ + return flush(og); + } + return 0; + } + + public int eof() { + return e_o_s; + } + + public int reset() { + body_fill = 0; + body_returned = 0; + + lacing_fill = 0; + lacing_packet = 0; + lacing_returned = 0; + + header_fill = 0; + + e_o_s = 0; + b_o_s = 0; + pageno = -1; + packetno = 0; + granulepos = 0; + return (0); + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jogg/SyncState.java b/sources/wasm-gc-teavm/java/com/jcraft/jogg/SyncState.java new file mode 100644 index 0000000..a706c4f --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jogg/SyncState.java @@ -0,0 +1,273 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jogg; + +// DECODING PRIMITIVES: packet streaming layer + +// This has two layers to place more of the multi-serialno and paging +// control in the application's hands. First, we expose a data buffer +// using ogg_decode_buffer(). The app either copies into the +// buffer, or passes it directly to read(), etc. We then call +// ogg_decode_wrote() to tell how many bytes we just added. +// +// Pages are returned (pointers into the buffer in ogg_sync_state) +// by ogg_decode_stream(). The page is then submitted to +// ogg_decode_page() along with the appropriate +// ogg_stream_state* (ie, matching serialno). We then get raw +// packets out calling ogg_stream_packet() with a +// ogg_stream_state. See the 'frame-prog.txt' docs for details and +// example code. + +public class SyncState { + + public byte[] data; + int storage; + int fill; + int returned; + + int unsynced; + int headerbytes; + int bodybytes; + + public int clear() { + data = null; + return (0); + } + + public int buffer(int size) { + // first, clear out any space that has been previously returned + if (returned != 0) { + fill -= returned; + if (fill > 0) { + System.arraycopy(data, returned, data, 0, fill); + } + returned = 0; + } + + if (size > storage - fill) { + // We need to extend the internal buffer + int newsize = size + fill + 4096; // an extra page to be nice + if (data != null) { + byte[] foo = new byte[newsize]; + System.arraycopy(data, 0, foo, 0, data.length); + data = foo; + } else { + data = new byte[newsize]; + } + storage = newsize; + } + + return (fill); + } + + public int wrote(int bytes) { + if (fill + bytes > storage) + return (-1); + fill += bytes; + return (0); + } + + // sync the stream. This is meant to be useful for finding page + // boundaries. + // + // return values for this: + // -n) skipped n bytes + // 0) page not ready; more data (no bytes skipped) + // n) page synced at current location; page length n bytes + private Page pageseek = new Page(); + private byte[] chksum = new byte[4]; + + public int pageseek(Page og) { + int page = returned; + int next; + int bytes = fill - returned; + + if (headerbytes == 0) { + int _headerbytes, i; + if (bytes < 27) + return (0); // not enough for a header + + /* verify capture pattern */ + if (data[page] != 'O' || data[page + 1] != 'g' || data[page + 2] != 'g' || data[page + 3] != 'S') { + headerbytes = 0; + bodybytes = 0; + + // search for possible capture + next = 0; + for (int ii = 0; ii < bytes - 1; ii++) { + if (data[page + 1 + ii] == 'O') { + next = page + 1 + ii; + break; + } + } + // next=memchr(page+1,'O',bytes-1); + if (next == 0) + next = fill; + + returned = next; + return (-(next - page)); + } + _headerbytes = (data[page + 26] & 0xff) + 27; + if (bytes < _headerbytes) + return (0); // not enough for header + seg table + + // count up body length in the segment table + + for (i = 0; i < (data[page + 26] & 0xff); i++) { + bodybytes += (data[page + 27 + i] & 0xff); + } + headerbytes = _headerbytes; + } + + if (bodybytes + headerbytes > bytes) + return (0); + + // The whole test page is buffered. Verify the checksum + synchronized (chksum) { + // Grab the checksum bytes, set the header field to zero + + System.arraycopy(data, page + 22, chksum, 0, 4); + data[page + 22] = 0; + data[page + 23] = 0; + data[page + 24] = 0; + data[page + 25] = 0; + + // set up a temp page struct and recompute the checksum + Page log = pageseek; + log.header_base = data; + log.header = page; + log.header_len = headerbytes; + + log.body_base = data; + log.body = page + headerbytes; + log.body_len = bodybytes; + log.checksum(); + + // Compare + if (chksum[0] != data[page + 22] || chksum[1] != data[page + 23] || chksum[2] != data[page + 24] + || chksum[3] != data[page + 25]) { + // D'oh. Mismatch! Corrupt page (or miscapture and not a page at all) + // replace the computed checksum with the one actually read in + System.arraycopy(chksum, 0, data, page + 22, 4); + // Bad checksum. Lose sync */ + + headerbytes = 0; + bodybytes = 0; + // search for possible capture + next = 0; + for (int ii = 0; ii < bytes - 1; ii++) { + if (data[page + 1 + ii] == 'O') { + next = page + 1 + ii; + break; + } + } + // next=memchr(page+1,'O',bytes-1); + if (next == 0) + next = fill; + returned = next; + return (-(next - page)); + } + } + + // yes, have a whole page all ready to go + { + page = returned; + + if (og != null) { + og.header_base = data; + og.header = page; + og.header_len = headerbytes; + og.body_base = data; + og.body = page + headerbytes; + og.body_len = bodybytes; + } + + unsynced = 0; + returned += (bytes = headerbytes + bodybytes); + headerbytes = 0; + bodybytes = 0; + return (bytes); + } + } + + // sync the stream and get a page. Keep trying until we find a page. + // Supress 'sync errors' after reporting the first. + // + // return values: + // -1) recapture (hole in data) + // 0) need more data + // 1) page returned + // + // Returns pointers into buffered data; invalidated by next call to + // _stream, _clear, _init, or _buffer + + public int pageout(Page og) { + // all we need to do is verify a page at the head of the stream + // buffer. If it doesn't verify, we look for the next potential + // frame + + while (true) { + int ret = pageseek(og); + if (ret > 0) { + // have a page + return (1); + } + if (ret == 0) { + // need more data + return (0); + } + + // head did not start a synced page... skipped some bytes + if (unsynced == 0) { + unsynced = 1; + return (-1); + } + // loop. keep looking + } + } + + // clear things to an initial state. Good to call, eg, before seeking + public int reset() { + fill = 0; + returned = 0; + unsynced = 0; + headerbytes = 0; + bodybytes = 0; + return (0); + } + + public void init() { + } + + public int getDataOffset() { + return returned; + } + + public int getBufferOffset() { + return fill; + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Block.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Block.java new file mode 100644 index 0000000..0a0e166 --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Block.java @@ -0,0 +1,126 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +public class Block { + /// necessary stream state for linking to the framing abstraction + float[][] pcm = new float[0][]; // this is a pointer into local storage + Buffer opb = new Buffer(); + + int lW; + int W; + int nW; + int pcmend; + int mode; + + int eofflag; + long granulepos; + long sequence; + DspState vd; // For read-only access of configuration + + // bitmetrics for the frame + int glue_bits; + int time_bits; + int floor_bits; + int res_bits; + + public Block(DspState vd) { + this.vd = vd; + if (vd.analysisp != 0) { + opb.writeinit(); + } + } + + public void init(DspState vd) { + this.vd = vd; + } + + public int clear() { + if (vd != null) { + if (vd.analysisp != 0) { + opb.writeclear(); + } + } + return (0); + } + + public int synthesis(Packet op) { + Info vi = vd.vi; + + // first things first. Make sure decode is ready + opb.readinit(op.packet_base, op.packet, op.bytes); + + // Check the packet type + if (opb.read(1) != 0) { + // Oops. This is not an audio data packet + return (-1); + } + + // read our mode and pre/post windowsize + int _mode = opb.read(vd.modebits); + if (_mode == -1) + return (-1); + + mode = _mode; + W = vi.mode_param[mode].blockflag; + if (W != 0) { + lW = opb.read(1); + nW = opb.read(1); + if (nW == -1) + return (-1); + } else { + lW = 0; + nW = 0; + } + + // more setup + granulepos = op.granulepos; + sequence = op.packetno - 3; // first block is third packet + eofflag = op.e_o_s; + + // alloc pcm passback storage + pcmend = vi.blocksizes[W]; + if (pcm.length < vi.channels) { + pcm = new float[vi.channels][]; + } + for (int i = 0; i < vi.channels; i++) { + if (pcm[i] == null || pcm[i].length < pcmend) { + pcm[i] = new float[pcmend]; + } else { + for (int j = 0; j < pcmend; j++) { + pcm[i][j] = 0; + } + } + } + + // unpack_header enforces range checking + int type = vi.map_type[vi.mode_param[mode].mapping]; + return (FuncMapping.mapping_P[type].inverse(this, vd.mode[mode])); + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/CodeBook.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/CodeBook.java new file mode 100644 index 0000000..c6c7f9f --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/CodeBook.java @@ -0,0 +1,471 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class CodeBook { + int dim; // codebook dimensions (elements per vector) + int entries; // codebook entries + StaticCodeBook c = new StaticCodeBook(); + + float[] valuelist; // list of dim*entries actual entry values + int[] codelist; // list of bitstream codewords for each entry + DecodeAux decode_tree; + + // returns the number of bits + int encode(int a, Buffer b) { + b.write(codelist[a], c.lengthlist[a]); + return (c.lengthlist[a]); + } + + // One the encode side, our vector writers are each designed for a + // specific purpose, and the encoder is not flexible without modification: + // + // The LSP vector coder uses a single stage nearest-match with no + // interleave, so no step and no error return. This is specced by floor0 + // and doesn't change. + // + // Residue0 encoding interleaves, uses multiple stages, and each stage + // peels of a specific amount of resolution from a lattice (thus we want + // to match by threshhold, not nearest match). Residue doesn't *have* to + // be encoded that way, but to change it, one will need to add more + // infrastructure on the encode side (decode side is specced and simpler) + + // floor0 LSP (single stage, non interleaved, nearest match) + // returns entry number and *modifies a* to the quantization value + int errorv(float[] a) { + int best = best(a, 1); + for (int k = 0; k < dim; k++) { + a[k] = valuelist[best * dim + k]; + } + return (best); + } + + // returns the number of bits and *modifies a* to the quantization value + int encodev(int best, float[] a, Buffer b) { + for (int k = 0; k < dim; k++) { + a[k] = valuelist[best * dim + k]; + } + return (encode(best, b)); + } + + // res0 (multistage, interleave, lattice) + // returns the number of bits and *modifies a* to the remainder value + int encodevs(float[] a, Buffer b, int step, int addmul) { + int best = besterror(a, step, addmul); + return (encode(best, b)); + } + + private int[] t = new int[15]; // decodevs_add is synchronized for re-using t. + + synchronized int decodevs_add(float[] a, int offset, Buffer b, int n) { + int step = n / dim; + int entry; + int i, j, o; + + if (t.length < step) { + t = new int[step]; + } + + for (i = 0; i < step; i++) { + entry = decode(b); + if (entry == -1) + return (-1); + t[i] = entry * dim; + } + for (i = 0, o = 0; i < dim; i++, o += step) { + for (j = 0; j < step; j++) { + a[offset + o + j] += valuelist[t[j] + i]; + } + } + + return (0); + } + + int decodev_add(float[] a, int offset, Buffer b, int n) { + int i, j, entry; + int t; + + if (dim > 8) { + for (i = 0; i < n;) { + entry = decode(b); + if (entry == -1) + return (-1); + t = entry * dim; + for (j = 0; j < dim;) { + a[offset + (i++)] += valuelist[t + (j++)]; + } + } + } else { + for (i = 0; i < n;) { + entry = decode(b); + if (entry == -1) + return (-1); + t = entry * dim; + j = 0; + switch (dim) { + case 8: + a[offset + (i++)] += valuelist[t + (j++)]; + case 7: + a[offset + (i++)] += valuelist[t + (j++)]; + case 6: + a[offset + (i++)] += valuelist[t + (j++)]; + case 5: + a[offset + (i++)] += valuelist[t + (j++)]; + case 4: + a[offset + (i++)] += valuelist[t + (j++)]; + case 3: + a[offset + (i++)] += valuelist[t + (j++)]; + case 2: + a[offset + (i++)] += valuelist[t + (j++)]; + case 1: + a[offset + (i++)] += valuelist[t + (j++)]; + case 0: + break; + } + } + } + return (0); + } + + int decodev_set(float[] a, int offset, Buffer b, int n) { + int i, j, entry; + int t; + + for (i = 0; i < n;) { + entry = decode(b); + if (entry == -1) + return (-1); + t = entry * dim; + for (j = 0; j < dim;) { + a[offset + i++] = valuelist[t + (j++)]; + } + } + return (0); + } + + int decodevv_add(float[][] a, int offset, int ch, Buffer b, int n) { + int i, j, entry; + int chptr = 0; + + for (i = offset / ch; i < (offset + n) / ch;) { + entry = decode(b); + if (entry == -1) + return (-1); + + int t = entry * dim; + for (j = 0; j < dim; j++) { + a[chptr++][i] += valuelist[t + j]; + if (chptr == ch) { + chptr = 0; + i++; + } + } + } + return (0); + } + + // Decode side is specced and easier, because we don't need to find + // matches using different criteria; we simply read and map. There are + // two things we need to do 'depending': + // + // We may need to support interleave. We don't really, but it's + // convenient to do it here rather than rebuild the vector later. + // + // Cascades may be additive or multiplicitive; this is not inherent in + // the codebook, but set in the code using the codebook. Like + // interleaving, it's easiest to do it here. + // stage==0 -> declarative (set the value) + // stage==1 -> additive + // stage==2 -> multiplicitive + + // returns the entry number or -1 on eof + int decode(Buffer b) { + int ptr = 0; + DecodeAux t = decode_tree; + int lok = b.look(t.tabn); + + if (lok >= 0) { + ptr = t.tab[lok]; + b.adv(t.tabl[lok]); + if (ptr <= 0) { + return -ptr; + } + } + do { + switch (b.read1()) { + case 0: + ptr = t.ptr0[ptr]; + break; + case 1: + ptr = t.ptr1[ptr]; + break; + case -1: + default: + return (-1); + } + } while (ptr > 0); + return (-ptr); + } + + // returns the entry number or -1 on eof + int decodevs(float[] a, int index, Buffer b, int step, int addmul) { + int entry = decode(b); + if (entry == -1) + return (-1); + switch (addmul) { + case -1: + for (int i = 0, o = 0; i < dim; i++, o += step) + a[index + o] = valuelist[entry * dim + i]; + break; + case 0: + for (int i = 0, o = 0; i < dim; i++, o += step) + a[index + o] += valuelist[entry * dim + i]; + break; + case 1: + for (int i = 0, o = 0; i < dim; i++, o += step) + a[index + o] *= valuelist[entry * dim + i]; + break; + default: + // System.err.println("CodeBook.decodeves: addmul="+addmul); + } + return (entry); + } + + int best(float[] a, int step) { + // brute force it! + { + int besti = -1; + float best = 0.f; + int e = 0; + for (int i = 0; i < entries; i++) { + if (c.lengthlist[i] > 0) { + float _this = dist(dim, valuelist, e, a, step); + if (besti == -1 || _this < best) { + best = _this; + besti = i; + } + } + e += dim; + } + return (besti); + } + } + + // returns the entry number and *modifies a* to the remainder value + int besterror(float[] a, int step, int addmul) { + int best = best(a, step); + switch (addmul) { + case 0: + for (int i = 0, o = 0; i < dim; i++, o += step) + a[o] -= valuelist[best * dim + i]; + break; + case 1: + for (int i = 0, o = 0; i < dim; i++, o += step) { + float val = valuelist[best * dim + i]; + if (val == 0) { + a[o] = 0; + } else { + a[o] /= val; + } + } + break; + } + return (best); + } + + void clear() { + } + + private static float dist(int el, float[] ref, int index, float[] b, int step) { + float acc = (float) 0.; + for (int i = 0; i < el; i++) { + float val = (ref[index + i] - b[i * step]); + acc += val * val; + } + return (acc); + } + + int init_decode(StaticCodeBook s) { + c = s; + entries = s.entries; + dim = s.dim; + valuelist = s.unquantize(); + + decode_tree = make_decode_tree(); + if (decode_tree == null) { + clear(); + return (-1); + } + return (0); + } + + // given a list of word lengths, generate a list of codewords. Works + // for length ordered or unordered, always assigns the lowest valued + // codewords first. Extended to handle unused entries (length 0) + static int[] make_words(int[] l, int n) { + int[] marker = new int[33]; + int[] r = new int[n]; + + for (int i = 0; i < n; i++) { + int length = l[i]; + if (length > 0) { + int entry = marker[length]; + + // when we claim a node for an entry, we also claim the nodes + // below it (pruning off the imagined tree that may have dangled + // from it) as well as blocking the use of any nodes directly + // above for leaves + + // update ourself + if (length < 32 && (entry >>> length) != 0) { + // error condition; the lengths must specify an overpopulated tree + // free(r); + return (null); + } + r[i] = entry; + + // Look to see if the next shorter marker points to the node + // above. if so, update it and repeat. + { + for (int j = length; j > 0; j--) { + if ((marker[j] & 1) != 0) { + // have to jump branches + if (j == 1) + marker[1]++; + else + marker[j] = marker[j - 1] << 1; + break; // invariant says next upper marker would already + // have been moved if it was on the same path + } + marker[j]++; + } + } + + // prune the tree; the implicit invariant says all the longer + // markers were dangling from our just-taken node. Dangle them + // from our *new* node. + for (int j = length + 1; j < 33; j++) { + if ((marker[j] >>> 1) == entry) { + entry = marker[j]; + marker[j] = marker[j - 1] << 1; + } else { + break; + } + } + } + } + + // bitreverse the words because our bitwise packer/unpacker is LSb + // endian + for (int i = 0; i < n; i++) { + int temp = 0; + for (int j = 0; j < l[i]; j++) { + temp <<= 1; + temp |= (r[i] >>> j) & 1; + } + r[i] = temp; + } + + return (r); + } + + // build the decode helper tree from the codewords + DecodeAux make_decode_tree() { + int top = 0; + DecodeAux t = new DecodeAux(); + int[] ptr0 = t.ptr0 = new int[entries * 2]; + int[] ptr1 = t.ptr1 = new int[entries * 2]; + int[] codelist = make_words(c.lengthlist, c.entries); + + if (codelist == null) + return (null); + t.aux = entries * 2; + + for (int i = 0; i < entries; i++) { + if (c.lengthlist[i] > 0) { + int ptr = 0; + int j; + for (j = 0; j < c.lengthlist[i] - 1; j++) { + int bit = (codelist[i] >>> j) & 1; + if (bit == 0) { + if (ptr0[ptr] == 0) { + ptr0[ptr] = ++top; + } + ptr = ptr0[ptr]; + } else { + if (ptr1[ptr] == 0) { + ptr1[ptr] = ++top; + } + ptr = ptr1[ptr]; + } + } + + if (((codelist[i] >>> j) & 1) == 0) { + ptr0[ptr] = -i; + } else { + ptr1[ptr] = -i; + } + + } + } + + t.tabn = Util.ilog(entries) - 4; + + if (t.tabn < 5) + t.tabn = 5; + int n = 1 << t.tabn; + t.tab = new int[n]; + t.tabl = new int[n]; + for (int i = 0; i < n; i++) { + int p = 0; + int j = 0; + for (j = 0; j < t.tabn && (p > 0 || j == 0); j++) { + if ((i & (1 << j)) != 0) { + p = ptr1[p]; + } else { + p = ptr0[p]; + } + } + t.tab[i] = p; // -code + t.tabl[i] = j; // length + } + + return (t); + } + + class DecodeAux { + int[] tab; + int[] tabl; + int tabn; + + int[] ptr0; + int[] ptr1; + int aux; // number of tree entries + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Comment.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Comment.java new file mode 100644 index 0000000..77f95db --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Comment.java @@ -0,0 +1,240 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +// the comments are not part of vorbis_info so that vorbis_info can be +// static storage +public class Comment { + private static byte[] _vorbis = "vorbis".getBytes(); + private static byte[] _vendor = "Xiphophorus libVorbis I 20000508".getBytes(); + + private static final int OV_EIMPL = -130; + + // unlimited user comment fields. + public byte[][] user_comments; + public int[] comment_lengths; + public int comments; + public byte[] vendor; + + public void init() { + user_comments = null; + comments = 0; + vendor = null; + } + + public void add(String comment) { + add(comment.getBytes()); + } + + private void add(byte[] comment) { + byte[][] foo = new byte[comments + 2][]; + if (user_comments != null) { + System.arraycopy(user_comments, 0, foo, 0, comments); + } + user_comments = foo; + + int[] goo = new int[comments + 2]; + if (comment_lengths != null) { + System.arraycopy(comment_lengths, 0, goo, 0, comments); + } + comment_lengths = goo; + + byte[] bar = new byte[comment.length + 1]; + System.arraycopy(comment, 0, bar, 0, comment.length); + user_comments[comments] = bar; + comment_lengths[comments] = comment.length; + comments++; + user_comments[comments] = null; + } + + public void add_tag(String tag, String contents) { + if (contents == null) + contents = ""; + add(tag + "=" + contents); + } + + static boolean tagcompare(byte[] s1, byte[] s2, int n) { + int c = 0; + byte u1, u2; + while (c < n) { + u1 = s1[c]; + u2 = s2[c]; + if ('Z' >= u1 && u1 >= 'A') + u1 = (byte) (u1 - 'A' + 'a'); + if ('Z' >= u2 && u2 >= 'A') + u2 = (byte) (u2 - 'A' + 'a'); + if (u1 != u2) { + return false; + } + c++; + } + return true; + } + + public String query(String tag) { + return query(tag, 0); + } + + public String query(String tag, int count) { + int foo = query(tag.getBytes(), count); + if (foo == -1) + return null; + byte[] comment = user_comments[foo]; + for (int i = 0; i < comment_lengths[foo]; i++) { + if (comment[i] == '=') { + return new String(comment, i + 1, comment_lengths[foo] - (i + 1)); + } + } + return null; + } + + private int query(byte[] tag, int count) { + int i = 0; + int found = 0; + int fulltaglen = tag.length + 1; + byte[] fulltag = new byte[fulltaglen]; + System.arraycopy(tag, 0, fulltag, 0, tag.length); + fulltag[tag.length] = (byte) '='; + + for (i = 0; i < comments; i++) { + if (tagcompare(user_comments[i], fulltag, fulltaglen)) { + if (count == found) { + // We return a pointer to the data, not a copy + // return user_comments[i] + taglen + 1; + return i; + } else { + found++; + } + } + } + return -1; + } + + int unpack(Buffer opb) { + int vendorlen = opb.read(32); + if (vendorlen < 0) { + clear(); + return (-1); + } + vendor = new byte[vendorlen + 1]; + opb.read(vendor, vendorlen); + comments = opb.read(32); + if (comments < 0) { + clear(); + return (-1); + } + user_comments = new byte[comments + 1][]; + comment_lengths = new int[comments + 1]; + + for (int i = 0; i < comments; i++) { + int len = opb.read(32); + if (len < 0) { + clear(); + return (-1); + } + comment_lengths[i] = len; + user_comments[i] = new byte[len + 1]; + opb.read(user_comments[i], len); + } + if (opb.read(1) != 1) { + clear(); + return (-1); + + } + return (0); + } + + int pack(Buffer opb) { + // preamble + opb.write(0x03, 8); + opb.write(_vorbis); + + // vendor + opb.write(_vendor.length, 32); + opb.write(_vendor); + + // comments + opb.write(comments, 32); + if (comments != 0) { + for (int i = 0; i < comments; i++) { + if (user_comments[i] != null) { + opb.write(comment_lengths[i], 32); + opb.write(user_comments[i]); + } else { + opb.write(0, 32); + } + } + } + opb.write(1, 1); + return (0); + } + + public int header_out(Packet op) { + Buffer opb = new Buffer(); + opb.writeinit(); + + if (pack(opb) != 0) + return OV_EIMPL; + + op.packet_base = new byte[opb.bytes()]; + op.packet = 0; + op.bytes = opb.bytes(); + System.arraycopy(opb.buffer(), 0, op.packet_base, 0, op.bytes); + op.b_o_s = 0; + op.e_o_s = 0; + op.granulepos = 0; + return 0; + } + + void clear() { + for (int i = 0; i < comments; i++) + user_comments[i] = null; + user_comments = null; + vendor = null; + } + + public String getVendor() { + return new String(vendor, 0, vendor.length - 1); + } + + public String getComment(int i) { + if (comments <= i) + return null; + return new String(user_comments[i], 0, user_comments[i].length - 1); + } + + public String toString() { + String foo = "Vendor: " + new String(vendor, 0, vendor.length - 1); + for (int i = 0; i < comments; i++) { + foo = foo + "\nComment: " + new String(user_comments[i], 0, user_comments[i].length - 1); + } + foo = foo + "\n"; + return foo; + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Drft.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Drft.java new file mode 100644 index 0000000..de8aad4 --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Drft.java @@ -0,0 +1,1319 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class Drft { + int n; + float[] trigcache; + int[] splitcache; + + void backward(float[] data) { + if (n == 1) + return; + drftb1(n, data, trigcache, trigcache, n, splitcache); + } + + void init(int n) { + this.n = n; + trigcache = new float[3 * n]; + splitcache = new int[32]; + fdrffti(n, trigcache, splitcache); + } + + void clear() { + if (trigcache != null) + trigcache = null; + if (splitcache != null) + splitcache = null; + } + + static int[] ntryh = { 4, 2, 3, 5 }; + static float tpi = 6.28318530717958647692528676655900577f; + static float hsqt2 = .70710678118654752440084436210485f; + static float taui = .86602540378443864676372317075293618f; + static float taur = -.5f; + static float sqrt2 = 1.4142135623730950488016887242097f; + + static void drfti1(int n, float[] wa, int index, int[] ifac) { + float arg, argh, argld, fi; + int ntry = 0, i, j = -1; + int k1, l1, l2, ib; + int ld, ii, ip, is, nq, nr; + int ido, ipm, nfm1; + int nl = n; + int nf = 0; + + int state = 101; + + loop: while (true) { + switch (state) { + case 101: + j++; + if (j < 4) + ntry = ntryh[j]; + else + ntry += 2; + case 104: + nq = nl / ntry; + nr = nl - ntry * nq; + if (nr != 0) { + state = 101; + break; + } + nf++; + ifac[nf + 1] = ntry; + nl = nq; + if (ntry != 2) { + state = 107; + break; + } + if (nf == 1) { + state = 107; + break; + } + + for (i = 1; i < nf; i++) { + ib = nf - i + 1; + ifac[ib + 1] = ifac[ib]; + } + ifac[2] = 2; + case 107: + if (nl != 1) { + state = 104; + break; + } + ifac[0] = n; + ifac[1] = nf; + argh = tpi / n; + is = 0; + nfm1 = nf - 1; + l1 = 1; + + if (nfm1 == 0) + return; + + for (k1 = 0; k1 < nfm1; k1++) { + ip = ifac[k1 + 2]; + ld = 0; + l2 = l1 * ip; + ido = n / l2; + ipm = ip - 1; + + for (j = 0; j < ipm; j++) { + ld += l1; + i = is; + argld = (float) ld * argh; + fi = 0.f; + for (ii = 2; ii < ido; ii += 2) { + fi += 1.f; + arg = fi * argld; + wa[index + i++] = (float) Math.cos(arg); + wa[index + i++] = (float) Math.sin(arg); + } + is += ido; + } + l1 = l2; + } + break loop; + } + } + } + + static void fdrffti(int n, float[] wsave, int[] ifac) { + if (n == 1) + return; + drfti1(n, wsave, n, ifac); + } + + static void dradf2(int ido, int l1, float[] cc, float[] ch, float[] wa1, int index) { + int i, k; + float ti2, tr2; + int t0, t1, t2, t3, t4, t5, t6; + + t1 = 0; + t0 = (t2 = l1 * ido); + t3 = ido << 1; + for (k = 0; k < l1; k++) { + ch[t1 << 1] = cc[t1] + cc[t2]; + ch[(t1 << 1) + t3 - 1] = cc[t1] - cc[t2]; + t1 += ido; + t2 += ido; + } + + if (ido < 2) + return; + + if (ido != 2) { + t1 = 0; + t2 = t0; + for (k = 0; k < l1; k++) { + t3 = t2; + t4 = (t1 << 1) + (ido << 1); + t5 = t1; + t6 = t1 + t1; + for (i = 2; i < ido; i += 2) { + t3 += 2; + t4 -= 2; + t5 += 2; + t6 += 2; + tr2 = wa1[index + i - 2] * cc[t3 - 1] + wa1[index + i - 1] * cc[t3]; + ti2 = wa1[index + i - 2] * cc[t3] - wa1[index + i - 1] * cc[t3 - 1]; + ch[t6] = cc[t5] + ti2; + ch[t4] = ti2 - cc[t5]; + ch[t6 - 1] = cc[t5 - 1] + tr2; + ch[t4 - 1] = cc[t5 - 1] - tr2; + } + t1 += ido; + t2 += ido; + } + if (ido % 2 == 1) + return; + } + + t3 = (t2 = (t1 = ido) - 1); + t2 += t0; + for (k = 0; k < l1; k++) { + ch[t1] = -cc[t2]; + ch[t1 - 1] = cc[t3]; + t1 += ido << 1; + t2 += ido; + t3 += ido; + } + } + + static void dradf4(int ido, int l1, float[] cc, float[] ch, float[] wa1, int index1, float[] wa2, int index2, + float[] wa3, int index3) { + int i, k, t0, t1, t2, t3, t4, t5, t6; + float ci2, ci3, ci4, cr2, cr3, cr4, ti1, ti2, ti3, ti4, tr1, tr2, tr3, tr4; + t0 = l1 * ido; + + t1 = t0; + t4 = t1 << 1; + t2 = t1 + (t1 << 1); + t3 = 0; + + for (k = 0; k < l1; k++) { + tr1 = cc[t1] + cc[t2]; + tr2 = cc[t3] + cc[t4]; + + ch[t5 = t3 << 2] = tr1 + tr2; + ch[(ido << 2) + t5 - 1] = tr2 - tr1; + ch[(t5 += (ido << 1)) - 1] = cc[t3] - cc[t4]; + ch[t5] = cc[t2] - cc[t1]; + + t1 += ido; + t2 += ido; + t3 += ido; + t4 += ido; + } + if (ido < 2) + return; + + if (ido != 2) { + t1 = 0; + for (k = 0; k < l1; k++) { + t2 = t1; + t4 = t1 << 2; + t5 = (t6 = ido << 1) + t4; + for (i = 2; i < ido; i += 2) { + t3 = (t2 += 2); + t4 += 2; + t5 -= 2; + + t3 += t0; + cr2 = wa1[index1 + i - 2] * cc[t3 - 1] + wa1[index1 + i - 1] * cc[t3]; + ci2 = wa1[index1 + i - 2] * cc[t3] - wa1[index1 + i - 1] * cc[t3 - 1]; + t3 += t0; + cr3 = wa2[index2 + i - 2] * cc[t3 - 1] + wa2[index2 + i - 1] * cc[t3]; + ci3 = wa2[index2 + i - 2] * cc[t3] - wa2[index2 + i - 1] * cc[t3 - 1]; + t3 += t0; + cr4 = wa3[index3 + i - 2] * cc[t3 - 1] + wa3[index3 + i - 1] * cc[t3]; + ci4 = wa3[index3 + i - 2] * cc[t3] - wa3[index3 + i - 1] * cc[t3 - 1]; + + tr1 = cr2 + cr4; + tr4 = cr4 - cr2; + ti1 = ci2 + ci4; + ti4 = ci2 - ci4; + + ti2 = cc[t2] + ci3; + ti3 = cc[t2] - ci3; + tr2 = cc[t2 - 1] + cr3; + tr3 = cc[t2 - 1] - cr3; + + ch[t4 - 1] = tr1 + tr2; + ch[t4] = ti1 + ti2; + + ch[t5 - 1] = tr3 - ti4; + ch[t5] = tr4 - ti3; + + ch[t4 + t6 - 1] = ti4 + tr3; + ch[t4 + t6] = tr4 + ti3; + + ch[t5 + t6 - 1] = tr2 - tr1; + ch[t5 + t6] = ti1 - ti2; + } + t1 += ido; + } + if ((ido & 1) != 0) + return; + } + + t2 = (t1 = t0 + ido - 1) + (t0 << 1); + t3 = ido << 2; + t4 = ido; + t5 = ido << 1; + t6 = ido; + + for (k = 0; k < l1; k++) { + ti1 = -hsqt2 * (cc[t1] + cc[t2]); + tr1 = hsqt2 * (cc[t1] - cc[t2]); + + ch[t4 - 1] = tr1 + cc[t6 - 1]; + ch[t4 + t5 - 1] = cc[t6 - 1] - tr1; + + ch[t4] = ti1 - cc[t1 + t0]; + ch[t4 + t5] = ti1 + cc[t1 + t0]; + + t1 += ido; + t2 += ido; + t4 += t3; + t6 += ido; + } + } + + static void dradfg(int ido, int ip, int l1, int idl1, float[] cc, float[] c1, float[] c2, float[] ch, float[] ch2, + float[] wa, int index) { + int idij, ipph, i, j, k, l, ic, ik, is; + int t0, t1, t2 = 0, t3, t4, t5, t6, t7, t8, t9, t10; + float dc2, ai1, ai2, ar1, ar2, ds2; + int nbd; + float dcp = 0, arg, dsp = 0, ar1h, ar2h; + int idp2, ipp2; + + arg = tpi / (float) ip; + dcp = (float) Math.cos(arg); + dsp = (float) Math.sin(arg); + ipph = (ip + 1) >> 1; + ipp2 = ip; + idp2 = ido; + nbd = (ido - 1) >> 1; + t0 = l1 * ido; + t10 = ip * ido; + + int state = 100; + loop: while (true) { + switch (state) { + case 101: + if (ido == 1) { + state = 119; + break; + } + for (ik = 0; ik < idl1; ik++) + ch2[ik] = c2[ik]; + + t1 = 0; + for (j = 1; j < ip; j++) { + t1 += t0; + t2 = t1; + for (k = 0; k < l1; k++) { + ch[t2] = c1[t2]; + t2 += ido; + } + } + + is = -ido; + t1 = 0; + if (nbd > l1) { + for (j = 1; j < ip; j++) { + t1 += t0; + is += ido; + t2 = -ido + t1; + for (k = 0; k < l1; k++) { + idij = is - 1; + t2 += ido; + t3 = t2; + for (i = 2; i < ido; i += 2) { + idij += 2; + t3 += 2; + ch[t3 - 1] = wa[index + idij - 1] * c1[t3 - 1] + wa[index + idij] * c1[t3]; + ch[t3] = wa[index + idij - 1] * c1[t3] - wa[index + idij] * c1[t3 - 1]; + } + } + } + } else { + + for (j = 1; j < ip; j++) { + is += ido; + idij = is - 1; + t1 += t0; + t2 = t1; + for (i = 2; i < ido; i += 2) { + idij += 2; + t2 += 2; + t3 = t2; + for (k = 0; k < l1; k++) { + ch[t3 - 1] = wa[index + idij - 1] * c1[t3 - 1] + wa[index + idij] * c1[t3]; + ch[t3] = wa[index + idij - 1] * c1[t3] - wa[index + idij] * c1[t3 - 1]; + t3 += ido; + } + } + } + } + + t1 = 0; + t2 = ipp2 * t0; + if (nbd < l1) { + for (j = 1; j < ipph; j++) { + t1 += t0; + t2 -= t0; + t3 = t1; + t4 = t2; + for (i = 2; i < ido; i += 2) { + t3 += 2; + t4 += 2; + t5 = t3 - ido; + t6 = t4 - ido; + for (k = 0; k < l1; k++) { + t5 += ido; + t6 += ido; + c1[t5 - 1] = ch[t5 - 1] + ch[t6 - 1]; + c1[t6 - 1] = ch[t5] - ch[t6]; + c1[t5] = ch[t5] + ch[t6]; + c1[t6] = ch[t6 - 1] - ch[t5 - 1]; + } + } + } + } else { + for (j = 1; j < ipph; j++) { + t1 += t0; + t2 -= t0; + t3 = t1; + t4 = t2; + for (k = 0; k < l1; k++) { + t5 = t3; + t6 = t4; + for (i = 2; i < ido; i += 2) { + t5 += 2; + t6 += 2; + c1[t5 - 1] = ch[t5 - 1] + ch[t6 - 1]; + c1[t6 - 1] = ch[t5] - ch[t6]; + c1[t5] = ch[t5] + ch[t6]; + c1[t6] = ch[t6 - 1] - ch[t5 - 1]; + } + t3 += ido; + t4 += ido; + } + } + } + case 119: + for (ik = 0; ik < idl1; ik++) + c2[ik] = ch2[ik]; + + t1 = 0; + t2 = ipp2 * idl1; + for (j = 1; j < ipph; j++) { + t1 += t0; + t2 -= t0; + t3 = t1 - ido; + t4 = t2 - ido; + for (k = 0; k < l1; k++) { + t3 += ido; + t4 += ido; + c1[t3] = ch[t3] + ch[t4]; + c1[t4] = ch[t4] - ch[t3]; + } + } + + ar1 = 1.f; + ai1 = 0.f; + t1 = 0; + t2 = ipp2 * idl1; + t3 = (ip - 1) * idl1; + for (l = 1; l < ipph; l++) { + t1 += idl1; + t2 -= idl1; + ar1h = dcp * ar1 - dsp * ai1; + ai1 = dcp * ai1 + dsp * ar1; + ar1 = ar1h; + t4 = t1; + t5 = t2; + t6 = t3; + t7 = idl1; + + for (ik = 0; ik < idl1; ik++) { + ch2[t4++] = c2[ik] + ar1 * c2[t7++]; + ch2[t5++] = ai1 * c2[t6++]; + } + + dc2 = ar1; + ds2 = ai1; + ar2 = ar1; + ai2 = ai1; + + t4 = idl1; + t5 = (ipp2 - 1) * idl1; + for (j = 2; j < ipph; j++) { + t4 += idl1; + t5 -= idl1; + + ar2h = dc2 * ar2 - ds2 * ai2; + ai2 = dc2 * ai2 + ds2 * ar2; + ar2 = ar2h; + + t6 = t1; + t7 = t2; + t8 = t4; + t9 = t5; + for (ik = 0; ik < idl1; ik++) { + ch2[t6++] += ar2 * c2[t8++]; + ch2[t7++] += ai2 * c2[t9++]; + } + } + } + t1 = 0; + for (j = 1; j < ipph; j++) { + t1 += idl1; + t2 = t1; + for (ik = 0; ik < idl1; ik++) + ch2[ik] += c2[t2++]; + } + + if (ido < l1) { + state = 132; + break; + } + + t1 = 0; + t2 = 0; + for (k = 0; k < l1; k++) { + t3 = t1; + t4 = t2; + for (i = 0; i < ido; i++) + cc[t4++] = ch[t3++]; + t1 += ido; + t2 += t10; + } + state = 135; + break; + + case 132: + for (i = 0; i < ido; i++) { + t1 = i; + t2 = i; + for (k = 0; k < l1; k++) { + cc[t2] = ch[t1]; + t1 += ido; + t2 += t10; + } + } + case 135: + t1 = 0; + t2 = ido << 1; + t3 = 0; + t4 = ipp2 * t0; + for (j = 1; j < ipph; j++) { + t1 += t2; + t3 += t0; + t4 -= t0; + + t5 = t1; + t6 = t3; + t7 = t4; + + for (k = 0; k < l1; k++) { + cc[t5 - 1] = ch[t6]; + cc[t5] = ch[t7]; + t5 += t10; + t6 += ido; + t7 += ido; + } + } + + if (ido == 1) + return; + if (nbd < l1) { + state = 141; + break; + } + + t1 = -ido; + t3 = 0; + t4 = 0; + t5 = ipp2 * t0; + for (j = 1; j < ipph; j++) { + t1 += t2; + t3 += t2; + t4 += t0; + t5 -= t0; + t6 = t1; + t7 = t3; + t8 = t4; + t9 = t5; + for (k = 0; k < l1; k++) { + for (i = 2; i < ido; i += 2) { + ic = idp2 - i; + cc[i + t7 - 1] = ch[i + t8 - 1] + ch[i + t9 - 1]; + cc[ic + t6 - 1] = ch[i + t8 - 1] - ch[i + t9 - 1]; + cc[i + t7] = ch[i + t8] + ch[i + t9]; + cc[ic + t6] = ch[i + t9] - ch[i + t8]; + } + t6 += t10; + t7 += t10; + t8 += ido; + t9 += ido; + } + } + return; + case 141: + t1 = -ido; + t3 = 0; + t4 = 0; + t5 = ipp2 * t0; + for (j = 1; j < ipph; j++) { + t1 += t2; + t3 += t2; + t4 += t0; + t5 -= t0; + for (i = 2; i < ido; i += 2) { + t6 = idp2 + t1 - i; + t7 = i + t3; + t8 = i + t4; + t9 = i + t5; + for (k = 0; k < l1; k++) { + cc[t7 - 1] = ch[t8 - 1] + ch[t9 - 1]; + cc[t6 - 1] = ch[t8 - 1] - ch[t9 - 1]; + cc[t7] = ch[t8] + ch[t9]; + cc[t6] = ch[t9] - ch[t8]; + t6 += t10; + t7 += t10; + t8 += ido; + t9 += ido; + } + } + } + break loop; + } + } + } + + static void drftf1(int n, float[] c, float[] ch, float[] wa, int[] ifac) { + int i, k1, l1, l2; + int na, kh, nf; + int ip, iw, ido, idl1, ix2, ix3; + + nf = ifac[1]; + na = 1; + l2 = n; + iw = n; + + for (k1 = 0; k1 < nf; k1++) { + kh = nf - k1; + ip = ifac[kh + 1]; + l1 = l2 / ip; + ido = n / l2; + idl1 = ido * l1; + iw -= (ip - 1) * ido; + na = 1 - na; + + int state = 100; + loop: while (true) { + switch (state) { + case 100: + if (ip != 4) { + state = 102; + break; + } + + ix2 = iw + ido; + ix3 = ix2 + ido; + if (na != 0) + dradf4(ido, l1, ch, c, wa, iw - 1, wa, ix2 - 1, wa, ix3 - 1); + else + dradf4(ido, l1, c, ch, wa, iw - 1, wa, ix2 - 1, wa, ix3 - 1); + state = 110; + break; + case 102: + if (ip != 2) { + state = 104; + break; + } + if (na != 0) { + state = 103; + break; + } + dradf2(ido, l1, c, ch, wa, iw - 1); + state = 110; + break; + case 103: + dradf2(ido, l1, ch, c, wa, iw - 1); + case 104: + if (ido == 1) + na = 1 - na; + if (na != 0) { + state = 109; + break; + } + dradfg(ido, ip, l1, idl1, c, c, c, ch, ch, wa, iw - 1); + na = 1; + state = 110; + break; + case 109: + dradfg(ido, ip, l1, idl1, ch, ch, ch, c, c, wa, iw - 1); + na = 0; + case 110: + l2 = l1; + break loop; + } + } + } + if (na == 1) + return; + for (i = 0; i < n; i++) + c[i] = ch[i]; + } + + static void dradb2(int ido, int l1, float[] cc, float[] ch, float[] wa1, int index) { + int i, k, t0, t1, t2, t3, t4, t5, t6; + float ti2, tr2; + + t0 = l1 * ido; + + t1 = 0; + t2 = 0; + t3 = (ido << 1) - 1; + for (k = 0; k < l1; k++) { + ch[t1] = cc[t2] + cc[t3 + t2]; + ch[t1 + t0] = cc[t2] - cc[t3 + t2]; + t2 = (t1 += ido) << 1; + } + + if (ido < 2) + return; + if (ido != 2) { + t1 = 0; + t2 = 0; + for (k = 0; k < l1; k++) { + t3 = t1; + t5 = (t4 = t2) + (ido << 1); + t6 = t0 + t1; + for (i = 2; i < ido; i += 2) { + t3 += 2; + t4 += 2; + t5 -= 2; + t6 += 2; + ch[t3 - 1] = cc[t4 - 1] + cc[t5 - 1]; + tr2 = cc[t4 - 1] - cc[t5 - 1]; + ch[t3] = cc[t4] - cc[t5]; + ti2 = cc[t4] + cc[t5]; + ch[t6 - 1] = wa1[index + i - 2] * tr2 - wa1[index + i - 1] * ti2; + ch[t6] = wa1[index + i - 2] * ti2 + wa1[index + i - 1] * tr2; + } + t2 = (t1 += ido) << 1; + } + if ((ido % 2) == 1) + return; + } + + t1 = ido - 1; + t2 = ido - 1; + for (k = 0; k < l1; k++) { + ch[t1] = cc[t2] + cc[t2]; + ch[t1 + t0] = -(cc[t2 + 1] + cc[t2 + 1]); + t1 += ido; + t2 += ido << 1; + } + } + + static void dradb3(int ido, int l1, float[] cc, float[] ch, float[] wa1, int index1, float[] wa2, int index2) { + int i, k, t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10; + float ci2, ci3, di2, di3, cr2, cr3, dr2, dr3, ti2, tr2; + t0 = l1 * ido; + + t1 = 0; + t2 = t0 << 1; + t3 = ido << 1; + t4 = ido + (ido << 1); + t5 = 0; + for (k = 0; k < l1; k++) { + tr2 = cc[t3 - 1] + cc[t3 - 1]; + cr2 = cc[t5] + (taur * tr2); + ch[t1] = cc[t5] + tr2; + ci3 = taui * (cc[t3] + cc[t3]); + ch[t1 + t0] = cr2 - ci3; + ch[t1 + t2] = cr2 + ci3; + t1 += ido; + t3 += t4; + t5 += t4; + } + + if (ido == 1) + return; + + t1 = 0; + t3 = ido << 1; + for (k = 0; k < l1; k++) { + t7 = t1 + (t1 << 1); + t6 = (t5 = t7 + t3); + t8 = t1; + t10 = (t9 = t1 + t0) + t0; + + for (i = 2; i < ido; i += 2) { + t5 += 2; + t6 -= 2; + t7 += 2; + t8 += 2; + t9 += 2; + t10 += 2; + tr2 = cc[t5 - 1] + cc[t6 - 1]; + cr2 = cc[t7 - 1] + (taur * tr2); + ch[t8 - 1] = cc[t7 - 1] + tr2; + ti2 = cc[t5] - cc[t6]; + ci2 = cc[t7] + (taur * ti2); + ch[t8] = cc[t7] + ti2; + cr3 = taui * (cc[t5 - 1] - cc[t6 - 1]); + ci3 = taui * (cc[t5] + cc[t6]); + dr2 = cr2 - ci3; + dr3 = cr2 + ci3; + di2 = ci2 + cr3; + di3 = ci2 - cr3; + ch[t9 - 1] = wa1[index1 + i - 2] * dr2 - wa1[index1 + i - 1] * di2; + ch[t9] = wa1[index1 + i - 2] * di2 + wa1[index1 + i - 1] * dr2; + ch[t10 - 1] = wa2[index2 + i - 2] * dr3 - wa2[index2 + i - 1] * di3; + ch[t10] = wa2[index2 + i - 2] * di3 + wa2[index2 + i - 1] * dr3; + } + t1 += ido; + } + } + + static void dradb4(int ido, int l1, float[] cc, float[] ch, float[] wa1, int index1, float[] wa2, int index2, + float[] wa3, int index3) { + int i, k, t0, t1, t2, t3, t4, t5, t6, t7, t8; + float ci2, ci3, ci4, cr2, cr3, cr4, ti1, ti2, ti3, ti4, tr1, tr2, tr3, tr4; + t0 = l1 * ido; + + t1 = 0; + t2 = ido << 2; + t3 = 0; + t6 = ido << 1; + for (k = 0; k < l1; k++) { + t4 = t3 + t6; + t5 = t1; + tr3 = cc[t4 - 1] + cc[t4 - 1]; + tr4 = cc[t4] + cc[t4]; + tr1 = cc[t3] - cc[(t4 += t6) - 1]; + tr2 = cc[t3] + cc[t4 - 1]; + ch[t5] = tr2 + tr3; + ch[t5 += t0] = tr1 - tr4; + ch[t5 += t0] = tr2 - tr3; + ch[t5 += t0] = tr1 + tr4; + t1 += ido; + t3 += t2; + } + + if (ido < 2) + return; + if (ido != 2) { + t1 = 0; + for (k = 0; k < l1; k++) { + t5 = (t4 = (t3 = (t2 = t1 << 2) + t6)) + t6; + t7 = t1; + for (i = 2; i < ido; i += 2) { + t2 += 2; + t3 += 2; + t4 -= 2; + t5 -= 2; + t7 += 2; + ti1 = cc[t2] + cc[t5]; + ti2 = cc[t2] - cc[t5]; + ti3 = cc[t3] - cc[t4]; + tr4 = cc[t3] + cc[t4]; + tr1 = cc[t2 - 1] - cc[t5 - 1]; + tr2 = cc[t2 - 1] + cc[t5 - 1]; + ti4 = cc[t3 - 1] - cc[t4 - 1]; + tr3 = cc[t3 - 1] + cc[t4 - 1]; + ch[t7 - 1] = tr2 + tr3; + cr3 = tr2 - tr3; + ch[t7] = ti2 + ti3; + ci3 = ti2 - ti3; + cr2 = tr1 - tr4; + cr4 = tr1 + tr4; + ci2 = ti1 + ti4; + ci4 = ti1 - ti4; + + ch[(t8 = t7 + t0) - 1] = wa1[index1 + i - 2] * cr2 - wa1[index1 + i - 1] * ci2; + ch[t8] = wa1[index1 + i - 2] * ci2 + wa1[index1 + i - 1] * cr2; + ch[(t8 += t0) - 1] = wa2[index2 + i - 2] * cr3 - wa2[index2 + i - 1] * ci3; + ch[t8] = wa2[index2 + i - 2] * ci3 + wa2[index2 + i - 1] * cr3; + ch[(t8 += t0) - 1] = wa3[index3 + i - 2] * cr4 - wa3[index3 + i - 1] * ci4; + ch[t8] = wa3[index3 + i - 2] * ci4 + wa3[index3 + i - 1] * cr4; + } + t1 += ido; + } + if (ido % 2 == 1) + return; + } + + t1 = ido; + t2 = ido << 2; + t3 = ido - 1; + t4 = ido + (ido << 1); + for (k = 0; k < l1; k++) { + t5 = t3; + ti1 = cc[t1] + cc[t4]; + ti2 = cc[t4] - cc[t1]; + tr1 = cc[t1 - 1] - cc[t4 - 1]; + tr2 = cc[t1 - 1] + cc[t4 - 1]; + ch[t5] = tr2 + tr2; + ch[t5 += t0] = sqrt2 * (tr1 - ti1); + ch[t5 += t0] = ti2 + ti2; + ch[t5 += t0] = -sqrt2 * (tr1 + ti1); + + t3 += ido; + t1 += t2; + t4 += t2; + } + } + + static void dradbg(int ido, int ip, int l1, int idl1, float[] cc, float[] c1, float[] c2, float[] ch, float[] ch2, + float[] wa, int index) { + + int idij, ipph = 0, i, j, k, l, ik, is, t0 = 0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10 = 0, t11, t12; + float dc2, ai1, ai2, ar1, ar2, ds2; + int nbd = 0; + float dcp = 0, arg, dsp = 0, ar1h, ar2h; + int ipp2 = 0; + + int state = 100; + + loop: while (true) { + switch (state) { + case 100: + t10 = ip * ido; + t0 = l1 * ido; + arg = tpi / (float) ip; + dcp = (float) Math.cos(arg); + dsp = (float) Math.sin(arg); + nbd = (ido - 1) >>> 1; + ipp2 = ip; + ipph = (ip + 1) >>> 1; + if (ido < l1) { + state = 103; + break; + } + t1 = 0; + t2 = 0; + for (k = 0; k < l1; k++) { + t3 = t1; + t4 = t2; + for (i = 0; i < ido; i++) { + ch[t3] = cc[t4]; + t3++; + t4++; + } + t1 += ido; + t2 += t10; + } + state = 106; + break; + case 103: + t1 = 0; + for (i = 0; i < ido; i++) { + t2 = t1; + t3 = t1; + for (k = 0; k < l1; k++) { + ch[t2] = cc[t3]; + t2 += ido; + t3 += t10; + } + t1++; + } + case 106: + t1 = 0; + t2 = ipp2 * t0; + t7 = (t5 = ido << 1); + for (j = 1; j < ipph; j++) { + t1 += t0; + t2 -= t0; + t3 = t1; + t4 = t2; + t6 = t5; + for (k = 0; k < l1; k++) { + ch[t3] = cc[t6 - 1] + cc[t6 - 1]; + ch[t4] = cc[t6] + cc[t6]; + t3 += ido; + t4 += ido; + t6 += t10; + } + t5 += t7; + } + if (ido == 1) { + state = 116; + break; + } + if (nbd < l1) { + state = 112; + break; + } + + t1 = 0; + t2 = ipp2 * t0; + t7 = 0; + for (j = 1; j < ipph; j++) { + t1 += t0; + t2 -= t0; + t3 = t1; + t4 = t2; + + t7 += (ido << 1); + t8 = t7; + for (k = 0; k < l1; k++) { + t5 = t3; + t6 = t4; + t9 = t8; + t11 = t8; + for (i = 2; i < ido; i += 2) { + t5 += 2; + t6 += 2; + t9 += 2; + t11 -= 2; + ch[t5 - 1] = cc[t9 - 1] + cc[t11 - 1]; + ch[t6 - 1] = cc[t9 - 1] - cc[t11 - 1]; + ch[t5] = cc[t9] - cc[t11]; + ch[t6] = cc[t9] + cc[t11]; + } + t3 += ido; + t4 += ido; + t8 += t10; + } + } + state = 116; + break; + case 112: + t1 = 0; + t2 = ipp2 * t0; + t7 = 0; + for (j = 1; j < ipph; j++) { + t1 += t0; + t2 -= t0; + t3 = t1; + t4 = t2; + t7 += (ido << 1); + t8 = t7; + t9 = t7; + for (i = 2; i < ido; i += 2) { + t3 += 2; + t4 += 2; + t8 += 2; + t9 -= 2; + t5 = t3; + t6 = t4; + t11 = t8; + t12 = t9; + for (k = 0; k < l1; k++) { + ch[t5 - 1] = cc[t11 - 1] + cc[t12 - 1]; + ch[t6 - 1] = cc[t11 - 1] - cc[t12 - 1]; + ch[t5] = cc[t11] - cc[t12]; + ch[t6] = cc[t11] + cc[t12]; + t5 += ido; + t6 += ido; + t11 += t10; + t12 += t10; + } + } + } + case 116: + ar1 = 1.f; + ai1 = 0.f; + t1 = 0; + t9 = (t2 = ipp2 * idl1); + t3 = (ip - 1) * idl1; + for (l = 1; l < ipph; l++) { + t1 += idl1; + t2 -= idl1; + + ar1h = dcp * ar1 - dsp * ai1; + ai1 = dcp * ai1 + dsp * ar1; + ar1 = ar1h; + t4 = t1; + t5 = t2; + t6 = 0; + t7 = idl1; + t8 = t3; + for (ik = 0; ik < idl1; ik++) { + c2[t4++] = ch2[t6++] + ar1 * ch2[t7++]; + c2[t5++] = ai1 * ch2[t8++]; + } + dc2 = ar1; + ds2 = ai1; + ar2 = ar1; + ai2 = ai1; + + t6 = idl1; + t7 = t9 - idl1; + for (j = 2; j < ipph; j++) { + t6 += idl1; + t7 -= idl1; + ar2h = dc2 * ar2 - ds2 * ai2; + ai2 = dc2 * ai2 + ds2 * ar2; + ar2 = ar2h; + t4 = t1; + t5 = t2; + t11 = t6; + t12 = t7; + for (ik = 0; ik < idl1; ik++) { + c2[t4++] += ar2 * ch2[t11++]; + c2[t5++] += ai2 * ch2[t12++]; + } + } + } + + t1 = 0; + for (j = 1; j < ipph; j++) { + t1 += idl1; + t2 = t1; + for (ik = 0; ik < idl1; ik++) + ch2[ik] += ch2[t2++]; + } + + t1 = 0; + t2 = ipp2 * t0; + for (j = 1; j < ipph; j++) { + t1 += t0; + t2 -= t0; + t3 = t1; + t4 = t2; + for (k = 0; k < l1; k++) { + ch[t3] = c1[t3] - c1[t4]; + ch[t4] = c1[t3] + c1[t4]; + t3 += ido; + t4 += ido; + } + } + + if (ido == 1) { + state = 132; + break; + } + if (nbd < l1) { + state = 128; + break; + } + + t1 = 0; + t2 = ipp2 * t0; + for (j = 1; j < ipph; j++) { + t1 += t0; + t2 -= t0; + t3 = t1; + t4 = t2; + for (k = 0; k < l1; k++) { + t5 = t3; + t6 = t4; + for (i = 2; i < ido; i += 2) { + t5 += 2; + t6 += 2; + ch[t5 - 1] = c1[t5 - 1] - c1[t6]; + ch[t6 - 1] = c1[t5 - 1] + c1[t6]; + ch[t5] = c1[t5] + c1[t6 - 1]; + ch[t6] = c1[t5] - c1[t6 - 1]; + } + t3 += ido; + t4 += ido; + } + } + state = 132; + break; + case 128: + t1 = 0; + t2 = ipp2 * t0; + for (j = 1; j < ipph; j++) { + t1 += t0; + t2 -= t0; + t3 = t1; + t4 = t2; + for (i = 2; i < ido; i += 2) { + t3 += 2; + t4 += 2; + t5 = t3; + t6 = t4; + for (k = 0; k < l1; k++) { + ch[t5 - 1] = c1[t5 - 1] - c1[t6]; + ch[t6 - 1] = c1[t5 - 1] + c1[t6]; + ch[t5] = c1[t5] + c1[t6 - 1]; + ch[t6] = c1[t5] - c1[t6 - 1]; + t5 += ido; + t6 += ido; + } + } + } + case 132: + if (ido == 1) + return; + + for (ik = 0; ik < idl1; ik++) + c2[ik] = ch2[ik]; + + t1 = 0; + for (j = 1; j < ip; j++) { + t2 = (t1 += t0); + for (k = 0; k < l1; k++) { + c1[t2] = ch[t2]; + t2 += ido; + } + } + + if (nbd > l1) { + state = 139; + break; + } + + is = -ido - 1; + t1 = 0; + for (j = 1; j < ip; j++) { + is += ido; + t1 += t0; + idij = is; + t2 = t1; + for (i = 2; i < ido; i += 2) { + t2 += 2; + idij += 2; + t3 = t2; + for (k = 0; k < l1; k++) { + c1[t3 - 1] = wa[index + idij - 1] * ch[t3 - 1] - wa[index + idij] * ch[t3]; + c1[t3] = wa[index + idij - 1] * ch[t3] + wa[index + idij] * ch[t3 - 1]; + t3 += ido; + } + } + } + return; + + case 139: + is = -ido - 1; + t1 = 0; + for (j = 1; j < ip; j++) { + is += ido; + t1 += t0; + t2 = t1; + for (k = 0; k < l1; k++) { + idij = is; + t3 = t2; + for (i = 2; i < ido; i += 2) { + idij += 2; + t3 += 2; + c1[t3 - 1] = wa[index + idij - 1] * ch[t3 - 1] - wa[index + idij] * ch[t3]; + c1[t3] = wa[index + idij - 1] * ch[t3] + wa[index + idij] * ch[t3 - 1]; + } + t2 += ido; + } + } + break loop; + } + } + } + + static void drftb1(int n, float[] c, float[] ch, float[] wa, int index, int[] ifac) { + int i, k1, l1, l2 = 0; + int na; + int nf, ip = 0, iw, ix2, ix3, ido = 0, idl1 = 0; + + nf = ifac[1]; + na = 0; + l1 = 1; + iw = 1; + + for (k1 = 0; k1 < nf; k1++) { + int state = 100; + loop: while (true) { + switch (state) { + case 100: + ip = ifac[k1 + 2]; + l2 = ip * l1; + ido = n / l2; + idl1 = ido * l1; + if (ip != 4) { + state = 103; + break; + } + ix2 = iw + ido; + ix3 = ix2 + ido; + + if (na != 0) + dradb4(ido, l1, ch, c, wa, index + iw - 1, wa, index + ix2 - 1, wa, index + ix3 - 1); + else + dradb4(ido, l1, c, ch, wa, index + iw - 1, wa, index + ix2 - 1, wa, index + ix3 - 1); + na = 1 - na; + state = 115; + break; + case 103: + if (ip != 2) { + state = 106; + break; + } + + if (na != 0) + dradb2(ido, l1, ch, c, wa, index + iw - 1); + else + dradb2(ido, l1, c, ch, wa, index + iw - 1); + na = 1 - na; + state = 115; + break; + + case 106: + if (ip != 3) { + state = 109; + break; + } + + ix2 = iw + ido; + if (na != 0) + dradb3(ido, l1, ch, c, wa, index + iw - 1, wa, index + ix2 - 1); + else + dradb3(ido, l1, c, ch, wa, index + iw - 1, wa, index + ix2 - 1); + na = 1 - na; + state = 115; + break; + case 109: + if (na != 0) + dradbg(ido, ip, l1, idl1, ch, ch, ch, c, c, wa, index + iw - 1); + else + dradbg(ido, ip, l1, idl1, c, c, c, ch, ch, wa, index + iw - 1); + if (ido == 1) + na = 1 - na; + + case 115: + l1 = l2; + iw += (ip - 1) * ido; + break loop; + } + } + } + if (na == 0) + return; + for (i = 0; i < n; i++) + c[i] = ch[i]; + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/DspState.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/DspState.java new file mode 100644 index 0000000..997397f --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/DspState.java @@ -0,0 +1,369 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +public class DspState { + static final float M_PI = 3.1415926539f; + static final int VI_TRANSFORMB = 1; + static final int VI_WINDOWB = 1; + + int analysisp; + Info vi; + int modebits; + + float[][] pcm; + int pcm_storage; + int pcm_current; + int pcm_returned; + + float[] multipliers; + int envelope_storage; + int envelope_current; + + int eofflag; + + int lW; + int W; + int nW; + int centerW; + + long granulepos; + long sequence; + + long glue_bits; + long time_bits; + long floor_bits; + long res_bits; + + // local lookup storage + float[][][][][] window; // block, leadin, leadout, type + Object[][] transform; + CodeBook[] fullbooks; + // backend lookups are tied to the mode, not the backend or naked mapping + Object[] mode; + + // local storage, only used on the encoding side. This way the + // application does not need to worry about freeing some packets' + // memory and not others'; packet storage is always tracked. + // Cleared next call to a _dsp_ function + byte[] header; + byte[] header1; + byte[] header2; + + public DspState() { + transform = new Object[2][]; + window = new float[2][][][][]; + window[0] = new float[2][][][]; + window[0][0] = new float[2][][]; + window[0][1] = new float[2][][]; + window[0][0][0] = new float[2][]; + window[0][0][1] = new float[2][]; + window[0][1][0] = new float[2][]; + window[0][1][1] = new float[2][]; + window[1] = new float[2][][][]; + window[1][0] = new float[2][][]; + window[1][1] = new float[2][][]; + window[1][0][0] = new float[2][]; + window[1][0][1] = new float[2][]; + window[1][1][0] = new float[2][]; + window[1][1][1] = new float[2][]; + } + + static float[] window(int type, int window, int left, int right) { + float[] ret = new float[window]; + switch (type) { + case 0: + // The 'vorbis window' (window 0) is sin(sin(x)*sin(x)*2pi) + { + int leftbegin = window / 4 - left / 2; + int rightbegin = window - window / 4 - right / 2; + + for (int i = 0; i < left; i++) { + float x = (float) ((i + .5) / left * M_PI / 2.); + x = (float) Math.sin(x); + x *= x; + x *= M_PI / 2.; + x = (float) Math.sin(x); + ret[i + leftbegin] = x; + } + + for (int i = leftbegin + left; i < rightbegin; i++) { + ret[i] = 1.f; + } + + for (int i = 0; i < right; i++) { + float x = (float) ((right - i - .5) / right * M_PI / 2.); + x = (float) Math.sin(x); + x *= x; + x *= M_PI / 2.; + x = (float) Math.sin(x); + ret[i + rightbegin] = x; + } + } + break; + default: + // free(ret); + return (null); + } + return (ret); + } + + // Analysis side code, but directly related to blocking. Thus it's + // here and not in analysis.c (which is for analysis transforms only). + // The init is here because some of it is shared + + int init(Info vi, boolean encp) { + this.vi = vi; + modebits = Util.ilog2(vi.modes); + + transform[0] = new Object[VI_TRANSFORMB]; + transform[1] = new Object[VI_TRANSFORMB]; + + // MDCT is tranform 0 + + transform[0][0] = new Mdct(); + transform[1][0] = new Mdct(); + ((Mdct) transform[0][0]).init(vi.blocksizes[0]); + ((Mdct) transform[1][0]).init(vi.blocksizes[1]); + + window[0][0][0] = new float[VI_WINDOWB][]; + window[0][0][1] = window[0][0][0]; + window[0][1][0] = window[0][0][0]; + window[0][1][1] = window[0][0][0]; + window[1][0][0] = new float[VI_WINDOWB][]; + window[1][0][1] = new float[VI_WINDOWB][]; + window[1][1][0] = new float[VI_WINDOWB][]; + window[1][1][1] = new float[VI_WINDOWB][]; + + for (int i = 0; i < VI_WINDOWB; i++) { + window[0][0][0][i] = window(i, vi.blocksizes[0], vi.blocksizes[0] / 2, vi.blocksizes[0] / 2); + window[1][0][0][i] = window(i, vi.blocksizes[1], vi.blocksizes[0] / 2, vi.blocksizes[0] / 2); + window[1][0][1][i] = window(i, vi.blocksizes[1], vi.blocksizes[0] / 2, vi.blocksizes[1] / 2); + window[1][1][0][i] = window(i, vi.blocksizes[1], vi.blocksizes[1] / 2, vi.blocksizes[0] / 2); + window[1][1][1][i] = window(i, vi.blocksizes[1], vi.blocksizes[1] / 2, vi.blocksizes[1] / 2); + } + + fullbooks = new CodeBook[vi.books]; + for (int i = 0; i < vi.books; i++) { + fullbooks[i] = new CodeBook(); + fullbooks[i].init_decode(vi.book_param[i]); + } + + // initialize the storage vectors to a decent size greater than the + // minimum + + pcm_storage = 8192; // we'll assume later that we have + // a minimum of twice the blocksize of + // accumulated samples in analysis + pcm = new float[vi.channels][]; + { + for (int i = 0; i < vi.channels; i++) { + pcm[i] = new float[pcm_storage]; + } + } + + // all 1 (large block) or 0 (small block) + // explicitly set for the sake of clarity + lW = 0; // previous window size + W = 0; // current window size + + // all vector indexes; multiples of samples_per_envelope_step + centerW = vi.blocksizes[1] / 2; + + pcm_current = centerW; + + // initialize all the mapping/backend lookups + mode = new Object[vi.modes]; + for (int i = 0; i < vi.modes; i++) { + int mapnum = vi.mode_param[i].mapping; + int maptype = vi.map_type[mapnum]; + mode[i] = FuncMapping.mapping_P[maptype].look(this, vi.mode_param[i], vi.map_param[mapnum]); + } + return (0); + } + + public int synthesis_init(Info vi) { + init(vi, false); + // Adjust centerW to allow an easier mechanism for determining output + pcm_returned = centerW; + centerW -= vi.blocksizes[W] / 4 + vi.blocksizes[lW] / 4; + granulepos = -1; + sequence = -1; + return (0); + } + + DspState(Info vi) { + this(); + init(vi, false); + // Adjust centerW to allow an easier mechanism for determining output + pcm_returned = centerW; + centerW -= vi.blocksizes[W] / 4 + vi.blocksizes[lW] / 4; + granulepos = -1; + sequence = -1; + } + + // Unike in analysis, the window is only partially applied for each + // block. The time domain envelope is not yet handled at the point of + // calling (as it relies on the previous block). + + public int synthesis_blockin(Block vb) { + // Shift out any PCM/multipliers that we returned previously + // centerW is currently the center of the last block added + if (centerW > vi.blocksizes[1] / 2 && pcm_returned > 8192) { + // don't shift too much; we need to have a minimum PCM buffer of + // 1/2 long block + + int shiftPCM = centerW - vi.blocksizes[1] / 2; + shiftPCM = (pcm_returned < shiftPCM ? pcm_returned : shiftPCM); + + pcm_current -= shiftPCM; + centerW -= shiftPCM; + pcm_returned -= shiftPCM; + if (shiftPCM != 0) { + for (int i = 0; i < vi.channels; i++) { + System.arraycopy(pcm[i], shiftPCM, pcm[i], 0, pcm_current); + } + } + } + + lW = W; + W = vb.W; + nW = -1; + + glue_bits += vb.glue_bits; + time_bits += vb.time_bits; + floor_bits += vb.floor_bits; + res_bits += vb.res_bits; + + if (sequence + 1 != vb.sequence) + granulepos = -1; // out of sequence; lose count + + sequence = vb.sequence; + + { + int sizeW = vi.blocksizes[W]; + int _centerW = centerW + vi.blocksizes[lW] / 4 + sizeW / 4; + int beginW = _centerW - sizeW / 2; + int endW = beginW + sizeW; + int beginSl = 0; + int endSl = 0; + + // Do we have enough PCM/mult storage for the block? + if (endW > pcm_storage) { + // expand the storage + pcm_storage = endW + vi.blocksizes[1]; + for (int i = 0; i < vi.channels; i++) { + float[] foo = new float[pcm_storage]; + System.arraycopy(pcm[i], 0, foo, 0, pcm[i].length); + pcm[i] = foo; + } + } + + // overlap/add PCM + switch (W) { + case 0: + beginSl = 0; + endSl = vi.blocksizes[0] / 2; + break; + case 1: + beginSl = vi.blocksizes[1] / 4 - vi.blocksizes[lW] / 4; + endSl = beginSl + vi.blocksizes[lW] / 2; + break; + } + + for (int j = 0; j < vi.channels; j++) { + int _pcm = beginW; + // the overlap/add section + int i = 0; + for (i = beginSl; i < endSl; i++) { + pcm[j][_pcm + i] += vb.pcm[j][i]; + } + // the remaining section + for (; i < sizeW; i++) { + pcm[j][_pcm + i] = vb.pcm[j][i]; + } + } + + // track the frame number... This is for convenience, but also + // making sure our last packet doesn't end with added padding. If + // the last packet is partial, the number of samples we'll have to + // return will be past the vb->granulepos. + // + // This is not foolproof! It will be confused if we begin + // decoding at the last page after a seek or hole. In that case, + // we don't have a starting point to judge where the last frame + // is. For this reason, vorbisfile will always try to make sure + // it reads the last two marked pages in proper sequence + + if (granulepos == -1) { + granulepos = vb.granulepos; + } else { + granulepos += (_centerW - centerW); + if (vb.granulepos != -1 && granulepos != vb.granulepos) { + if (granulepos > vb.granulepos && vb.eofflag != 0) { + // partial last frame. Strip the padding off + _centerW -= (granulepos - vb.granulepos); + } // else{ Shouldn't happen *unless* the bitstream is out of + // spec. Either way, believe the bitstream } + granulepos = vb.granulepos; + } + } + + // Update, cleanup + + centerW = _centerW; + pcm_current = endW; + if (vb.eofflag != 0) + eofflag = 1; + } + return (0); + } + + // pcm==NULL indicates we just want the pending samples, no more + public int synthesis_pcmout(float[][][] _pcm, int[] index) { + if (pcm_returned < centerW) { + if (_pcm != null) { + for (int i = 0; i < vi.channels; i++) { + index[i] = pcm_returned; + } + _pcm[0] = pcm; + } + return (centerW - pcm_returned); + } + return (0); + } + + public int synthesis_read(int bytes) { + if (bytes != 0 && pcm_returned + bytes > centerW) + return (-1); + pcm_returned += bytes; + return (0); + } + + public void clear() { + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Floor0.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Floor0.java new file mode 100644 index 0000000..b2d49c6 --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Floor0.java @@ -0,0 +1,332 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class Floor0 extends FuncFloor { + + void pack(Object i, Buffer opb) { + InfoFloor0 info = (InfoFloor0) i; + opb.write(info.order, 8); + opb.write(info.rate, 16); + opb.write(info.barkmap, 16); + opb.write(info.ampbits, 6); + opb.write(info.ampdB, 8); + opb.write(info.numbooks - 1, 4); + for (int j = 0; j < info.numbooks; j++) + opb.write(info.books[j], 8); + } + + Object unpack(Info vi, Buffer opb) { + InfoFloor0 info = new InfoFloor0(); + info.order = opb.read(8); + info.rate = opb.read(16); + info.barkmap = opb.read(16); + info.ampbits = opb.read(6); + info.ampdB = opb.read(8); + info.numbooks = opb.read(4) + 1; + + if ((info.order < 1) || (info.rate < 1) || (info.barkmap < 1) || (info.numbooks < 1)) { + return (null); + } + + for (int j = 0; j < info.numbooks; j++) { + info.books[j] = opb.read(8); + if (info.books[j] < 0 || info.books[j] >= vi.books) { + return (null); + } + } + return (info); + } + + Object look(DspState vd, InfoMode mi, Object i) { + float scale; + Info vi = vd.vi; + InfoFloor0 info = (InfoFloor0) i; + LookFloor0 look = new LookFloor0(); + look.m = info.order; + look.n = vi.blocksizes[mi.blockflag] / 2; + look.ln = info.barkmap; + look.vi = info; + look.lpclook.init(look.ln, look.m); + + // we choose a scaling constant so that: + scale = look.ln / toBARK((float) (info.rate / 2.)); + + // the mapping from a linear scale to a smaller bark scale is + // straightforward. We do *not* make sure that the linear mapping + // does not skip bark-scale bins; the decoder simply skips them and + // the encoder may do what it wishes in filling them. They're + // necessary in some mapping combinations to keep the scale spacing + // accurate + look.linearmap = new int[look.n]; + for (int j = 0; j < look.n; j++) { + int val = (int) Math.floor(toBARK((float) ((info.rate / 2.) / look.n * j)) * scale); // bark numbers + // represent band + // edges + if (val >= look.ln) + val = look.ln; // guard against the approximation + look.linearmap[j] = val; + } + return look; + } + + static float toBARK(float f) { + return (float) (13.1 * Math.atan(.00074 * (f)) + 2.24 * Math.atan((f) * (f) * 1.85e-8) + 1e-4 * (f)); + } + + Object state(Object i) { + EchstateFloor0 state = new EchstateFloor0(); + InfoFloor0 info = (InfoFloor0) i; + + // a safe size if usually too big (dim==1) + state.codewords = new int[info.order]; + state.curve = new float[info.barkmap]; + state.frameno = -1; + return (state); + } + + void free_info(Object i) { + } + + void free_look(Object i) { + } + + void free_state(Object vs) { + } + + int forward(Block vb, Object i, float[] in, float[] out, Object vs) { + return 0; + } + + float[] lsp = null; + + int inverse(Block vb, Object i, float[] out) { + // System.err.println("Floor0.inverse "+i.getClass()+"]"); + LookFloor0 look = (LookFloor0) i; + InfoFloor0 info = look.vi; + int ampraw = vb.opb.read(info.ampbits); + if (ampraw > 0) { // also handles the -1 out of data case + int maxval = (1 << info.ampbits) - 1; + float amp = (float) ampraw / maxval * info.ampdB; + int booknum = vb.opb.read(Util.ilog(info.numbooks)); + + if (booknum != -1 && booknum < info.numbooks) { + + synchronized (this) { + if (lsp == null || lsp.length < look.m) { + lsp = new float[look.m]; + } else { + for (int j = 0; j < look.m; j++) + lsp[j] = 0.f; + } + + CodeBook b = vb.vd.fullbooks[info.books[booknum]]; + float last = 0.f; + + for (int j = 0; j < look.m; j++) + out[j] = 0.0f; + + for (int j = 0; j < look.m; j += b.dim) { + if (b.decodevs(lsp, j, vb.opb, 1, -1) == -1) { + for (int k = 0; k < look.n; k++) + out[k] = 0.0f; + return (0); + } + } + for (int j = 0; j < look.m;) { + for (int k = 0; k < b.dim; k++, j++) + lsp[j] += last; + last = lsp[j - 1]; + } + // take the coefficients back to a spectral envelope curve + Lsp.lsp_to_curve(out, look.linearmap, look.n, look.ln, lsp, look.m, amp, info.ampdB); + + return (1); + } + } + } + return (0); + } + + Object inverse1(Block vb, Object i, Object memo) { + LookFloor0 look = (LookFloor0) i; + InfoFloor0 info = look.vi; + float[] lsp = null; + if (memo instanceof float[]) { + lsp = (float[]) memo; + } + + int ampraw = vb.opb.read(info.ampbits); + if (ampraw > 0) { // also handles the -1 out of data case + int maxval = (1 << info.ampbits) - 1; + float amp = (float) ampraw / maxval * info.ampdB; + int booknum = vb.opb.read(Util.ilog(info.numbooks)); + + if (booknum != -1 && booknum < info.numbooks) { + CodeBook b = vb.vd.fullbooks[info.books[booknum]]; + float last = 0.f; + + if (lsp == null || lsp.length < look.m + 1) { + lsp = new float[look.m + 1]; + } else { + for (int j = 0; j < lsp.length; j++) + lsp[j] = 0.f; + } + + for (int j = 0; j < look.m; j += b.dim) { + if (b.decodev_set(lsp, j, vb.opb, b.dim) == -1) { + return (null); + } + } + + for (int j = 0; j < look.m;) { + for (int k = 0; k < b.dim; k++, j++) + lsp[j] += last; + last = lsp[j - 1]; + } + lsp[look.m] = amp; + return (lsp); + } + } + return (null); + } + + int inverse2(Block vb, Object i, Object memo, float[] out) { + LookFloor0 look = (LookFloor0) i; + InfoFloor0 info = look.vi; + + if (memo != null) { + float[] lsp = (float[]) memo; + float amp = lsp[look.m]; + + Lsp.lsp_to_curve(out, look.linearmap, look.n, look.ln, lsp, look.m, amp, info.ampdB); + return (1); + } + for (int j = 0; j < look.n; j++) { + out[j] = 0.f; + } + return (0); + } + + static float fromdB(float x) { + return (float) (Math.exp((x) * .11512925)); + } + + static void lsp_to_lpc(float[] lsp, float[] lpc, int m) { + int i, j, m2 = m / 2; + float[] O = new float[m2]; + float[] E = new float[m2]; + float A; + float[] Ae = new float[m2 + 1]; + float[] Ao = new float[m2 + 1]; + float B; + float[] Be = new float[m2]; + float[] Bo = new float[m2]; + float temp; + + // even/odd roots setup + for (i = 0; i < m2; i++) { + O[i] = (float) (-2. * Math.cos(lsp[i * 2])); + E[i] = (float) (-2. * Math.cos(lsp[i * 2 + 1])); + } + + // set up impulse response + for (j = 0; j < m2; j++) { + Ae[j] = 0.f; + Ao[j] = 1.f; + Be[j] = 0.f; + Bo[j] = 1.f; + } + Ao[j] = 1.f; + Ae[j] = 1.f; + + // run impulse response + for (i = 1; i < m + 1; i++) { + A = B = 0.f; + for (j = 0; j < m2; j++) { + temp = O[j] * Ao[j] + Ae[j]; + Ae[j] = Ao[j]; + Ao[j] = A; + A += temp; + + temp = E[j] * Bo[j] + Be[j]; + Be[j] = Bo[j]; + Bo[j] = B; + B += temp; + } + lpc[i - 1] = (A + Ao[j] + B - Ae[j]) / 2; + Ao[j] = A; + Ae[j] = B; + } + } + + static void lpc_to_curve(float[] curve, float[] lpc, float amp, LookFloor0 l, String name, int frameno) { + // l->m+1 must be less than l->ln, but guard in case we get a bad stream + float[] lcurve = new float[Math.max(l.ln * 2, l.m * 2 + 2)]; + + if (amp == 0) { + for (int j = 0; j < l.n; j++) + curve[j] = 0.0f; + return; + } + l.lpclook.lpc_to_curve(lcurve, lpc, amp); + + for (int i = 0; i < l.n; i++) + curve[i] = lcurve[l.linearmap[i]]; + } + + class InfoFloor0 { + int order; + int rate; + int barkmap; + + int ampbits; + int ampdB; + + int numbooks; // <= 16 + int[] books = new int[16]; + } + + class LookFloor0 { + int n; + int ln; + int m; + int[] linearmap; + + InfoFloor0 vi; + Lpc lpclook = new Lpc(); + } + + class EchstateFloor0 { + int[] codewords; + float[] curve; + long frameno; + long codes; + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Floor1.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Floor1.java new file mode 100644 index 0000000..c9f77c3 --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Floor1.java @@ -0,0 +1,584 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class Floor1 extends FuncFloor { + static final int floor1_rangedb = 140; + static final int VIF_POSIT = 63; + + void pack(Object i, Buffer opb) { + InfoFloor1 info = (InfoFloor1) i; + + int count = 0; + int rangebits; + int maxposit = info.postlist[1]; + int maxclass = -1; + + /* save out partitions */ + opb.write(info.partitions, 5); /* only 0 to 31 legal */ + for (int j = 0; j < info.partitions; j++) { + opb.write(info.partitionclass[j], 4); /* only 0 to 15 legal */ + if (maxclass < info.partitionclass[j]) + maxclass = info.partitionclass[j]; + } + + /* save out partition classes */ + for (int j = 0; j < maxclass + 1; j++) { + opb.write(info.class_dim[j] - 1, 3); /* 1 to 8 */ + opb.write(info.class_subs[j], 2); /* 0 to 3 */ + if (info.class_subs[j] != 0) { + opb.write(info.class_book[j], 8); + } + for (int k = 0; k < (1 << info.class_subs[j]); k++) { + opb.write(info.class_subbook[j][k] + 1, 8); + } + } + + /* save out the post list */ + opb.write(info.mult - 1, 2); /* only 1,2,3,4 legal now */ + opb.write(Util.ilog2(maxposit), 4); + rangebits = Util.ilog2(maxposit); + + for (int j = 0, k = 0; j < info.partitions; j++) { + count += info.class_dim[info.partitionclass[j]]; + for (; k < count; k++) { + opb.write(info.postlist[k + 2], rangebits); + } + } + } + + Object unpack(Info vi, Buffer opb) { + int count = 0, maxclass = -1, rangebits; + InfoFloor1 info = new InfoFloor1(); + + /* read partitions */ + info.partitions = opb.read(5); /* only 0 to 31 legal */ + for (int j = 0; j < info.partitions; j++) { + info.partitionclass[j] = opb.read(4); /* only 0 to 15 legal */ + if (maxclass < info.partitionclass[j]) + maxclass = info.partitionclass[j]; + } + + /* read partition classes */ + for (int j = 0; j < maxclass + 1; j++) { + info.class_dim[j] = opb.read(3) + 1; /* 1 to 8 */ + info.class_subs[j] = opb.read(2); /* 0,1,2,3 bits */ + if (info.class_subs[j] < 0) { + info.free(); + return (null); + } + if (info.class_subs[j] != 0) { + info.class_book[j] = opb.read(8); + } + if (info.class_book[j] < 0 || info.class_book[j] >= vi.books) { + info.free(); + return (null); + } + for (int k = 0; k < (1 << info.class_subs[j]); k++) { + info.class_subbook[j][k] = opb.read(8) - 1; + if (info.class_subbook[j][k] < -1 || info.class_subbook[j][k] >= vi.books) { + info.free(); + return (null); + } + } + } + + /* read the post list */ + info.mult = opb.read(2) + 1; /* only 1,2,3,4 legal now */ + rangebits = opb.read(4); + + for (int j = 0, k = 0; j < info.partitions; j++) { + count += info.class_dim[info.partitionclass[j]]; + for (; k < count; k++) { + int t = info.postlist[k + 2] = opb.read(rangebits); + if (t < 0 || t >= (1 << rangebits)) { + info.free(); + return (null); + } + } + } + info.postlist[0] = 0; + info.postlist[1] = 1 << rangebits; + + return (info); + } + + Object look(DspState vd, InfoMode mi, Object i) { + int _n = 0; + + int[] sortpointer = new int[VIF_POSIT + 2]; + + // Info vi=vd.vi; + + InfoFloor1 info = (InfoFloor1) i; + LookFloor1 look = new LookFloor1(); + look.vi = info; + look.n = info.postlist[1]; + + /* + * we drop each position value in-between already decoded values, and use linear + * interpolation to predict each new value past the edges. The positions are + * read in the order of the position list... we precompute the bounding + * positions in the lookup. Of course, the neighbors can change (if a position + * is declined), but this is an initial mapping + */ + + for (int j = 0; j < info.partitions; j++) { + _n += info.class_dim[info.partitionclass[j]]; + } + _n += 2; + look.posts = _n; + + /* also store a sorted position index */ + for (int j = 0; j < _n; j++) { + sortpointer[j] = j; + } + // qsort(sortpointer,n,sizeof(int),icomp); // !! + + int foo; + for (int j = 0; j < _n - 1; j++) { + for (int k = j; k < _n; k++) { + if (info.postlist[sortpointer[j]] > info.postlist[sortpointer[k]]) { + foo = sortpointer[k]; + sortpointer[k] = sortpointer[j]; + sortpointer[j] = foo; + } + } + } + + /* points from sort order back to range number */ + for (int j = 0; j < _n; j++) { + look.forward_index[j] = sortpointer[j]; + } + /* points from range order to sorted position */ + for (int j = 0; j < _n; j++) { + look.reverse_index[look.forward_index[j]] = j; + } + /* we actually need the post values too */ + for (int j = 0; j < _n; j++) { + look.sorted_index[j] = info.postlist[look.forward_index[j]]; + } + + /* quantize values to multiplier spec */ + switch (info.mult) { + case 1: /* 1024 -> 256 */ + look.quant_q = 256; + break; + case 2: /* 1024 -> 128 */ + look.quant_q = 128; + break; + case 3: /* 1024 -> 86 */ + look.quant_q = 86; + break; + case 4: /* 1024 -> 64 */ + look.quant_q = 64; + break; + default: + look.quant_q = -1; + } + + /* + * discover our neighbors for decode where we don't use fit flags (that would + * push the neighbors outward) + */ + for (int j = 0; j < _n - 2; j++) { + int lo = 0; + int hi = 1; + int lx = 0; + int hx = look.n; + int currentx = info.postlist[j + 2]; + for (int k = 0; k < j + 2; k++) { + int x = info.postlist[k]; + if (x > lx && x < currentx) { + lo = k; + lx = x; + } + if (x < hx && x > currentx) { + hi = k; + hx = x; + } + } + look.loneighbor[j] = lo; + look.hineighbor[j] = hi; + } + + return look; + } + + void free_info(Object i) { + } + + void free_look(Object i) { + } + + void free_state(Object vs) { + } + + int forward(Block vb, Object i, float[] in, float[] out, Object vs) { + return 0; + } + + Object inverse1(Block vb, Object ii, Object memo) { + LookFloor1 look = (LookFloor1) ii; + InfoFloor1 info = look.vi; + CodeBook[] books = vb.vd.fullbooks; + + /* unpack wrapped/predicted values from stream */ + if (vb.opb.read(1) == 1) { + int[] fit_value = null; + if (memo instanceof int[]) { + fit_value = (int[]) memo; + } + if (fit_value == null || fit_value.length < look.posts) { + fit_value = new int[look.posts]; + } else { + for (int i = 0; i < fit_value.length; i++) + fit_value[i] = 0; + } + + fit_value[0] = vb.opb.read(Util.ilog(look.quant_q - 1)); + fit_value[1] = vb.opb.read(Util.ilog(look.quant_q - 1)); + + /* partition by partition */ + for (int i = 0, j = 2; i < info.partitions; i++) { + int clss = info.partitionclass[i]; + int cdim = info.class_dim[clss]; + int csubbits = info.class_subs[clss]; + int csub = 1 << csubbits; + int cval = 0; + + /* decode the partition's first stage cascade value */ + if (csubbits != 0) { + cval = books[info.class_book[clss]].decode(vb.opb); + + if (cval == -1) { + return (null); + } + } + + for (int k = 0; k < cdim; k++) { + int book = info.class_subbook[clss][cval & (csub - 1)]; + cval >>>= csubbits; + if (book >= 0) { + if ((fit_value[j + k] = books[book].decode(vb.opb)) == -1) { + return (null); + } + } else { + fit_value[j + k] = 0; + } + } + j += cdim; + } + + /* unwrap positive values and reconsitute via linear interpolation */ + for (int i = 2; i < look.posts; i++) { + int predicted = render_point(info.postlist[look.loneighbor[i - 2]], + info.postlist[look.hineighbor[i - 2]], fit_value[look.loneighbor[i - 2]], + fit_value[look.hineighbor[i - 2]], info.postlist[i]); + int hiroom = look.quant_q - predicted; + int loroom = predicted; + int room = (hiroom < loroom ? hiroom : loroom) << 1; + int val = fit_value[i]; + + if (val != 0) { + if (val >= room) { + if (hiroom > loroom) { + val = val - loroom; + } else { + val = -1 - (val - hiroom); + } + } else { + if ((val & 1) != 0) { + val = -((val + 1) >>> 1); + } else { + val >>= 1; + } + } + + fit_value[i] = val + predicted; + fit_value[look.loneighbor[i - 2]] &= 0x7fff; + fit_value[look.hineighbor[i - 2]] &= 0x7fff; + } else { + fit_value[i] = predicted | 0x8000; + } + } + return (fit_value); + } + + return (null); + } + + private static int render_point(int x0, int x1, int y0, int y1, int x) { + y0 &= 0x7fff; /* mask off flag */ + y1 &= 0x7fff; + + { + int dy = y1 - y0; + int adx = x1 - x0; + int ady = Math.abs(dy); + int err = ady * (x - x0); + + int off = (int) (err / adx); + if (dy < 0) + return (y0 - off); + return (y0 + off); + } + } + + int inverse2(Block vb, Object i, Object memo, float[] out) { + LookFloor1 look = (LookFloor1) i; + InfoFloor1 info = look.vi; + int n = vb.vd.vi.blocksizes[vb.mode] / 2; + + if (memo != null) { + /* render the lines */ + int[] fit_value = (int[]) memo; + int hx = 0; + int lx = 0; + int ly = fit_value[0] * info.mult; + for (int j = 1; j < look.posts; j++) { + int current = look.forward_index[j]; + int hy = fit_value[current] & 0x7fff; + if (hy == fit_value[current]) { + hy *= info.mult; + hx = info.postlist[current]; + + render_line(lx, hx, ly, hy, out); + + lx = hx; + ly = hy; + } + } + for (int j = hx; j < n; j++) { + out[j] *= out[j - 1]; /* be certain */ + } + return (1); + } + for (int j = 0; j < n; j++) { + out[j] = 0.f; + } + return (0); + } + + private static float[] FLOOR_fromdB_LOOKUP = { 1.0649863e-07F, 1.1341951e-07F, 1.2079015e-07F, 1.2863978e-07F, + 1.3699951e-07F, 1.4590251e-07F, 1.5538408e-07F, 1.6548181e-07F, 1.7623575e-07F, 1.8768855e-07F, + 1.9988561e-07F, 2.128753e-07F, 2.2670913e-07F, 2.4144197e-07F, 2.5713223e-07F, 2.7384213e-07F, + 2.9163793e-07F, 3.1059021e-07F, 3.3077411e-07F, 3.5226968e-07F, 3.7516214e-07F, 3.9954229e-07F, + 4.2550680e-07F, 4.5315863e-07F, 4.8260743e-07F, 5.1396998e-07F, 5.4737065e-07F, 5.8294187e-07F, + 6.2082472e-07F, 6.6116941e-07F, 7.0413592e-07F, 7.4989464e-07F, 7.9862701e-07F, 8.5052630e-07F, + 9.0579828e-07F, 9.6466216e-07F, 1.0273513e-06F, 1.0941144e-06F, 1.1652161e-06F, 1.2409384e-06F, + 1.3215816e-06F, 1.4074654e-06F, 1.4989305e-06F, 1.5963394e-06F, 1.7000785e-06F, 1.8105592e-06F, + 1.9282195e-06F, 2.0535261e-06F, 2.1869758e-06F, 2.3290978e-06F, 2.4804557e-06F, 2.6416497e-06F, + 2.8133190e-06F, 2.9961443e-06F, 3.1908506e-06F, 3.3982101e-06F, 3.6190449e-06F, 3.8542308e-06F, + 4.1047004e-06F, 4.3714470e-06F, 4.6555282e-06F, 4.9580707e-06F, 5.2802740e-06F, 5.6234160e-06F, + 5.9888572e-06F, 6.3780469e-06F, 6.7925283e-06F, 7.2339451e-06F, 7.7040476e-06F, 8.2047000e-06F, + 8.7378876e-06F, 9.3057248e-06F, 9.9104632e-06F, 1.0554501e-05F, 1.1240392e-05F, 1.1970856e-05F, + 1.2748789e-05F, 1.3577278e-05F, 1.4459606e-05F, 1.5399272e-05F, 1.6400004e-05F, 1.7465768e-05F, + 1.8600792e-05F, 1.9809576e-05F, 2.1096914e-05F, 2.2467911e-05F, 2.3928002e-05F, 2.5482978e-05F, + 2.7139006e-05F, 2.8902651e-05F, 3.0780908e-05F, 3.2781225e-05F, 3.4911534e-05F, 3.7180282e-05F, + 3.9596466e-05F, 4.2169667e-05F, 4.4910090e-05F, 4.7828601e-05F, 5.0936773e-05F, 5.4246931e-05F, + 5.7772202e-05F, 6.1526565e-05F, 6.5524908e-05F, 6.9783085e-05F, 7.4317983e-05F, 7.9147585e-05F, + 8.4291040e-05F, 8.9768747e-05F, 9.5602426e-05F, 0.00010181521F, 0.00010843174F, 0.00011547824F, + 0.00012298267F, 0.00013097477F, 0.00013948625F, 0.00014855085F, 0.00015820453F, 0.00016848555F, + 0.00017943469F, 0.00019109536F, 0.00020351382F, 0.00021673929F, 0.00023082423F, 0.00024582449F, + 0.00026179955F, 0.00027881276F, 0.00029693158F, 0.00031622787F, 0.00033677814F, 0.00035866388F, + 0.00038197188F, 0.00040679456F, 0.00043323036F, 0.00046138411F, 0.00049136745F, 0.00052329927F, + 0.00055730621F, 0.00059352311F, 0.00063209358F, 0.00067317058F, 0.00071691700F, 0.00076350630F, + 0.00081312324F, 0.00086596457F, 0.00092223983F, 0.00098217216F, 0.0010459992F, 0.0011139742F, 0.0011863665F, + 0.0012634633F, 0.0013455702F, 0.0014330129F, 0.0015261382F, 0.0016253153F, 0.0017309374F, 0.0018434235F, + 0.0019632195F, 0.0020908006F, 0.0022266726F, 0.0023713743F, 0.0025254795F, 0.0026895994F, 0.0028643847F, + 0.0030505286F, 0.0032487691F, 0.0034598925F, 0.0036847358F, 0.0039241906F, 0.0041792066F, 0.0044507950F, + 0.0047400328F, 0.0050480668F, 0.0053761186F, 0.0057254891F, 0.0060975636F, 0.0064938176F, 0.0069158225F, + 0.0073652516F, 0.0078438871F, 0.0083536271F, 0.0088964928F, 0.009474637F, 0.010090352F, 0.010746080F, + 0.011444421F, 0.012188144F, 0.012980198F, 0.013823725F, 0.014722068F, 0.015678791F, 0.016697687F, + 0.017782797F, 0.018938423F, 0.020169149F, 0.021479854F, 0.022875735F, 0.024362330F, 0.025945531F, + 0.027631618F, 0.029427276F, 0.031339626F, 0.033376252F, 0.035545228F, 0.037855157F, 0.040315199F, + 0.042935108F, 0.045725273F, 0.048696758F, 0.051861348F, 0.055231591F, 0.058820850F, 0.062643361F, + 0.066714279F, 0.071049749F, 0.075666962F, 0.080584227F, 0.085821044F, 0.091398179F, 0.097337747F, + 0.10366330F, 0.11039993F, 0.11757434F, 0.12521498F, 0.13335215F, 0.14201813F, 0.15124727F, 0.16107617F, + 0.17154380F, 0.18269168F, 0.19456402F, 0.20720788F, 0.22067342F, 0.23501402F, 0.25028656F, 0.26655159F, + 0.28387361F, 0.30232132F, 0.32196786F, 0.34289114F, 0.36517414F, 0.38890521F, 0.41417847F, 0.44109412F, + 0.46975890F, 0.50028648F, 0.53279791F, 0.56742212F, 0.60429640F, 0.64356699F, 0.68538959F, 0.72993007F, + 0.77736504F, 0.82788260F, 0.88168307F, 0.9389798F, 1.F }; + + private static void render_line(int x0, int x1, int y0, int y1, float[] d) { + int dy = y1 - y0; + int adx = x1 - x0; + int ady = Math.abs(dy); + int base = dy / adx; + int sy = (dy < 0 ? base - 1 : base + 1); + int x = x0; + int y = y0; + int err = 0; + + ady -= Math.abs(base * adx); + + d[x] *= FLOOR_fromdB_LOOKUP[y]; + while (++x < x1) { + err = err + ady; + if (err >= adx) { + err -= adx; + y += sy; + } else { + y += base; + } + d[x] *= FLOOR_fromdB_LOOKUP[y]; + } + } + + class InfoFloor1 { + static final int VIF_POSIT = 63; + static final int VIF_CLASS = 16; + static final int VIF_PARTS = 31; + + int partitions; /* 0 to 31 */ + int[] partitionclass = new int[VIF_PARTS]; /* 0 to 15 */ + + int[] class_dim = new int[VIF_CLASS]; /* 1 to 8 */ + int[] class_subs = new int[VIF_CLASS]; /* 0,1,2,3 (bits: 1< + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +abstract class FuncFloor { + + public static FuncFloor[] floor_P = { new Floor0(), new Floor1() }; + + abstract void pack(Object i, Buffer opb); + + abstract Object unpack(Info vi, Buffer opb); + + abstract Object look(DspState vd, InfoMode mi, Object i); + + abstract void free_info(Object i); + + abstract void free_look(Object i); + + abstract void free_state(Object vs); + + abstract int forward(Block vb, Object i, float[] in, float[] out, Object vs); + + abstract Object inverse1(Block vb, Object i, Object memo); + + abstract int inverse2(Block vb, Object i, Object memo, float[] out); +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/FuncMapping.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/FuncMapping.java new file mode 100644 index 0000000..1a83098 --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/FuncMapping.java @@ -0,0 +1,45 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +abstract class FuncMapping { + public static FuncMapping[] mapping_P = { new Mapping0() }; + + abstract void pack(Info info, Object imap, Buffer buffer); + + abstract Object unpack(Info info, Buffer buffer); + + abstract Object look(DspState vd, InfoMode vm, Object m); + + abstract void free_info(Object imap); + + abstract void free_look(Object imap); + + abstract int inverse(Block vd, Object lm); +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/FuncResidue.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/FuncResidue.java new file mode 100644 index 0000000..33ab1e2 --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/FuncResidue.java @@ -0,0 +1,45 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +abstract class FuncResidue { + public static FuncResidue[] residue_P = { new Residue0(), new Residue1(), new Residue2() }; + + abstract void pack(Object vr, Buffer opb); + + abstract Object unpack(Info vi, Buffer opb); + + abstract Object look(DspState vd, InfoMode vm, Object vr); + + abstract void free_info(Object i); + + abstract void free_look(Object i); + + abstract int inverse(Block vb, Object vl, float[][] in, int[] nonzero, int ch); +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/FuncTime.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/FuncTime.java new file mode 100644 index 0000000..019b634 --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/FuncTime.java @@ -0,0 +1,45 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +abstract class FuncTime { + public static FuncTime[] time_P = { new Time0() }; + + abstract void pack(Object i, Buffer opb); + + abstract Object unpack(Info vi, Buffer opb); + + abstract Object look(DspState vd, InfoMode vm, Object i); + + abstract void free_info(Object i); + + abstract void free_look(Object i); + + abstract int inverse(Block vb, Object i, float[] in, float[] out); +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Info.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Info.java new file mode 100644 index 0000000..e0f0285 --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Info.java @@ -0,0 +1,468 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +public class Info { + private static final int OV_EBADPACKET = -136; + private static final int OV_ENOTAUDIO = -135; + + private static byte[] _vorbis = "vorbis".getBytes(); + private static final int VI_TIMEB = 1; + // private static final int VI_FLOORB=1; + private static final int VI_FLOORB = 2; + // private static final int VI_RESB=1; + private static final int VI_RESB = 3; + private static final int VI_MAPB = 1; + private static final int VI_WINDOWB = 1; + + public int version; + public int channels; + public int rate; + + // The below bitrate declarations are *hints*. + // Combinations of the three values carry the following implications: + // + // all three set to the same value: + // implies a fixed rate bitstream + // only nominal set: + // implies a VBR stream that averages the nominal bitrate. No hard + // upper/lower limit + // upper and or lower set: + // implies a VBR bitstream that obeys the bitrate limits. nominal + // may also be set to give a nominal rate. + // none set: + // the coder does not care to speculate. + + int bitrate_upper; + int bitrate_nominal; + int bitrate_lower; + + // Vorbis supports only short and long blocks, but allows the + // encoder to choose the sizes + + int[] blocksizes = new int[2]; + + // modes are the primary means of supporting on-the-fly different + // blocksizes, different channel mappings (LR or mid-side), + // different residue backends, etc. Each mode consists of a + // blocksize flag and a mapping (along with the mapping setup + + int modes; + int maps; + int times; + int floors; + int residues; + int books; + int psys; // encode only + + InfoMode[] mode_param = null; + + int[] map_type = null; + Object[] map_param = null; + + int[] time_type = null; + Object[] time_param = null; + + int[] floor_type = null; + Object[] floor_param = null; + + int[] residue_type = null; + Object[] residue_param = null; + + StaticCodeBook[] book_param = null; + + PsyInfo[] psy_param = new PsyInfo[64]; // encode only + + // for block long/sort tuning; encode only + int envelopesa; + float preecho_thresh; + float preecho_clamp; + + // used by synthesis, which has a full, alloced vi + public void init() { + rate = 0; + } + + public void clear() { + for (int i = 0; i < modes; i++) { + mode_param[i] = null; + } + mode_param = null; + + for (int i = 0; i < maps; i++) { // unpack does the range checking + FuncMapping.mapping_P[map_type[i]].free_info(map_param[i]); + } + map_param = null; + + for (int i = 0; i < times; i++) { // unpack does the range checking + FuncTime.time_P[time_type[i]].free_info(time_param[i]); + } + time_param = null; + + for (int i = 0; i < floors; i++) { // unpack does the range checking + FuncFloor.floor_P[floor_type[i]].free_info(floor_param[i]); + } + floor_param = null; + + for (int i = 0; i < residues; i++) { // unpack does the range checking + FuncResidue.residue_P[residue_type[i]].free_info(residue_param[i]); + } + residue_param = null; + + // the static codebooks *are* freed if you call info_clear, because + // decode side does alloc a 'static' codebook. Calling clear on the + // full codebook does not clear the static codebook (that's our + // responsibility) + for (int i = 0; i < books; i++) { + // just in case the decoder pre-cleared to save space + if (book_param[i] != null) { + book_param[i].clear(); + book_param[i] = null; + } + } + // if(vi->book_param)free(vi->book_param); + book_param = null; + + for (int i = 0; i < psys; i++) { + psy_param[i].free(); + } + + } + + // Header packing/unpacking + int unpack_info(Buffer opb) { + version = opb.read(32); + if (version != 0) + return (-1); + + channels = opb.read(8); + rate = opb.read(32); + + bitrate_upper = opb.read(32); + bitrate_nominal = opb.read(32); + bitrate_lower = opb.read(32); + + blocksizes[0] = 1 << opb.read(4); + blocksizes[1] = 1 << opb.read(4); + + if ((rate < 1) || (channels < 1) || (blocksizes[0] < 8) || (blocksizes[1] < blocksizes[0]) + || (opb.read(1) != 1)) { + clear(); + return (-1); + } + return (0); + } + + // all of the real encoding details are here. The modes, books, + // everything + int unpack_books(Buffer opb) { + + books = opb.read(8) + 1; + + if (book_param == null || book_param.length != books) + book_param = new StaticCodeBook[books]; + for (int i = 0; i < books; i++) { + book_param[i] = new StaticCodeBook(); + if (book_param[i].unpack(opb) != 0) { + clear(); + return (-1); + } + } + + // time backend settings + times = opb.read(6) + 1; + if (time_type == null || time_type.length != times) + time_type = new int[times]; + if (time_param == null || time_param.length != times) + time_param = new Object[times]; + for (int i = 0; i < times; i++) { + time_type[i] = opb.read(16); + if (time_type[i] < 0 || time_type[i] >= VI_TIMEB) { + clear(); + return (-1); + } + time_param[i] = FuncTime.time_P[time_type[i]].unpack(this, opb); + if (time_param[i] == null) { + clear(); + return (-1); + } + } + + // floor backend settings + floors = opb.read(6) + 1; + if (floor_type == null || floor_type.length != floors) + floor_type = new int[floors]; + if (floor_param == null || floor_param.length != floors) + floor_param = new Object[floors]; + + for (int i = 0; i < floors; i++) { + floor_type[i] = opb.read(16); + if (floor_type[i] < 0 || floor_type[i] >= VI_FLOORB) { + clear(); + return (-1); + } + + floor_param[i] = FuncFloor.floor_P[floor_type[i]].unpack(this, opb); + if (floor_param[i] == null) { + clear(); + return (-1); + } + } + + // residue backend settings + residues = opb.read(6) + 1; + + if (residue_type == null || residue_type.length != residues) + residue_type = new int[residues]; + + if (residue_param == null || residue_param.length != residues) + residue_param = new Object[residues]; + + for (int i = 0; i < residues; i++) { + residue_type[i] = opb.read(16); + if (residue_type[i] < 0 || residue_type[i] >= VI_RESB) { + clear(); + return (-1); + } + residue_param[i] = FuncResidue.residue_P[residue_type[i]].unpack(this, opb); + if (residue_param[i] == null) { + clear(); + return (-1); + } + } + + // map backend settings + maps = opb.read(6) + 1; + if (map_type == null || map_type.length != maps) + map_type = new int[maps]; + if (map_param == null || map_param.length != maps) + map_param = new Object[maps]; + for (int i = 0; i < maps; i++) { + map_type[i] = opb.read(16); + if (map_type[i] < 0 || map_type[i] >= VI_MAPB) { + clear(); + return (-1); + } + map_param[i] = FuncMapping.mapping_P[map_type[i]].unpack(this, opb); + if (map_param[i] == null) { + clear(); + return (-1); + } + } + + // mode settings + modes = opb.read(6) + 1; + if (mode_param == null || mode_param.length != modes) + mode_param = new InfoMode[modes]; + for (int i = 0; i < modes; i++) { + mode_param[i] = new InfoMode(); + mode_param[i].blockflag = opb.read(1); + mode_param[i].windowtype = opb.read(16); + mode_param[i].transformtype = opb.read(16); + mode_param[i].mapping = opb.read(8); + + if ((mode_param[i].windowtype >= VI_WINDOWB) || (mode_param[i].transformtype >= VI_WINDOWB) + || (mode_param[i].mapping >= maps)) { + clear(); + return (-1); + } + } + + if (opb.read(1) != 1) { + clear(); + return (-1); + } + + return (0); + } + + // The Vorbis header is in three packets; the initial small packet in + // the first page that identifies basic parameters, a second packet + // with bitstream comments and a third packet that holds the + // codebook. + + public int synthesis_headerin(Comment vc, Packet op) { + Buffer opb = new Buffer(); + + if (op != null) { + opb.readinit(op.packet_base, op.packet, op.bytes); + + // Which of the three types of header is this? + // Also verify header-ness, vorbis + { + byte[] buffer = new byte[6]; + int packtype = opb.read(8); + opb.read(buffer, 6); + if (buffer[0] != 'v' || buffer[1] != 'o' || buffer[2] != 'r' || buffer[3] != 'b' || buffer[4] != 'i' + || buffer[5] != 's') { + // not a vorbis header + return (-1); + } + switch (packtype) { + case 0x01: // least significant *bit* is read first + if (op.b_o_s == 0) { + // Not the initial packet + return (-1); + } + if (rate != 0) { + // previously initialized info header + return (-1); + } + return (unpack_info(opb)); + case 0x03: // least significant *bit* is read first + if (rate == 0) { + // um... we didn't get the initial header + return (-1); + } + return (vc.unpack(opb)); + case 0x05: // least significant *bit* is read first + if (rate == 0 || vc.vendor == null) { + // um... we didn;t get the initial header or comments yet + return (-1); + } + return (unpack_books(opb)); + default: + // Not a valid vorbis header type + // return(-1); + break; + } + } + } + return (-1); + } + + // pack side + int pack_info(Buffer opb) { + // preamble + opb.write(0x01, 8); + opb.write(_vorbis); + + // basic information about the stream + opb.write(0x00, 32); + opb.write(channels, 8); + opb.write(rate, 32); + + opb.write(bitrate_upper, 32); + opb.write(bitrate_nominal, 32); + opb.write(bitrate_lower, 32); + + opb.write(Util.ilog2(blocksizes[0]), 4); + opb.write(Util.ilog2(blocksizes[1]), 4); + opb.write(1, 1); + return (0); + } + + int pack_books(Buffer opb) { + opb.write(0x05, 8); + opb.write(_vorbis); + + // books + opb.write(books - 1, 8); + for (int i = 0; i < books; i++) { + if (book_param[i].pack(opb) != 0) { + // goto err_out; + return (-1); + } + } + + // times + opb.write(times - 1, 6); + for (int i = 0; i < times; i++) { + opb.write(time_type[i], 16); + FuncTime.time_P[time_type[i]].pack(this.time_param[i], opb); + } + + // floors + opb.write(floors - 1, 6); + for (int i = 0; i < floors; i++) { + opb.write(floor_type[i], 16); + FuncFloor.floor_P[floor_type[i]].pack(floor_param[i], opb); + } + + // residues + opb.write(residues - 1, 6); + for (int i = 0; i < residues; i++) { + opb.write(residue_type[i], 16); + FuncResidue.residue_P[residue_type[i]].pack(residue_param[i], opb); + } + + // maps + opb.write(maps - 1, 6); + for (int i = 0; i < maps; i++) { + opb.write(map_type[i], 16); + FuncMapping.mapping_P[map_type[i]].pack(this, map_param[i], opb); + } + + // modes + opb.write(modes - 1, 6); + for (int i = 0; i < modes; i++) { + opb.write(mode_param[i].blockflag, 1); + opb.write(mode_param[i].windowtype, 16); + opb.write(mode_param[i].transformtype, 16); + opb.write(mode_param[i].mapping, 8); + } + opb.write(1, 1); + return (0); + } + + public int blocksize(Packet op) { + // codec_setup_info + Buffer opb = new Buffer(); + + int mode; + + opb.readinit(op.packet_base, op.packet, op.bytes); + + /* Check the packet type */ + if (opb.read(1) != 0) { + /* Oops. This is not an audio data packet */ + return (OV_ENOTAUDIO); + } + { + int modebits = 0; + int v = modes; + while (v > 1) { + modebits++; + v >>>= 1; + } + + /* read our mode and pre/post windowsize */ + mode = opb.read(modebits); + } + if (mode == -1) + return (OV_EBADPACKET); + return (blocksizes[mode_param[mode].blockflag]); + } + + public String toString() { + return "version:" + version + ", channels:" + channels + ", rate:" + rate + + ", bitrate:" + bitrate_upper + "," + bitrate_nominal + "," + + bitrate_lower; + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/InfoMode.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/InfoMode.java new file mode 100644 index 0000000..e7f203c --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/InfoMode.java @@ -0,0 +1,34 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class InfoMode { + int blockflag; + int windowtype; + int transformtype; + int mapping; +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/JOrbisException.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/JOrbisException.java new file mode 100644 index 0000000..7862e98 --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/JOrbisException.java @@ -0,0 +1,40 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +public class JOrbisException extends Exception { + + private static final long serialVersionUID = 1L; + + public JOrbisException() { + super(); + } + + public JOrbisException(String s) { + super("JOrbis: " + s); + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Lookup.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Lookup.java new file mode 100644 index 0000000..5accc93 --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Lookup.java @@ -0,0 +1,122 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class Lookup { + static final int COS_LOOKUP_SZ = 128; + static final float[] COS_LOOKUP = { +1.0000000000000f, +0.9996988186962f, +0.9987954562052f, +0.9972904566787f, + +0.9951847266722f, +0.9924795345987f, +0.9891765099648f, +0.9852776423889f, +0.9807852804032f, + +0.9757021300385f, +0.9700312531945f, +0.9637760657954f, +0.9569403357322f, +0.9495281805930f, + +0.9415440651830f, +0.9329927988347f, +0.9238795325113f, +0.9142097557035f, +0.9039892931234f, + +0.8932243011955f, +0.8819212643484f, +0.8700869911087f, +0.8577286100003f, +0.8448535652497f, + +0.8314696123025f, +0.8175848131516f, +0.8032075314806f, +0.7883464276266f, +0.7730104533627f, + +0.7572088465065f, +0.7409511253550f, +0.7242470829515f, +0.7071067811865f, +0.6895405447371f, + +0.6715589548470f, +0.6531728429538f, +0.6343932841636f, +0.6152315905806f, +0.5956993044924f, + +0.5758081914178f, +0.5555702330196f, +0.5349976198871f, +0.5141027441932f, +0.4928981922298f, + +0.4713967368260f, +0.4496113296546f, +0.4275550934303f, +0.4052413140050f, +0.3826834323651f, + +0.3598950365350f, +0.3368898533922f, +0.3136817403989f, +0.2902846772545f, +0.2667127574749f, + +0.2429801799033f, +0.2191012401569f, +0.1950903220161f, +0.1709618887603f, +0.1467304744554f, + +0.1224106751992f, +0.0980171403296f, +0.0735645635997f, +0.0490676743274f, +0.0245412285229f, + +0.0000000000000f, -0.0245412285229f, -0.0490676743274f, -0.0735645635997f, -0.0980171403296f, + -0.1224106751992f, -0.1467304744554f, -0.1709618887603f, -0.1950903220161f, -0.2191012401569f, + -0.2429801799033f, -0.2667127574749f, -0.2902846772545f, -0.3136817403989f, -0.3368898533922f, + -0.3598950365350f, -0.3826834323651f, -0.4052413140050f, -0.4275550934303f, -0.4496113296546f, + -0.4713967368260f, -0.4928981922298f, -0.5141027441932f, -0.5349976198871f, -0.5555702330196f, + -0.5758081914178f, -0.5956993044924f, -0.6152315905806f, -0.6343932841636f, -0.6531728429538f, + -0.6715589548470f, -0.6895405447371f, -0.7071067811865f, -0.7242470829515f, -0.7409511253550f, + -0.7572088465065f, -0.7730104533627f, -0.7883464276266f, -0.8032075314806f, -0.8175848131516f, + -0.8314696123025f, -0.8448535652497f, -0.8577286100003f, -0.8700869911087f, -0.8819212643484f, + -0.8932243011955f, -0.9039892931234f, -0.9142097557035f, -0.9238795325113f, -0.9329927988347f, + -0.9415440651830f, -0.9495281805930f, -0.9569403357322f, -0.9637760657954f, -0.9700312531945f, + -0.9757021300385f, -0.9807852804032f, -0.9852776423889f, -0.9891765099648f, -0.9924795345987f, + -0.9951847266722f, -0.9972904566787f, -0.9987954562052f, -0.9996988186962f, -1.0000000000000f, }; + + /* interpolated lookup based cos function, domain 0 to PI only */ + static float coslook(float a) { + double d = a * (.31830989 * (float) COS_LOOKUP_SZ); + int i = (int) d; + return COS_LOOKUP[i] + ((float) (d - i)) * (COS_LOOKUP[i + 1] - COS_LOOKUP[i]); + } + + static final int INVSQ_LOOKUP_SZ = 32; + static final float[] INVSQ_LOOKUP = { 1.414213562373f, 1.392621247646f, 1.371988681140f, 1.352246807566f, + 1.333333333333f, 1.315191898443f, 1.297771369046f, 1.281025230441f, 1.264911064067f, 1.249390095109f, + 1.234426799697f, 1.219988562661f, 1.206045378311f, 1.192569588000f, 1.179535649239f, 1.166919931983f, + 1.154700538379f, 1.142857142857f, 1.131370849898f, 1.120224067222f, 1.109400392450f, 1.098884511590f, + 1.088662107904f, 1.078719779941f, 1.069044967650f, 1.059625885652f, 1.050451462878f, 1.041511287847f, + 1.032795558989f, 1.024295039463f, 1.016001016002f, 1.007905261358f, 1.000000000000f, }; + + /* interpolated 1./sqrt(p) where .5 <= p < 1. */ + static float invsqlook(float a) { + double d = a * (2.f * (float) INVSQ_LOOKUP_SZ) - (float) INVSQ_LOOKUP_SZ; + int i = (int) d; + return INVSQ_LOOKUP[i] + ((float) (d - i)) * (INVSQ_LOOKUP[i + 1] - INVSQ_LOOKUP[i]); + } + + static final int INVSQ2EXP_LOOKUP_MIN = -32; + static final int INVSQ2EXP_LOOKUP_MAX = 32; + static final float[] INVSQ2EXP_LOOKUP = { 65536.f, 46340.95001f, 32768.f, 23170.47501f, 16384.f, 11585.2375f, + 8192.f, 5792.618751f, 4096.f, 2896.309376f, 2048.f, 1448.154688f, 1024.f, 724.0773439f, 512.f, 362.038672f, + 256.f, 181.019336f, 128.f, 90.50966799f, 64.f, 45.254834f, 32.f, 22.627417f, 16.f, 11.3137085f, 8.f, + 5.656854249f, 4.f, 2.828427125f, 2.f, 1.414213562f, 1.f, 0.7071067812f, 0.5f, 0.3535533906f, 0.25f, + 0.1767766953f, 0.125f, 0.08838834765f, 0.0625f, 0.04419417382f, 0.03125f, 0.02209708691f, 0.015625f, + 0.01104854346f, 0.0078125f, 0.005524271728f, 0.00390625f, 0.002762135864f, 0.001953125f, 0.001381067932f, + 0.0009765625f, 0.000690533966f, 0.00048828125f, 0.000345266983f, 0.000244140625f, 0.0001726334915f, + 0.0001220703125f, 8.631674575e-05f, 6.103515625e-05f, 4.315837288e-05f, 3.051757812e-05f, 2.157918644e-05f, + 1.525878906e-05f, }; + + /* interpolated 1./sqrt(p) where .5 <= p < 1. */ + static float invsq2explook(int a) { + return INVSQ2EXP_LOOKUP[a - INVSQ2EXP_LOOKUP_MIN]; + } + + static final int FROMdB_LOOKUP_SZ = 35; + static final int FROMdB2_LOOKUP_SZ = 32; + static final int FROMdB_SHIFT = 5; + static final int FROMdB2_SHIFT = 3; + static final int FROMdB2_MASK = 31; + static final float[] FROMdB_LOOKUP = { 1.f, 0.6309573445f, 0.3981071706f, 0.2511886432f, 0.1584893192f, 0.1f, + 0.06309573445f, 0.03981071706f, 0.02511886432f, 0.01584893192f, 0.01f, 0.006309573445f, 0.003981071706f, + 0.002511886432f, 0.001584893192f, 0.001f, 0.0006309573445f, 0.0003981071706f, 0.0002511886432f, + 0.0001584893192f, 0.0001f, 6.309573445e-05f, 3.981071706e-05f, 2.511886432e-05f, 1.584893192e-05f, 1e-05f, + 6.309573445e-06f, 3.981071706e-06f, 2.511886432e-06f, 1.584893192e-06f, 1e-06f, 6.309573445e-07f, + 3.981071706e-07f, 2.511886432e-07f, 1.584893192e-07f, }; + static final float[] FROMdB2_LOOKUP = { 0.9928302478f, 0.9786445908f, 0.9646616199f, 0.9508784391f, 0.9372921937f, + 0.92390007f, 0.9106992942f, 0.8976871324f, 0.8848608897f, 0.8722179097f, 0.8597555737f, 0.8474713009f, + 0.835362547f, 0.8234268041f, 0.8116616003f, 0.8000644989f, 0.7886330981f, 0.7773650302f, 0.7662579617f, + 0.755309592f, 0.7445176537f, 0.7338799116f, 0.7233941627f, 0.7130582353f, 0.7028699885f, 0.6928273125f, + 0.6829281272f, 0.6731703824f, 0.6635520573f, 0.6540711597f, 0.6447257262f, 0.6355138211f, }; + + /* interpolated lookup based fromdB function, domain -140dB to 0dB only */ + static float fromdBlook(float a) { + int i = (int) (a * ((float) (-(1 << FROMdB2_SHIFT)))); + return (i < 0) ? 1.f + : ((i >= (FROMdB_LOOKUP_SZ << FROMdB_SHIFT)) ? 0.f + : FROMdB_LOOKUP[i >>> FROMdB_SHIFT] * FROMdB2_LOOKUP[i & FROMdB2_MASK]); + } + +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Lpc.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Lpc.java new file mode 100644 index 0000000..4160c50 --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Lpc.java @@ -0,0 +1,185 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class Lpc { + // en/decode lookups + Drft fft = new Drft();; + + int ln; + int m; + + // Autocorrelation LPC coeff generation algorithm invented by + // N. Levinson in 1947, modified by J. Durbin in 1959. + + // Input : n elements of time doamin data + // Output: m lpc coefficients, excitation energy + + static float lpc_from_data(float[] data, float[] lpc, int n, int m) { + float[] aut = new float[m + 1]; + float error; + int i, j; + + // autocorrelation, p+1 lag coefficients + + j = m + 1; + while (j-- != 0) { + float d = 0; + for (i = j; i < n; i++) + d += data[i] * data[i - j]; + aut[j] = d; + } + + // Generate lpc coefficients from autocorr values + + error = aut[0]; + /* + * if(error==0){ for(int k=0; k + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +/* + function: LSP (also called LSF) conversion routines + + The LSP generation code is taken (with minimal modification) from + "On the Computation of the LSP Frequencies" by Joseph Rothweiler + , available at: + + http://www2.xtdl.com/~rothwlr/lsfpaper/lsfpage.html + ********************************************************************/ + +class Lsp { + + static final float M_PI = (float) (3.1415926539); + + static void lsp_to_curve(float[] curve, int[] map, int n, int ln, float[] lsp, int m, float amp, float ampoffset) { + int i; + float wdel = M_PI / ln; + for (i = 0; i < m; i++) + lsp[i] = Lookup.coslook(lsp[i]); + int m2 = (m / 2) * 2; + + i = 0; + while (i < n) { + int k = map[i]; + float p = .7071067812f; + float q = .7071067812f; + float w = Lookup.coslook(wdel * k); + + for (int j = 0; j < m2; j += 2) { + q *= lsp[j] - w; + p *= lsp[j + 1] - w; + } + + if ((m & 1) != 0) { + /* odd order filter; slightly assymetric */ + /* the last coefficient */ + q *= lsp[m - 1] - w; + q *= q; + p *= p * (1.f - w * w); + } else { + /* even order filter; still symmetric */ + q *= q * (1.f + w); + p *= p * (1.f - w); + } + + // q=frexp(p+q,&qexp); + q = p + q; + int hx = Float.floatToIntBits(q); + int ix = 0x7fffffff & hx; + int qexp = 0; + + if (ix >= 0x7f800000 || (ix == 0)) { + // 0,inf,nan + } else { + if (ix < 0x00800000) { // subnormal + q *= 3.3554432000e+07; // 0x4c000000 + hx = Float.floatToIntBits(q); + ix = 0x7fffffff & hx; + qexp = -25; + } + qexp += ((ix >>> 23) - 126); + hx = (hx & 0x807fffff) | 0x3f000000; + q = Float.intBitsToFloat(hx); + } + + q = Lookup.fromdBlook(amp * Lookup.invsqlook(q) * Lookup.invsq2explook(qexp + m) - ampoffset); + + do { + curve[i++] *= q; + } while (i < n && map[i] == k); + + } + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Mapping0.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Mapping0.java new file mode 100644 index 0000000..005562c --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Mapping0.java @@ -0,0 +1,361 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class Mapping0 extends FuncMapping { + static int seq = 0; + + void free_info(Object imap) { + }; + + void free_look(Object imap) { + } + + Object look(DspState vd, InfoMode vm, Object m) { + // System.err.println("Mapping0.look"); + Info vi = vd.vi; + LookMapping0 look = new LookMapping0(); + InfoMapping0 info = look.map = (InfoMapping0) m; + look.mode = vm; + + look.time_look = new Object[info.submaps]; + look.floor_look = new Object[info.submaps]; + look.residue_look = new Object[info.submaps]; + + look.time_func = new FuncTime[info.submaps]; + look.floor_func = new FuncFloor[info.submaps]; + look.residue_func = new FuncResidue[info.submaps]; + + for (int i = 0; i < info.submaps; i++) { + int timenum = info.timesubmap[i]; + int floornum = info.floorsubmap[i]; + int resnum = info.residuesubmap[i]; + + look.time_func[i] = FuncTime.time_P[vi.time_type[timenum]]; + look.time_look[i] = look.time_func[i].look(vd, vm, vi.time_param[timenum]); + look.floor_func[i] = FuncFloor.floor_P[vi.floor_type[floornum]]; + look.floor_look[i] = look.floor_func[i].look(vd, vm, vi.floor_param[floornum]); + look.residue_func[i] = FuncResidue.residue_P[vi.residue_type[resnum]]; + look.residue_look[i] = look.residue_func[i].look(vd, vm, vi.residue_param[resnum]); + + } + + if (vi.psys != 0 && vd.analysisp != 0) { + // ?? + } + + look.ch = vi.channels; + + return (look); + } + + void pack(Info vi, Object imap, Buffer opb) { + InfoMapping0 info = (InfoMapping0) imap; + + /* + * another 'we meant to do it this way' hack... up to beta 4, we packed 4 binary + * zeros here to signify one submapping in use. We now redefine that to mean + * four bitflags that indicate use of deeper features; bit0:submappings, + * bit1:coupling, bit2,3:reserved. This is backward compatable with all actual + * uses of the beta code. + */ + + if (info.submaps > 1) { + opb.write(1, 1); + opb.write(info.submaps - 1, 4); + } else { + opb.write(0, 1); + } + + if (info.coupling_steps > 0) { + opb.write(1, 1); + opb.write(info.coupling_steps - 1, 8); + for (int i = 0; i < info.coupling_steps; i++) { + opb.write(info.coupling_mag[i], Util.ilog2(vi.channels)); + opb.write(info.coupling_ang[i], Util.ilog2(vi.channels)); + } + } else { + opb.write(0, 1); + } + + opb.write(0, 2); /* 2,3:reserved */ + + /* we don't write the channel submappings if we only have one... */ + if (info.submaps > 1) { + for (int i = 0; i < vi.channels; i++) + opb.write(info.chmuxlist[i], 4); + } + for (int i = 0; i < info.submaps; i++) { + opb.write(info.timesubmap[i], 8); + opb.write(info.floorsubmap[i], 8); + opb.write(info.residuesubmap[i], 8); + } + } + + // also responsible for range checking + Object unpack(Info vi, Buffer opb) { + InfoMapping0 info = new InfoMapping0(); + + if (opb.read(1) != 0) { + info.submaps = opb.read(4) + 1; + } else { + info.submaps = 1; + } + + if (opb.read(1) != 0) { + info.coupling_steps = opb.read(8) + 1; + + for (int i = 0; i < info.coupling_steps; i++) { + int testM = info.coupling_mag[i] = opb.read(Util.ilog2(vi.channels)); + int testA = info.coupling_ang[i] = opb.read(Util.ilog2(vi.channels)); + + if (testM < 0 || testA < 0 || testM == testA || testM >= vi.channels || testA >= vi.channels) { + // goto err_out; + info.free(); + return (null); + } + } + } + + if (opb.read(2) > 0) { /* 2,3:reserved */ + info.free(); + return (null); + } + + if (info.submaps > 1) { + for (int i = 0; i < vi.channels; i++) { + info.chmuxlist[i] = opb.read(4); + if (info.chmuxlist[i] >= info.submaps) { + info.free(); + return (null); + } + } + } + + for (int i = 0; i < info.submaps; i++) { + info.timesubmap[i] = opb.read(8); + if (info.timesubmap[i] >= vi.times) { + info.free(); + return (null); + } + info.floorsubmap[i] = opb.read(8); + if (info.floorsubmap[i] >= vi.floors) { + info.free(); + return (null); + } + info.residuesubmap[i] = opb.read(8); + if (info.residuesubmap[i] >= vi.residues) { + info.free(); + return (null); + } + } + return info; + } + + float[][] pcmbundle = null; + int[] zerobundle = null; + int[] nonzero = null; + Object[] floormemo = null; + + synchronized int inverse(Block vb, Object l) { + DspState vd = vb.vd; + Info vi = vd.vi; + LookMapping0 look = (LookMapping0) l; + InfoMapping0 info = look.map; + InfoMode mode = look.mode; + int n = vb.pcmend = vi.blocksizes[vb.W]; + + float[] window = vd.window[vb.W][vb.lW][vb.nW][mode.windowtype]; + if (pcmbundle == null || pcmbundle.length < vi.channels) { + pcmbundle = new float[vi.channels][]; + nonzero = new int[vi.channels]; + zerobundle = new int[vi.channels]; + floormemo = new Object[vi.channels]; + } + + // time domain information decode (note that applying the + // information would have to happen later; we'll probably add a + // function entry to the harness for that later + // NOT IMPLEMENTED + + // recover the spectral envelope; store it in the PCM vector for now + for (int i = 0; i < vi.channels; i++) { + float[] pcm = vb.pcm[i]; + int submap = info.chmuxlist[i]; + + floormemo[i] = look.floor_func[submap].inverse1(vb, look.floor_look[submap], floormemo[i]); + if (floormemo[i] != null) { + nonzero[i] = 1; + } else { + nonzero[i] = 0; + } + for (int j = 0; j < n / 2; j++) { + pcm[j] = 0; + } + + } + + for (int i = 0; i < info.coupling_steps; i++) { + if (nonzero[info.coupling_mag[i]] != 0 || nonzero[info.coupling_ang[i]] != 0) { + nonzero[info.coupling_mag[i]] = 1; + nonzero[info.coupling_ang[i]] = 1; + } + } + + // recover the residue, apply directly to the spectral envelope + + for (int i = 0; i < info.submaps; i++) { + int ch_in_bundle = 0; + for (int j = 0; j < vi.channels; j++) { + if (info.chmuxlist[j] == i) { + if (nonzero[j] != 0) { + zerobundle[ch_in_bundle] = 1; + } else { + zerobundle[ch_in_bundle] = 0; + } + pcmbundle[ch_in_bundle++] = vb.pcm[j]; + } + } + + look.residue_func[i].inverse(vb, look.residue_look[i], pcmbundle, zerobundle, ch_in_bundle); + } + + for (int i = info.coupling_steps - 1; i >= 0; i--) { + float[] pcmM = vb.pcm[info.coupling_mag[i]]; + float[] pcmA = vb.pcm[info.coupling_ang[i]]; + + for (int j = 0; j < n / 2; j++) { + float mag = pcmM[j]; + float ang = pcmA[j]; + + if (mag > 0) { + if (ang > 0) { + pcmM[j] = mag; + pcmA[j] = mag - ang; + } else { + pcmA[j] = mag; + pcmM[j] = mag + ang; + } + } else { + if (ang > 0) { + pcmM[j] = mag; + pcmA[j] = mag + ang; + } else { + pcmA[j] = mag; + pcmM[j] = mag - ang; + } + } + } + } + + // /* compute and apply spectral envelope */ + + for (int i = 0; i < vi.channels; i++) { + float[] pcm = vb.pcm[i]; + int submap = info.chmuxlist[i]; + look.floor_func[submap].inverse2(vb, look.floor_look[submap], floormemo[i], pcm); + } + + // transform the PCM data; takes PCM vector, vb; modifies PCM vector + // only MDCT right now.... + + for (int i = 0; i < vi.channels; i++) { + float[] pcm = vb.pcm[i]; + // _analysis_output("out",seq+i,pcm,n/2,0,0); + ((Mdct) vd.transform[vb.W][0]).backward(pcm, pcm); + } + + // now apply the decoded pre-window time information + // NOT IMPLEMENTED + + // window the data + for (int i = 0; i < vi.channels; i++) { + float[] pcm = vb.pcm[i]; + if (nonzero[i] != 0) { + for (int j = 0; j < n; j++) { + pcm[j] *= window[j]; + } + } else { + for (int j = 0; j < n; j++) { + pcm[j] = 0.f; + } + } + } + + // now apply the decoded post-window time information + // NOT IMPLEMENTED + // all done! + return (0); + } + + class InfoMapping0 { + int submaps; // <= 16 + int[] chmuxlist = new int[256]; // up to 256 channels in a Vorbis stream + + int[] timesubmap = new int[16]; // [mux] + int[] floorsubmap = new int[16]; // [mux] submap to floors + int[] residuesubmap = new int[16];// [mux] submap to residue + int[] psysubmap = new int[16]; // [mux]; encode only + + int coupling_steps; + int[] coupling_mag = new int[256]; + int[] coupling_ang = new int[256]; + + void free() { + chmuxlist = null; + timesubmap = null; + floorsubmap = null; + residuesubmap = null; + psysubmap = null; + + coupling_mag = null; + coupling_ang = null; + } + } + + class LookMapping0 { + InfoMode mode; + InfoMapping0 map; + Object[] time_look; + Object[] floor_look; + Object[] floor_state; + Object[] residue_look; + PsyLook[] psy_look; + + FuncTime[] time_func; + FuncFloor[] floor_func; + FuncResidue[] residue_func; + + int ch; + float[][] decay; + int lastframe; // if a different mode is called, we need to + // invalidate decay and floor state + } + +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Mdct.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Mdct.java new file mode 100644 index 0000000..c2b29fb --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Mdct.java @@ -0,0 +1,249 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class Mdct { + + int n; + int log2n; + + float[] trig; + int[] bitrev; + + float scale; + + void init(int n) { + bitrev = new int[n / 4]; + trig = new float[n + n / 4]; + + log2n = (int) Math.rint(Math.log(n) / Math.log(2)); + this.n = n; + + int AE = 0; + int AO = 1; + int BE = AE + n / 2; + int BO = BE + 1; + int CE = BE + n / 2; + int CO = CE + 1; + // trig lookups... + for (int i = 0; i < n / 4; i++) { + trig[AE + i * 2] = (float) Math.cos((Math.PI / n) * (4 * i)); + trig[AO + i * 2] = (float) -Math.sin((Math.PI / n) * (4 * i)); + trig[BE + i * 2] = (float) Math.cos((Math.PI / (2 * n)) * (2 * i + 1)); + trig[BO + i * 2] = (float) Math.sin((Math.PI / (2 * n)) * (2 * i + 1)); + } + for (int i = 0; i < n / 8; i++) { + trig[CE + i * 2] = (float) Math.cos((Math.PI / n) * (4 * i + 2)); + trig[CO + i * 2] = (float) -Math.sin((Math.PI / n) * (4 * i + 2)); + } + + { + int mask = (1 << (log2n - 1)) - 1; + int msb = 1 << (log2n - 2); + for (int i = 0; i < n / 8; i++) { + int acc = 0; + for (int j = 0; msb >>> j != 0; j++) + if (((msb >>> j) & i) != 0) + acc |= 1 << j; + bitrev[i * 2] = ((~acc) & mask); + // bitrev[i*2]=((~acc)&mask)-1; + bitrev[i * 2 + 1] = acc; + } + } + scale = 4.f / n; + } + + void clear() { + } + + void forward(float[] in, float[] out) { + } + + float[] _x = new float[1024]; + float[] _w = new float[1024]; + + synchronized void backward(float[] in, float[] out) { + if (_x.length < n / 2) { + _x = new float[n / 2]; + } + if (_w.length < n / 2) { + _w = new float[n / 2]; + } + float[] x = _x; + float[] w = _w; + int n2 = n >>> 1; + int n4 = n >>> 2; + int n8 = n >>> 3; + + // rotate + step 1 + { + int inO = 1; + int xO = 0; + int A = n2; + + int i; + for (i = 0; i < n8; i++) { + A -= 2; + x[xO++] = -in[inO + 2] * trig[A + 1] - in[inO] * trig[A]; + x[xO++] = in[inO] * trig[A + 1] - in[inO + 2] * trig[A]; + inO += 4; + } + + inO = n2 - 4; + + for (i = 0; i < n8; i++) { + A -= 2; + x[xO++] = in[inO] * trig[A + 1] + in[inO + 2] * trig[A]; + x[xO++] = in[inO] * trig[A] - in[inO + 2] * trig[A + 1]; + inO -= 4; + } + } + + float[] xxx = mdct_kernel(x, w, n, n2, n4, n8); + int xx = 0; + + // step 8 + + { + int B = n2; + int o1 = n4, o2 = o1 - 1; + int o3 = n4 + n2, o4 = o3 - 1; + + for (int i = 0; i < n4; i++) { + float temp1 = (xxx[xx] * trig[B + 1] - xxx[xx + 1] * trig[B]); + float temp2 = -(xxx[xx] * trig[B] + xxx[xx + 1] * trig[B + 1]); + + out[o1] = -temp1; + out[o2] = temp1; + out[o3] = temp2; + out[o4] = temp2; + + o1++; + o2--; + o3++; + o4--; + xx += 2; + B += 2; + } + } + } + + private float[] mdct_kernel(float[] x, float[] w, int n, int n2, int n4, int n8) { + // step 2 + + int xA = n4; + int xB = 0; + int w2 = n4; + int A = n2; + + for (int i = 0; i < n4;) { + float x0 = x[xA] - x[xB]; + float x1; + w[w2 + i] = x[xA++] + x[xB++]; + + x1 = x[xA] - x[xB]; + A -= 4; + + w[i++] = x0 * trig[A] + x1 * trig[A + 1]; + w[i] = x1 * trig[A] - x0 * trig[A + 1]; + + w[w2 + i] = x[xA++] + x[xB++]; + i++; + } + + // step 3 + + { + for (int i = 0; i < log2n - 3; i++) { + int k0 = n >>> (i + 2); + int k1 = 1 << (i + 3); + int wbase = n2 - 2; + + A = 0; + float[] temp; + + for (int r = 0; r < (k0 >>> 2); r++) { + int w1 = wbase; + w2 = w1 - (k0 >> 1); + float AEv = trig[A], wA; + float AOv = trig[A + 1], wB; + wbase -= 2; + + k0++; + for (int s = 0; s < (2 << i); s++) { + wB = w[w1] - w[w2]; + x[w1] = w[w1] + w[w2]; + + wA = w[++w1] - w[++w2]; + x[w1] = w[w1] + w[w2]; + + x[w2] = wA * AEv - wB * AOv; + x[w2 - 1] = wB * AEv + wA * AOv; + + w1 -= k0; + w2 -= k0; + } + k0--; + A += k1; + } + + temp = w; + w = x; + x = temp; + } + } + + // step 4, 5, 6, 7 + { + int C = n; + int bit = 0; + int x1 = 0; + int x2 = n2 - 1; + + for (int i = 0; i < n8; i++) { + int t1 = bitrev[bit++]; + int t2 = bitrev[bit++]; + + float wA = w[t1] - w[t2 + 1]; + float wB = w[t1 - 1] + w[t2]; + float wC = w[t1] + w[t2 + 1]; + float wD = w[t1 - 1] - w[t2]; + + float wACE = wA * trig[C]; + float wBCE = wB * trig[C++]; + float wACO = wA * trig[C]; + float wBCO = wB * trig[C++]; + + x[x1++] = (wC + wACO + wBCE) * .5f; + x[x2--] = (-wD + wBCO - wACE) * .5f; + x[x1++] = (wD + wBCO - wACE) * .5f; + x[x2--] = (wC - wACO - wBCE) * .5f; + } + } + return (x); + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/PsyInfo.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/PsyInfo.java new file mode 100644 index 0000000..906efab --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/PsyInfo.java @@ -0,0 +1,74 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +// psychoacoustic setup +class PsyInfo { + int athp; + int decayp; + int smoothp; + int noisefitp; + int noisefit_subblock; + float noisefit_threshdB; + + float ath_att; + + int tonemaskp; + float[] toneatt_125Hz = new float[5]; + float[] toneatt_250Hz = new float[5]; + float[] toneatt_500Hz = new float[5]; + float[] toneatt_1000Hz = new float[5]; + float[] toneatt_2000Hz = new float[5]; + float[] toneatt_4000Hz = new float[5]; + float[] toneatt_8000Hz = new float[5]; + + int peakattp; + float[] peakatt_125Hz = new float[5]; + float[] peakatt_250Hz = new float[5]; + float[] peakatt_500Hz = new float[5]; + float[] peakatt_1000Hz = new float[5]; + float[] peakatt_2000Hz = new float[5]; + float[] peakatt_4000Hz = new float[5]; + float[] peakatt_8000Hz = new float[5]; + + int noisemaskp; + float[] noiseatt_125Hz = new float[5]; + float[] noiseatt_250Hz = new float[5]; + float[] noiseatt_500Hz = new float[5]; + float[] noiseatt_1000Hz = new float[5]; + float[] noiseatt_2000Hz = new float[5]; + float[] noiseatt_4000Hz = new float[5]; + float[] noiseatt_8000Hz = new float[5]; + + float max_curve_dB; + + float attack_coeff; + float decay_coeff; + + void free() { + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/PsyLook.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/PsyLook.java new file mode 100644 index 0000000..8fee7bf --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/PsyLook.java @@ -0,0 +1,42 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class PsyLook { + int n; + PsyInfo vi; + + float[][][] tonecurves; + float[][] peakatt; + float[][][] noisecurves; + + float[] ath; + int[] octave; + + void init(PsyInfo vi, int n, int rate) { + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Residue0.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Residue0.java new file mode 100644 index 0000000..d919709 --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Residue0.java @@ -0,0 +1,326 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class Residue0 extends FuncResidue { + void pack(Object vr, Buffer opb) { + InfoResidue0 info = (InfoResidue0) vr; + int acc = 0; + opb.write(info.begin, 24); + opb.write(info.end, 24); + + opb.write(info.grouping - 1, 24); /* + * residue vectors to group and code with a partitioned book + */ + opb.write(info.partitions - 1, 6); /* possible partition choices */ + opb.write(info.groupbook, 8); /* group huffman book */ + + /* + * secondstages is a bitmask; as encoding progresses pass by pass, a bitmask of + * one indicates this partition class has bits to write this pass + */ + for (int j = 0; j < info.partitions; j++) { + int i = info.secondstages[j]; + if (Util.ilog(i) > 3) { + /* yes, this is a minor hack due to not thinking ahead */ + opb.write(i, 3); + opb.write(1, 1); + opb.write(i >>> 3, 5); + } else { + opb.write(i, 4); /* trailing zero */ + } + acc += Util.icount(i); + } + for (int j = 0; j < acc; j++) { + opb.write(info.booklist[j], 8); + } + } + + Object unpack(Info vi, Buffer opb) { + int acc = 0; + InfoResidue0 info = new InfoResidue0(); + info.begin = opb.read(24); + info.end = opb.read(24); + info.grouping = opb.read(24) + 1; + info.partitions = opb.read(6) + 1; + info.groupbook = opb.read(8); + + for (int j = 0; j < info.partitions; j++) { + int cascade = opb.read(3); + if (opb.read(1) != 0) { + cascade |= (opb.read(5) << 3); + } + info.secondstages[j] = cascade; + acc += Util.icount(cascade); + } + + for (int j = 0; j < acc; j++) { + info.booklist[j] = opb.read(8); + } + + if (info.groupbook >= vi.books) { + free_info(info); + return (null); + } + + for (int j = 0; j < acc; j++) { + if (info.booklist[j] >= vi.books) { + free_info(info); + return (null); + } + } + return (info); + } + + Object look(DspState vd, InfoMode vm, Object vr) { + InfoResidue0 info = (InfoResidue0) vr; + LookResidue0 look = new LookResidue0(); + int acc = 0; + int dim; + int maxstage = 0; + look.info = info; + look.map = vm.mapping; + + look.parts = info.partitions; + look.fullbooks = vd.fullbooks; + look.phrasebook = vd.fullbooks[info.groupbook]; + + dim = look.phrasebook.dim; + + look.partbooks = new int[look.parts][]; + + for (int j = 0; j < look.parts; j++) { + int i = info.secondstages[j]; + int stages = Util.ilog(i); + if (stages != 0) { + if (stages > maxstage) + maxstage = stages; + look.partbooks[j] = new int[stages]; + for (int k = 0; k < stages; k++) { + if ((i & (1 << k)) != 0) { + look.partbooks[j][k] = info.booklist[acc++]; + } + } + } + } + + look.partvals = (int) Math.rint(Math.pow(look.parts, dim)); + look.stages = maxstage; + look.decodemap = new int[look.partvals][]; + for (int j = 0; j < look.partvals; j++) { + int val = j; + int mult = look.partvals / look.parts; + look.decodemap[j] = new int[dim]; + + for (int k = 0; k < dim; k++) { + int deco = val / mult; + val -= deco * mult; + mult /= look.parts; + look.decodemap[j][k] = deco; + } + } + return (look); + } + + void free_info(Object i) { + } + + void free_look(Object i) { + } + + private static int[][][] _01inverse_partword = new int[2][][]; // _01inverse is synchronized for + + // re-using partword + synchronized static int _01inverse(Block vb, Object vl, float[][] in, int ch, int decodepart) { + int i, j, k, l, s; + LookResidue0 look = (LookResidue0) vl; + InfoResidue0 info = look.info; + + // move all this setup out later + int samples_per_partition = info.grouping; + int partitions_per_word = look.phrasebook.dim; + int n = info.end - info.begin; + + int partvals = n / samples_per_partition; + int partwords = (partvals + partitions_per_word - 1) / partitions_per_word; + + if (_01inverse_partword.length < ch) { + _01inverse_partword = new int[ch][][]; + } + + for (j = 0; j < ch; j++) { + if (_01inverse_partword[j] == null || _01inverse_partword[j].length < partwords) { + _01inverse_partword[j] = new int[partwords][]; + } + } + + for (s = 0; s < look.stages; s++) { + // each loop decodes on partition codeword containing + // partitions_pre_word partitions + for (i = 0, l = 0; i < partvals; l++) { + if (s == 0) { + // fetch the partition word for each channel + for (j = 0; j < ch; j++) { + int temp = look.phrasebook.decode(vb.opb); + if (temp == -1) { + return (0); + } + _01inverse_partword[j][l] = look.decodemap[temp]; + if (_01inverse_partword[j][l] == null) { + return (0); + } + } + } + + // now we decode residual values for the partitions + for (k = 0; k < partitions_per_word && i < partvals; k++, i++) + for (j = 0; j < ch; j++) { + int offset = info.begin + i * samples_per_partition; + int index = _01inverse_partword[j][l][k]; + if ((info.secondstages[index] & (1 << s)) != 0) { + CodeBook stagebook = look.fullbooks[look.partbooks[index][s]]; + if (stagebook != null) { + if (decodepart == 0) { + if (stagebook.decodevs_add(in[j], offset, vb.opb, samples_per_partition) == -1) { + return (0); + } + } else if (decodepart == 1) { + if (stagebook.decodev_add(in[j], offset, vb.opb, samples_per_partition) == -1) { + return (0); + } + } + } + } + } + } + } + return (0); + } + + static int[][] _2inverse_partword = null; + + synchronized static int _2inverse(Block vb, Object vl, float[][] in, int ch) { + int i, k, l, s; + LookResidue0 look = (LookResidue0) vl; + InfoResidue0 info = look.info; + + // move all this setup out later + int samples_per_partition = info.grouping; + int partitions_per_word = look.phrasebook.dim; + int n = info.end - info.begin; + + int partvals = n / samples_per_partition; + int partwords = (partvals + partitions_per_word - 1) / partitions_per_word; + + if (_2inverse_partword == null || _2inverse_partword.length < partwords) { + _2inverse_partword = new int[partwords][]; + } + for (s = 0; s < look.stages; s++) { + for (i = 0, l = 0; i < partvals; l++) { + if (s == 0) { + // fetch the partition word for each channel + int temp = look.phrasebook.decode(vb.opb); + if (temp == -1) { + return (0); + } + _2inverse_partword[l] = look.decodemap[temp]; + if (_2inverse_partword[l] == null) { + return (0); + } + } + + // now we decode residual values for the partitions + for (k = 0; k < partitions_per_word && i < partvals; k++, i++) { + int offset = info.begin + i * samples_per_partition; + int index = _2inverse_partword[l][k]; + if ((info.secondstages[index] & (1 << s)) != 0) { + CodeBook stagebook = look.fullbooks[look.partbooks[index][s]]; + if (stagebook != null) { + if (stagebook.decodevv_add(in, offset, ch, vb.opb, samples_per_partition) == -1) { + return (0); + } + } + } + } + } + } + return (0); + } + + int inverse(Block vb, Object vl, float[][] in, int[] nonzero, int ch) { + int used = 0; + for (int i = 0; i < ch; i++) { + if (nonzero[i] != 0) { + in[used++] = in[i]; + } + } + if (used != 0) + return (_01inverse(vb, vl, in, used, 0)); + else + return (0); + } + + class LookResidue0 { + InfoResidue0 info; + int map; + + int parts; + int stages; + CodeBook[] fullbooks; + CodeBook phrasebook; + int[][] partbooks; + + int partvals; + int[][] decodemap; + + int postbits; + int phrasebits; + int frames; + } + + class InfoResidue0 { + // block-partitioned VQ coded straight residue + int begin; + int end; + + // first stage (lossless partitioning) + int grouping; // group n vectors per partition + int partitions; // possible codebooks for a partition + int groupbook; // huffbook for partitioning + int[] secondstages = new int[64]; // expanded out to pointers in lookup + int[] booklist = new int[256]; // list of second stage books + + // encode-only heuristic settings + float[] entmax = new float[64]; // book entropy threshholds + float[] ampmax = new float[64]; // book amp threshholds + int[] subgrp = new int[64]; // book heuristic subgroup size + int[] blimit = new int[64]; // subgroup position limits + } + +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Residue1.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Residue1.java new file mode 100644 index 0000000..55b0d9a --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Residue1.java @@ -0,0 +1,44 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class Residue1 extends Residue0 { + + int inverse(Block vb, Object vl, float[][] in, int[] nonzero, int ch) { + int used = 0; + for (int i = 0; i < ch; i++) { + if (nonzero[i] != 0) { + in[used++] = in[i]; + } + } + if (used != 0) { + return (_01inverse(vb, vl, in, used, 1)); + } else { + return 0; + } + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Residue2.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Residue2.java new file mode 100644 index 0000000..cb4ec8f --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Residue2.java @@ -0,0 +1,41 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +class Residue2 extends Residue0 { + + int inverse(Block vb, Object vl, float[][] in, int[] nonzero, int ch) { + int i = 0; + for (i = 0; i < ch; i++) + if (nonzero[i] != 0) + break; + if (i == ch) + return (0); /* no nonzero vectors */ + + return (_2inverse(vb, vl, in, ch)); + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/StaticCodeBook.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/StaticCodeBook.java new file mode 100644 index 0000000..d06a2e8 --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/StaticCodeBook.java @@ -0,0 +1,436 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class StaticCodeBook { + int dim; // codebook dimensions (elements per vector) + int entries; // codebook entries + int[] lengthlist; // codeword lengths in bits + + // mapping + int maptype; // 0=none + // 1=implicitly populated values from map column + // 2=listed arbitrary values + + // The below does a linear, single monotonic sequence mapping. + int q_min; // packed 32 bit float; quant value 0 maps to minval + int q_delta; // packed 32 bit float; val 1 - val 0 == delta + int q_quant; // bits: 0 < quant <= 16 + int q_sequencep; // bitflag + + // additional information for log (dB) mapping; the linear mapping + // is assumed to actually be values in dB. encodebias is used to + // assign an error weight to 0 dB. We have two additional flags: + // zeroflag indicates if entry zero is to represent -Inf dB; negflag + // indicates if we're to represent negative linear values in a + // mirror of the positive mapping. + + int[] quantlist; // map == 1: (int)(entries/dim) element column map + // map == 2: list of dim*entries quantized entry vals + + StaticCodeBook() { + } + + int pack(Buffer opb) { + int i; + boolean ordered = false; + + opb.write(0x564342, 24); + opb.write(dim, 16); + opb.write(entries, 24); + + // pack the codewords. There are two packings; length ordered and + // length random. Decide between the two now. + + for (i = 1; i < entries; i++) { + if (lengthlist[i] < lengthlist[i - 1]) + break; + } + if (i == entries) + ordered = true; + + if (ordered) { + // length ordered. We only need to say how many codewords of + // each length. The actual codewords are generated + // deterministically + + int count = 0; + opb.write(1, 1); // ordered + opb.write(lengthlist[0] - 1, 5); // 1 to 32 + + for (i = 1; i < entries; i++) { + int _this = lengthlist[i]; + int _last = lengthlist[i - 1]; + if (_this > _last) { + for (int j = _last; j < _this; j++) { + opb.write(i - count, Util.ilog(entries - count)); + count = i; + } + } + } + opb.write(i - count, Util.ilog(entries - count)); + } else { + // length random. Again, we don't code the codeword itself, just + // the length. This time, though, we have to encode each length + opb.write(0, 1); // unordered + + // algortihmic mapping has use for 'unused entries', which we tag + // here. The algorithmic mapping happens as usual, but the unused + // entry has no codeword. + for (i = 0; i < entries; i++) { + if (lengthlist[i] == 0) + break; + } + + if (i == entries) { + opb.write(0, 1); // no unused entries + for (i = 0; i < entries; i++) { + opb.write(lengthlist[i] - 1, 5); + } + } else { + opb.write(1, 1); // we have unused entries; thus we tag + for (i = 0; i < entries; i++) { + if (lengthlist[i] == 0) { + opb.write(0, 1); + } else { + opb.write(1, 1); + opb.write(lengthlist[i] - 1, 5); + } + } + } + } + + // is the entry number the desired return value, or do we have a + // mapping? If we have a mapping, what type? + opb.write(maptype, 4); + switch (maptype) { + case 0: + // no mapping + break; + case 1: + case 2: + // implicitly populated value mapping + // explicitly populated value mapping + if (quantlist == null) { + // no quantlist? error + return (-1); + } + + // values that define the dequantization + opb.write(q_min, 32); + opb.write(q_delta, 32); + opb.write(q_quant - 1, 4); + opb.write(q_sequencep, 1); + + { + int quantvals = 0; + switch (maptype) { + case 1: + // a single column of (c->entries/c->dim) quantized values for + // building a full value list algorithmically (square lattice) + quantvals = maptype1_quantvals(); + break; + case 2: + // every value (c->entries*c->dim total) specified explicitly + quantvals = entries * dim; + break; + } + + // quantized values + for (i = 0; i < quantvals; i++) { + opb.write(Math.abs(quantlist[i]), q_quant); + } + } + break; + default: + // error case; we don't have any other map types now + return (-1); + } + return (0); + } + + // unpacks a codebook from the packet buffer into the codebook struct, + // readies the codebook auxiliary structures for decode + int unpack(Buffer opb) { + int i; + // memset(s,0,sizeof(static_codebook)); + + // make sure alignment is correct + if (opb.read(24) != 0x564342) { + // goto _eofout; + clear(); + return (-1); + } + + // first the basic parameters + dim = opb.read(16); + entries = opb.read(24); + if (entries == -1) { + // goto _eofout; + clear(); + return (-1); + } + + // codeword ordering.... length ordered or unordered? + switch (opb.read(1)) { + case 0: + // unordered + lengthlist = new int[entries]; + + // allocated but unused entries? + if (opb.read(1) != 0) { + // yes, unused entries + + for (i = 0; i < entries; i++) { + if (opb.read(1) != 0) { + int num = opb.read(5); + if (num == -1) { + // goto _eofout; + clear(); + return (-1); + } + lengthlist[i] = num + 1; + } else { + lengthlist[i] = 0; + } + } + } else { + // all entries used; no tagging + for (i = 0; i < entries; i++) { + int num = opb.read(5); + if (num == -1) { + // goto _eofout; + clear(); + return (-1); + } + lengthlist[i] = num + 1; + } + } + break; + case 1: + // ordered + { + int length = opb.read(5) + 1; + lengthlist = new int[entries]; + + for (i = 0; i < entries;) { + int num = opb.read(Util.ilog(entries - i)); + if (num == -1) { + // goto _eofout; + clear(); + return (-1); + } + for (int j = 0; j < num; j++, i++) { + lengthlist[i] = length; + } + length++; + } + } + break; + default: + // EOF + return (-1); + } + + // Do we have a mapping to unpack? + switch ((maptype = opb.read(4))) { + case 0: + // no mapping + break; + case 1: + case 2: + // implicitly populated value mapping + // explicitly populated value mapping + q_min = opb.read(32); + q_delta = opb.read(32); + q_quant = opb.read(4) + 1; + q_sequencep = opb.read(1); + + { + int quantvals = 0; + switch (maptype) { + case 1: + quantvals = maptype1_quantvals(); + break; + case 2: + quantvals = entries * dim; + break; + } + + // quantized values + quantlist = new int[quantvals]; + for (i = 0; i < quantvals; i++) { + quantlist[i] = opb.read(q_quant); + } + if (quantlist[quantvals - 1] == -1) { + // goto _eofout; + clear(); + return (-1); + } + } + break; + default: + // goto _eofout; + clear(); + return (-1); + } + // all set + return (0); + // _errout: + // _eofout: + // vorbis_staticbook_clear(s); + // return(-1); + } + + // there might be a straightforward one-line way to do the below + // that's portable and totally safe against roundoff, but I haven't + // thought of it. Therefore, we opt on the side of caution + private int maptype1_quantvals() { + int vals = (int) (Math.floor(Math.pow(entries, 1. / dim))); + + // the above *should* be reliable, but we'll not assume that FP is + // ever reliable when bitstream sync is at stake; verify via integer + // means that vals really is the greatest value of dim for which + // vals^b->bim <= b->entries + // treat the above as an initial guess + while (true) { + int acc = 1; + int acc1 = 1; + for (int i = 0; i < dim; i++) { + acc *= vals; + acc1 *= vals + 1; + } + if (acc <= entries && acc1 > entries) { + return (vals); + } else { + if (acc > entries) { + vals--; + } else { + vals++; + } + } + } + } + + void clear() { + } + + // unpack the quantized list of values for encode/decode + // we need to deal with two map types: in map type 1, the values are + // generated algorithmically (each column of the vector counts through + // the values in the quant vector). in map type 2, all the values came + // in in an explicit list. Both value lists must be unpacked + float[] unquantize() { + + if (maptype == 1 || maptype == 2) { + int quantvals; + float mindel = float32_unpack(q_min); + float delta = float32_unpack(q_delta); + float[] r = new float[entries * dim]; + + // maptype 1 and 2 both use a quantized value vector, but + // different sizes + switch (maptype) { + case 1: + // most of the time, entries%dimensions == 0, but we need to be + // well defined. We define that the possible vales at each + // scalar is values == entries/dim. If entries%dim != 0, we'll + // have 'too few' values (values*dim "+val+" | ");} + val = Math.abs(val) * delta + mindel + last; + if (q_sequencep != 0) + last = val; + r[j * dim + k] = val; + // if((j*dim+k)==0){System.err.println(" $ r[0] -> "+r[0]+" | ");} + } + } + // System.err.println("\nr[0]="+r[0]); + } + return (r); + } + return (null); + } + + // 32 bit float (not IEEE; nonnormalized mantissa + + // biased exponent) : neeeeeee eeemmmmm mmmmmmmm mmmmmmmm + // Why not IEEE? It's just not that important here. + + static final int VQ_FEXP = 10; + static final int VQ_FMAN = 21; + static final int VQ_FEXP_BIAS = 768; // bias toward values smaller than 1. + + // doesn't currently guard under/overflow + static long float32_pack(float val) { + int sign = 0; + int exp; + int mant; + if (val < 0) { + sign = 0x80000000; + val = -val; + } + exp = (int) Math.floor(Math.log(val) / Math.log(2)); + mant = (int) Math.rint(Math.pow(val, (VQ_FMAN - 1) - exp)); + exp = (exp + VQ_FEXP_BIAS) << VQ_FMAN; + return (sign | exp | mant); + } + + static float float32_unpack(int val) { + float mant = val & 0x1fffff; + float exp = (val & 0x7fe00000) >>> VQ_FMAN; + if ((val & 0x80000000) != 0) + mant = -mant; + return (ldexp(mant, ((int) exp) - (VQ_FMAN - 1) - VQ_FEXP_BIAS)); + } + + static float ldexp(float foo, int e) { + return (float) (foo * Math.pow(2, e)); + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Time0.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Time0.java new file mode 100644 index 0000000..3e9e81f --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Time0.java @@ -0,0 +1,52 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +class Time0 extends FuncTime { + void pack(Object i, Buffer opb) { + } + + Object unpack(Info vi, Buffer opb) { + return ""; + } + + Object look(DspState vd, InfoMode mi, Object i) { + return ""; + } + + void free_info(Object i) { + } + + void free_look(Object i) { + } + + int inverse(Block vb, Object i, float[] in, float[] out) { + return 0; + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Util.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Util.java new file mode 100644 index 0000000..9efb0a9 --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/Util.java @@ -0,0 +1,30 @@ +package com.jcraft.jorbis; + +class Util { + static int ilog(int v) { + int ret = 0; + while (v != 0) { + ret++; + v >>>= 1; + } + return (ret); + } + + static int ilog2(int v) { + int ret = 0; + while (v > 1) { + ret++; + v >>>= 1; + } + return (ret); + } + + static int icount(int v) { + int ret = 0; + while (v != 0) { + ret += (v & 1); + v >>>= 1; + } + return (ret); + } +} diff --git a/sources/wasm-gc-teavm/java/com/jcraft/jorbis/VorbisFile.java b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/VorbisFile.java new file mode 100644 index 0000000..4222740 --- /dev/null +++ b/sources/wasm-gc-teavm/java/com/jcraft/jorbis/VorbisFile.java @@ -0,0 +1,1348 @@ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */ +/* JOrbis + * Copyright (C) 2000 ymnk, JCraft,Inc. + * + * Written by: 2000 ymnk + * + * Many thanks to + * Monty and + * The XIPHOPHORUS Company http://www.xiph.org/ . + * JOrbis has been based on their awesome works, Vorbis codec. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License + * as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +package com.jcraft.jorbis; + +import com.jcraft.jogg.*; + +import java.io.InputStream; +import java.io.IOException; + +public class VorbisFile { + static final int CHUNKSIZE = 8500; + static final int SEEK_SET = 0; + static final int SEEK_CUR = 1; + static final int SEEK_END = 2; + + static final int OV_FALSE = -1; + static final int OV_EOF = -2; + static final int OV_HOLE = -3; + + static final int OV_EREAD = -128; + static final int OV_EFAULT = -129; + static final int OV_EIMPL = -130; + static final int OV_EINVAL = -131; + static final int OV_ENOTVORBIS = -132; + static final int OV_EBADHEADER = -133; + static final int OV_EVERSION = -134; + static final int OV_ENOTAUDIO = -135; + static final int OV_EBADPACKET = -136; + static final int OV_EBADLINK = -137; + static final int OV_ENOSEEK = -138; + + InputStream datasource; + boolean seekable = false; + long offset; + long end; + + SyncState oy = new SyncState(); + + int links; + long[] offsets; + long[] dataoffsets; + int[] serialnos; + long[] pcmlengths; + Info[] vi; + Comment[] vc; + + // Decoding working state local storage + long pcm_offset; + boolean decode_ready = false; + + int current_serialno; + int current_link; + + float bittrack; + float samptrack; + + StreamState os = new StreamState(); // take physical pages, weld into a logical + // stream of packets + DspState vd = new DspState(); // central working state for + // the packet->PCM decoder + Block vb = new Block(vd); // local working space for packet->PCM decode + + // ov_callbacks callbacks; + + public VorbisFile(String file) throws JOrbisException { + super(); + InputStream is = null; + try { + is = new SeekableInputStream(file); + int ret = open(is, null, 0); + if (ret == -1) { + throw new JOrbisException("VorbisFile: open return -1"); + } + } catch (Exception e) { + throw new JOrbisException("VorbisFile: " + e.toString()); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + public VorbisFile(InputStream is, byte[] initial, int ibytes) throws JOrbisException { + super(); + int ret = open(is, initial, ibytes); + if (ret == -1) { + } + } + + private int get_data() { + int index = oy.buffer(CHUNKSIZE); + byte[] buffer = oy.data; + int bytes = 0; + try { + bytes = datasource.read(buffer, index, CHUNKSIZE); + } catch (Exception e) { + return OV_EREAD; + } + oy.wrote(bytes); + if (bytes == -1) { + bytes = 0; + } + return bytes; + } + + private void seek_helper(long offst) { + fseek(datasource, offst, SEEK_SET); + this.offset = offst; + oy.reset(); + } + + private int get_next_page(Page page, long boundary) { + if (boundary > 0) + boundary += offset; + while (true) { + int more; + if (boundary > 0 && offset >= boundary) + return OV_FALSE; + more = oy.pageseek(page); + if (more < 0) { + offset -= more; + } else { + if (more == 0) { + if (boundary == 0) + return OV_FALSE; + int ret = get_data(); + if (ret == 0) + return OV_EOF; + if (ret < 0) + return OV_EREAD; + } else { + int ret = (int) offset; // !!! + offset += more; + return ret; + } + } + } + } + + private int get_prev_page(Page page) throws JOrbisException { + long begin = offset; // !!! + int ret; + int offst = -1; + while (offst == -1) { + begin -= CHUNKSIZE; + if (begin < 0) + begin = 0; + seek_helper(begin); + while (offset < begin + CHUNKSIZE) { + ret = get_next_page(page, begin + CHUNKSIZE - offset); + if (ret == OV_EREAD) { + return OV_EREAD; + } + if (ret < 0) { + if (offst == -1) + throw new JOrbisException(); + break; + } else { + offst = ret; + } + } + } + seek_helper(offst); // !!! + ret = get_next_page(page, CHUNKSIZE); + if (ret < 0) { + return OV_EFAULT; + } + return offst; + } + + int bisect_forward_serialno(long begin, long searched, long end, int currentno, int m) { + long endsearched = end; + long next = end; + Page page = new Page(); + int ret; + + while (searched < endsearched) { + long bisect; + if (endsearched - searched < CHUNKSIZE) { + bisect = searched; + } else { + bisect = (searched + endsearched) / 2; + } + + seek_helper(bisect); + ret = get_next_page(page, -1); + if (ret == OV_EREAD) + return OV_EREAD; + if (ret < 0 || page.serialno() != currentno) { + endsearched = bisect; + if (ret >= 0) + next = ret; + } else { + searched = ret + page.header_len + page.body_len; + } + } + seek_helper(next); + ret = get_next_page(page, -1); + if (ret == OV_EREAD) + return OV_EREAD; + + if (searched >= end || ret == -1) { + links = m + 1; + offsets = new long[m + 2]; + offsets[m + 1] = searched; + } else { + ret = bisect_forward_serialno(next, offset, end, page.serialno(), m + 1); + if (ret == OV_EREAD) + return OV_EREAD; + } + offsets[m] = begin; + return 0; + } + + // uses the local ogg_stream storage in vf; this is important for + // non-streaming input sources + int fetch_headers(Info vi, Comment vc, int[] serialno, Page og_ptr) { + Page og = new Page(); + Packet op = new Packet(); + int ret; + + if (og_ptr == null) { + ret = get_next_page(og, CHUNKSIZE); + if (ret == OV_EREAD) + return OV_EREAD; + if (ret < 0) + return OV_ENOTVORBIS; + og_ptr = og; + } + + if (serialno != null) + serialno[0] = og_ptr.serialno(); + + os.init(og_ptr.serialno()); + + // extract the initial header from the first page and verify that the + // Ogg bitstream is in fact Vorbis data + + vi.init(); + vc.init(); + + int i = 0; + while (i < 3) { + os.pagein(og_ptr); + while (i < 3) { + int result = os.packetout(op); + if (result == 0) + break; + if (result == -1) { + vi.clear(); + vc.clear(); + os.clear(); + return -1; + } + if (vi.synthesis_headerin(vc, op) != 0) { + vi.clear(); + vc.clear(); + os.clear(); + return -1; + } + i++; + } + if (i < 3) + if (get_next_page(og_ptr, 1) < 0) { + vi.clear(); + vc.clear(); + os.clear(); + return -1; + } + } + return 0; + } + + // last step of the OggVorbis_File initialization; get all the + // vorbis_info structs and PCM positions. Only called by the seekable + // initialization (local stream storage is hacked slightly; pay + // attention to how that's done) + void prefetch_all_headers(Info first_i, Comment first_c, int dataoffset) throws JOrbisException { + Page og = new Page(); + int ret; + + vi = new Info[links]; + vc = new Comment[links]; + dataoffsets = new long[links]; + pcmlengths = new long[links]; + serialnos = new int[links]; + + for (int i = 0; i < links; i++) { + if (first_i != null && first_c != null && i == 0) { + // we already grabbed the initial header earlier. This just + // saves the waste of grabbing it again + vi[i] = first_i; + vc[i] = first_c; + dataoffsets[i] = dataoffset; + } else { + // seek to the location of the initial header + seek_helper(offsets[i]); // !!! + vi[i] = new Info(); + vc[i] = new Comment(); + if (fetch_headers(vi[i], vc[i], null, null) == -1) { + dataoffsets[i] = -1; + } else { + dataoffsets[i] = offset; + os.clear(); + } + } + + // get the serial number and PCM length of this link. To do this, + // get the last page of the stream + { + long end = offsets[i + 1]; // !!! + seek_helper(end); + + while (true) { + ret = get_prev_page(og); + if (ret == -1) { + // this should not be possible + vi[i].clear(); + vc[i].clear(); + break; + } + if (og.granulepos() != -1) { + serialnos[i] = og.serialno(); + pcmlengths[i] = og.granulepos(); + break; + } + } + } + } + } + + private int make_decode_ready() { + if (decode_ready) + System.exit(1); + vd.synthesis_init(vi[0]); + vb.init(vd); + decode_ready = true; + return (0); + } + + int open_seekable() throws JOrbisException { + Info initial_i = new Info(); + Comment initial_c = new Comment(); + int serialno; + long end; + int ret; + int dataoffset; + Page og = new Page(); + // is this even vorbis...? + int[] foo = new int[1]; + ret = fetch_headers(initial_i, initial_c, foo, null); + serialno = foo[0]; + dataoffset = (int) offset; // !! + os.clear(); + if (ret == -1) + return (-1); + if (ret < 0) + return (ret); + // we can seek, so set out learning all about this file + seekable = true; + fseek(datasource, 0, SEEK_END); + offset = ftell(datasource); + end = offset; + // We get the offset for the last page of the physical bitstream. + // Most OggVorbis files will contain a single logical bitstream + end = get_prev_page(og); + // moer than one logical bitstream? + if (og.serialno() != serialno) { + // Chained bitstream. Bisect-search each logical bitstream + // section. Do so based on serial number only + if (bisect_forward_serialno(0, 0, end + 1, serialno, 0) < 0) { + clear(); + return OV_EREAD; + } + } else { + // Only one logical bitstream + if (bisect_forward_serialno(0, end, end + 1, serialno, 0) < 0) { + clear(); + return OV_EREAD; + } + } + prefetch_all_headers(initial_i, initial_c, dataoffset); + return 0; + } + + int open_nonseekable() { + // we cannot seek. Set up a 'single' (current) logical bitstream entry + links = 1; + vi = new Info[links]; + vi[0] = new Info(); // ?? + vc = new Comment[links]; + vc[0] = new Comment(); // ?? bug? + + // Try to fetch the headers, maintaining all the storage + int[] foo = new int[1]; + if (fetch_headers(vi[0], vc[0], foo, null) == -1) + return (-1); + current_serialno = foo[0]; + make_decode_ready(); + return 0; + } + + // clear out the current logical bitstream decoder + void decode_clear() { + os.clear(); + vd.clear(); + vb.clear(); + decode_ready = false; + bittrack = 0.f; + samptrack = 0.f; + } + + // fetch and process a packet. Handles the case where we're at a + // bitstream boundary and dumps the decoding machine. If the decoding + // machine is unloaded, it loads it. It also keeps pcm_offset up to + // date (seek and read both use this. seek uses a special hack with + // readp). + // + // return: -1) hole in the data (lost packet) + // 0) need more date (only if readp==0)/eof + // 1) got a packet + + int process_packet(int readp) { + Page og = new Page(); + + // handle one packet. Try to fetch it from current stream state + // extract packets from page + while (true) { + // process a packet if we can. If the machine isn't loaded, + // neither is a page + if (decode_ready) { + Packet op = new Packet(); + int result = os.packetout(op); + long granulepos; + // if(result==-1)return(-1); // hole in the data. For now, swallow + // and go. We'll need to add a real + // error code in a bit. + if (result > 0) { + // got a packet. process it + granulepos = op.granulepos; + if (vb.synthesis(op) == 0) { // lazy check for lazy + // header handling. The + // header packets aren't + // audio, so if/when we + // submit them, + // vorbis_synthesis will + // reject them + // suck in the synthesis data and track bitrate + { + int oldsamples = vd.synthesis_pcmout(null, null); + vd.synthesis_blockin(vb); + samptrack += vd.synthesis_pcmout(null, null) - oldsamples; + bittrack += op.bytes * 8; + } + + // update the pcm offset. + if (granulepos != -1 && op.e_o_s == 0) { + int link = (seekable ? current_link : 0); + int samples; + // this packet has a pcm_offset on it (the last packet + // completed on a page carries the offset) After processing + // (above), we know the pcm position of the *last* sample + // ready to be returned. Find the offset of the *first* + // + // As an aside, this trick is inaccurate if we begin + // reading anew right at the last page; the end-of-stream + // granulepos declares the last frame in the stream, and the + // last packet of the last page may be a partial frame. + // So, we need a previous granulepos from an in-sequence page + // to have a reference point. Thus the !op.e_o_s clause above + + samples = vd.synthesis_pcmout(null, null); + granulepos -= samples; + for (int i = 0; i < link; i++) { + granulepos += pcmlengths[i]; + } + pcm_offset = granulepos; + } + return (1); + } + } + } + + if (readp == 0) + return (0); + if (get_next_page(og, -1) < 0) + return (0); // eof. leave unitialized + + // bitrate tracking; add the header's bytes here, the body bytes + // are done by packet above + bittrack += og.header_len * 8; + + // has our decoding just traversed a bitstream boundary? + if (decode_ready) { + if (current_serialno != og.serialno()) { + decode_clear(); + } + } + + // Do we need to load a new machine before submitting the page? + // This is different in the seekable and non-seekable cases. + // + // In the seekable case, we already have all the header + // information loaded and cached; we just initialize the machine + // with it and continue on our merry way. + // + // In the non-seekable (streaming) case, we'll only be at a + // boundary if we just left the previous logical bitstream and + // we're now nominally at the header of the next bitstream + + if (!decode_ready) { + int i; + if (seekable) { + current_serialno = og.serialno(); + + // match the serialno to bitstream section. We use this rather than + // offset positions to avoid problems near logical bitstream + // boundaries + for (i = 0; i < links; i++) { + if (serialnos[i] == current_serialno) + break; + } + if (i == links) + return (-1); // sign of a bogus stream. error out, + // leave machine uninitialized + current_link = i; + + os.init(current_serialno); + os.reset(); + + } else { + // we're streaming + // fetch the three header packets, build the info struct + int foo[] = new int[1]; + int ret = fetch_headers(vi[0], vc[0], foo, og); + current_serialno = foo[0]; + if (ret != 0) + return ret; + current_link++; + i = 0; + } + make_decode_ready(); + } + os.pagein(og); + } + } + + // The helpers are over; it's all toplevel interface from here on out + // clear out the OggVorbis_File struct + int clear() { + vb.clear(); + vd.clear(); + os.clear(); + + if (vi != null && links != 0) { + for (int i = 0; i < links; i++) { + vi[i].clear(); + vc[i].clear(); + } + vi = null; + vc = null; + } + if (dataoffsets != null) + dataoffsets = null; + if (pcmlengths != null) + pcmlengths = null; + if (serialnos != null) + serialnos = null; + if (offsets != null) + offsets = null; + oy.clear(); + + return (0); + } + + static int fseek(InputStream fis, long off, int whence) { + if (fis instanceof SeekableInputStream) { + SeekableInputStream sis = (SeekableInputStream) fis; + try { + if (whence == SEEK_SET) { + sis.seek(off); + } else if (whence == SEEK_END) { + sis.seek(sis.getLength() - off); + } else { + } + } catch (Exception e) { + } + return 0; + } + try { + if (whence == 0) { + fis.reset(); + } + fis.skip(off); + } catch (Exception e) { + return -1; + } + return 0; + } + + static long ftell(InputStream fis) { + try { + if (fis instanceof SeekableInputStream) { + SeekableInputStream sis = (SeekableInputStream) fis; + return (sis.tell()); + } + } catch (Exception e) { + } + return 0; + } + + // inspects the OggVorbis file and finds/documents all the logical + // bitstreams contained in it. Tries to be tolerant of logical + // bitstream sections that are truncated/woogie. + // + // return: -1) error + // 0) OK + + int open(InputStream is, byte[] initial, int ibytes) throws JOrbisException { + return open_callbacks(is, initial, ibytes); + } + + int open_callbacks(InputStream is, byte[] initial, int ibytes// , callbacks callbacks + ) throws JOrbisException { + int ret; + datasource = is; + + oy.init(); + + // perhaps some data was previously read into a buffer for testing + // against other stream types. Allow initialization from this + // previously read data (as we may be reading from a non-seekable + // stream) + if (initial != null) { + int index = oy.buffer(ibytes); + System.arraycopy(initial, 0, oy.data, index, ibytes); + oy.wrote(ibytes); + } + // can we seek? Stevens suggests the seek test was portable + if (is instanceof SeekableInputStream) { + ret = open_seekable(); + } else { + ret = open_nonseekable(); + } + if (ret != 0) { + datasource = null; + clear(); + } + return ret; + } + + // How many logical bitstreams in this physical bitstream? + public int streams() { + return links; + } + + // Is the FILE * associated with vf seekable? + public boolean seekable() { + return seekable; + } + + // returns the bitrate for a given logical bitstream or the entire + // physical bitstream. If the file is open for random access, it will + // find the *actual* average bitrate. If the file is streaming, it + // returns the nominal bitrate (if set) else the average of the + // upper/lower bounds (if set) else -1 (unset). + // + // If you want the actual bitrate field settings, get them from the + // vorbis_info structs + + public int bitrate(int i) { + if (i >= links) + return (-1); + if (!seekable && i != 0) + return (bitrate(0)); + if (i < 0) { + long bits = 0; + for (int j = 0; j < links; j++) { + bits += (offsets[j + 1] - dataoffsets[j]) * 8; + } + return ((int) Math.rint(bits / time_total(-1))); + } else { + if (seekable) { + // return the actual bitrate + return ((int) Math.rint((offsets[i + 1] - dataoffsets[i]) * 8 / time_total(i))); + } else { + // return nominal if set + if (vi[i].bitrate_nominal > 0) { + return vi[i].bitrate_nominal; + } else { + if (vi[i].bitrate_upper > 0) { + if (vi[i].bitrate_lower > 0) { + return (vi[i].bitrate_upper + vi[i].bitrate_lower) / 2; + } else { + return vi[i].bitrate_upper; + } + } + return (-1); + } + } + } + } + + // returns the actual bitrate since last call. returns -1 if no + // additional data to offer since last call (or at beginning of stream) + public int bitrate_instant() { + int _link = (seekable ? current_link : 0); + if (samptrack == 0) + return (-1); + int ret = (int) (bittrack / samptrack * vi[_link].rate + .5); + bittrack = 0.f; + samptrack = 0.f; + return (ret); + } + + public int serialnumber(int i) { + if (i >= links) + return (-1); + if (!seekable && i >= 0) + return (serialnumber(-1)); + if (i < 0) { + return (current_serialno); + } else { + return (serialnos[i]); + } + } + + // returns: total raw (compressed) length of content if i==-1 + // raw (compressed) length of that logical bitstream for i==0 to n + // -1 if the stream is not seekable (we can't know the length) + + public long raw_total(int i) { + if (!seekable || i >= links) + return (-1); + if (i < 0) { + long acc = 0; // bug? + for (int j = 0; j < links; j++) { + acc += raw_total(j); + } + return (acc); + } else { + return (offsets[i + 1] - offsets[i]); + } + } + + // returns: total PCM length (samples) of content if i==-1 + // PCM length (samples) of that logical bitstream for i==0 to n + // -1 if the stream is not seekable (we can't know the length) + public long pcm_total(int i) { + if (!seekable || i >= links) + return (-1); + if (i < 0) { + long acc = 0; + for (int j = 0; j < links; j++) { + acc += pcm_total(j); + } + return (acc); + } else { + return (pcmlengths[i]); + } + } + + // returns: total seconds of content if i==-1 + // seconds in that logical bitstream for i==0 to n + // -1 if the stream is not seekable (we can't know the length) + public float time_total(int i) { + if (!seekable || i >= links) + return (-1); + if (i < 0) { + float acc = 0; + for (int j = 0; j < links; j++) { + acc += time_total(j); + } + return (acc); + } else { + return ((float) (pcmlengths[i]) / vi[i].rate); + } + } + + // seek to an offset relative to the *compressed* data. This also + // immediately sucks in and decodes pages to update the PCM cursor. It + // will cross a logical bitstream boundary, but only if it can't get + // any packets out of the tail of the bitstream we seek to (so no + // surprises). + // + // returns zero on success, nonzero on failure + + public int raw_seek(int pos) { + if (!seekable) + return (-1); // don't dump machine if we can't seek + if (pos < 0 || pos > offsets[links]) { + // goto seek_error; + pcm_offset = -1; + decode_clear(); + return -1; + } + + // clear out decoding machine state + pcm_offset = -1; + decode_clear(); + + // seek + seek_helper(pos); + + // we need to make sure the pcm_offset is set. We use the + // _fetch_packet helper to process one packet with readp set, then + // call it until it returns '0' with readp not set (the last packet + // from a page has the 'granulepos' field set, and that's how the + // helper updates the offset + + switch (process_packet(1)) { + case 0: + // oh, eof. There are no packets remaining. Set the pcm offset to + // the end of file + pcm_offset = pcm_total(-1); + return (0); + case -1: + // error! missing data or invalid bitstream structure + // goto seek_error; + pcm_offset = -1; + decode_clear(); + return -1; + default: + // all OK + break; + } + while (true) { + switch (process_packet(0)) { + case 0: + // the offset is set. If it's a bogus bitstream with no offset + // information, it's not but that's not our fault. We still run + // gracefully, we're just missing the offset + return (0); + case -1: + // error! missing data or invalid bitstream structure + // goto seek_error; + pcm_offset = -1; + decode_clear(); + return -1; + default: + // continue processing packets + break; + } + } + + // seek_error: + // dump the machine so we're in a known state + // pcm_offset=-1; + // decode_clear(); + // return -1; + } + + // seek to a sample offset relative to the decompressed pcm stream + // returns zero on success, nonzero on failure + + public int pcm_seek(long pos) { + int link = -1; + long total = pcm_total(-1); + + if (!seekable) + return (-1); // don't dump machine if we can't seek + if (pos < 0 || pos > total) { + // goto seek_error; + pcm_offset = -1; + decode_clear(); + return -1; + } + + // which bitstream section does this pcm offset occur in? + for (link = links - 1; link >= 0; link--) { + total -= pcmlengths[link]; + if (pos >= total) + break; + } + + // search within the logical bitstream for the page with the highest + // pcm_pos preceeding (or equal to) pos. There is a danger here; + // missing pages or incorrect frame number information in the + // bitstream could make our task impossible. Account for that (it + // would be an error condition) + { + long target = pos - total; + long end = offsets[link + 1]; + long begin = offsets[link]; + int best = (int) begin; + + Page og = new Page(); + while (begin < end) { + long bisect; + int ret; + + if (end - begin < CHUNKSIZE) { + bisect = begin; + } else { + bisect = (end + begin) / 2; + } + + seek_helper(bisect); + ret = get_next_page(og, end - bisect); + + if (ret == -1) { + end = bisect; + } else { + long granulepos = og.granulepos(); + if (granulepos < target) { + best = ret; // raw offset of packet with granulepos + begin = offset; // raw offset of next packet + } else { + end = bisect; + } + } + } + // found our page. seek to it (call raw_seek). + if (raw_seek(best) != 0) { + // goto seek_error; + pcm_offset = -1; + decode_clear(); + return -1; + } + } + + // verify result + if (pcm_offset >= pos) { + // goto seek_error; + pcm_offset = -1; + decode_clear(); + return -1; + } + if (pos > pcm_total(-1)) { + // goto seek_error; + pcm_offset = -1; + decode_clear(); + return -1; + } + + // discard samples until we reach the desired position. Crossing a + // logical bitstream boundary with abandon is OK. + while (pcm_offset < pos) { + int target = (int) (pos - pcm_offset); + float[][][] _pcm = new float[1][][]; + int[] _index = new int[getInfo(-1).channels]; + int samples = vd.synthesis_pcmout(_pcm, _index); + + if (samples > target) + samples = target; + vd.synthesis_read(samples); + pcm_offset += samples; + + if (samples < target) + if (process_packet(1) == 0) { + pcm_offset = pcm_total(-1); // eof + } + } + return 0; + + // seek_error: + // dump machine so we're in a known state + // pcm_offset=-1; + // decode_clear(); + // return -1; + } + + // seek to a playback time relative to the decompressed pcm stream + // returns zero on success, nonzero on failure + int time_seek(float seconds) { + // translate time to PCM position and call pcm_seek + + int link = -1; + long pcm_total = pcm_total(-1); + float time_total = time_total(-1); + + if (!seekable) + return (-1); // don't dump machine if we can't seek + if (seconds < 0 || seconds > time_total) { + // goto seek_error; + pcm_offset = -1; + decode_clear(); + return -1; + } + + // which bitstream section does this time offset occur in? + for (link = links - 1; link >= 0; link--) { + pcm_total -= pcmlengths[link]; + time_total -= time_total(link); + if (seconds >= time_total) + break; + } + + // enough information to convert time offset to pcm offset + { + long target = (long) (pcm_total + (seconds - time_total) * vi[link].rate); + return (pcm_seek(target)); + } + + // seek_error: + // dump machine so we're in a known state + // pcm_offset=-1; + // decode_clear(); + // return -1; + } + + // tell the current stream offset cursor. Note that seek followed by + // tell will likely not give the set offset due to caching + public long raw_tell() { + return (offset); + } + + // return PCM offset (sample) of next PCM sample to be read + public long pcm_tell() { + return (pcm_offset); + } + + // return time offset (seconds) of next PCM sample to be read + public float time_tell() { + // translate time to PCM position and call pcm_seek + + int link = -1; + long pcm_total = 0; + float time_total = 0.f; + + if (seekable) { + pcm_total = pcm_total(-1); + time_total = time_total(-1); + + // which bitstream section does this time offset occur in? + for (link = links - 1; link >= 0; link--) { + pcm_total -= pcmlengths[link]; + time_total -= time_total(link); + if (pcm_offset >= pcm_total) + break; + } + } + + return ((float) time_total + (float) (pcm_offset - pcm_total) / vi[link].rate); + } + + // link: -1) return the vorbis_info struct for the bitstream section + // currently being decoded + // 0-n) to request information for a specific bitstream section + // + // In the case of a non-seekable bitstream, any call returns the + // current bitstream. NULL in the case that the machine is not + // initialized + + public Info getInfo(int link) { + if (seekable) { + if (link < 0) { + if (decode_ready) { + return vi[current_link]; + } else { + return null; + } + } else { + if (link >= links) { + return null; + } else { + return vi[link]; + } + } + } else { + if (decode_ready) { + return vi[0]; + } else { + return null; + } + } + } + + public Comment getComment(int link) { + if (seekable) { + if (link < 0) { + if (decode_ready) { + return vc[current_link]; + } else { + return null; + } + } else { + if (link >= links) { + return null; + } else { + return vc[link]; + } + } + } else { + if (decode_ready) { + return vc[0]; + } else { + return null; + } + } + } + + int host_is_big_endian() { + return 1; + // short pattern = 0xbabe; + // unsigned char *bytewise = (unsigned char *)&pattern; + // if (bytewise[0] == 0xba) return 1; + // assert(bytewise[0] == 0xbe); + // return 0; + } + + // up to this point, everything could more or less hide the multiple + // logical bitstream nature of chaining from the toplevel application + // if the toplevel application didn't particularly care. However, at + // the point that we actually read audio back, the multiple-section + // nature must surface: Multiple bitstream sections do not necessarily + // have to have the same number of channels or sampling rate. + // + // read returns the sequential logical bitstream number currently + // being decoded along with the PCM data in order that the toplevel + // application can take action on channel/sample rate changes. This + // number will be incremented even for streamed (non-seekable) streams + // (for seekable streams, it represents the actual logical bitstream + // index within the physical bitstream. Note that the accessor + // functions above are aware of this dichotomy). + // + // input values: buffer) a buffer to hold packed PCM data for return + // length) the byte length requested to be placed into buffer + // bigendianp) should the data be packed LSB first (0) or + // MSB first (1) + // word) word size for output. currently 1 (byte) or + // 2 (16 bit short) + // + // return values: -1) error/hole in data + // 0) EOF + // n) number of bytes of PCM actually returned. The + // below works on a packet-by-packet basis, so the + // return length is not related to the 'length' passed + // in, just guaranteed to fit. + // + // *section) set to the logical bitstream number + + int read(byte[] buffer, int length, int bigendianp, int word, int sgned, int[] bitstream) { + int host_endian = host_is_big_endian(); + int index = 0; + + while (true) { + if (decode_ready) { + float[][] pcm; + float[][][] _pcm = new float[1][][]; + int[] _index = new int[getInfo(-1).channels]; + int samples = vd.synthesis_pcmout(_pcm, _index); + pcm = _pcm[0]; + if (samples != 0) { + // yay! proceed to pack data into the byte buffer + int channels = getInfo(-1).channels; + int bytespersample = word * channels; + if (samples > length / bytespersample) + samples = length / bytespersample; + + // a tight loop to pack each size + { + int val; + if (word == 1) { + int off = (sgned != 0 ? 0 : 128); + for (int j = 0; j < samples; j++) { + for (int i = 0; i < channels; i++) { + val = (int) (pcm[i][_index[i] + j] * 128. + 0.5); + if (val > 127) + val = 127; + else if (val < -128) + val = -128; + buffer[index++] = (byte) (val + off); + } + } + } else { + int off = (sgned != 0 ? 0 : 32768); + + if (host_endian == bigendianp) { + if (sgned != 0) { + for (int i = 0; i < channels; i++) { // It's faster in this order + int src = _index[i]; + int dest = i; + for (int j = 0; j < samples; j++) { + val = (int) (pcm[i][src + j] * 32768. + 0.5); + if (val > 32767) + val = 32767; + else if (val < -32768) + val = -32768; + buffer[dest] = (byte) (val >>> 8); + buffer[dest + 1] = (byte) (val); + dest += channels * 2; + } + } + } else { + for (int i = 0; i < channels; i++) { + float[] src = pcm[i]; + int dest = i; + for (int j = 0; j < samples; j++) { + val = (int) (src[j] * 32768. + 0.5); + if (val > 32767) + val = 32767; + else if (val < -32768) + val = -32768; + buffer[dest] = (byte) ((val + off) >>> 8); + buffer[dest + 1] = (byte) (val + off); + dest += channels * 2; + } + } + } + } else if (bigendianp != 0) { + for (int j = 0; j < samples; j++) { + for (int i = 0; i < channels; i++) { + val = (int) (pcm[i][j] * 32768. + 0.5); + if (val > 32767) + val = 32767; + else if (val < -32768) + val = -32768; + val += off; + buffer[index++] = (byte) (val >>> 8); + buffer[index++] = (byte) val; + } + } + } else { + // int val; + for (int j = 0; j < samples; j++) { + for (int i = 0; i < channels; i++) { + val = (int) (pcm[i][j] * 32768. + 0.5); + if (val > 32767) + val = 32767; + else if (val < -32768) + val = -32768; + val += off; + buffer[index++] = (byte) val; + buffer[index++] = (byte) (val >>> 8); + } + } + } + } + } + + vd.synthesis_read(samples); + pcm_offset += samples; + if (bitstream != null) + bitstream[0] = current_link; + return (samples * bytespersample); + } + } + + // suck in another packet + switch (process_packet(1)) { + case 0: + return (0); + case -1: + return -1; + default: + break; + } + } + } + + public Info[] getInfo() { + return vi; + } + + public Comment[] getComment() { + return vc; + } + + public void close() throws java.io.IOException { + datasource.close(); + } + + class SeekableInputStream extends InputStream { + java.io.RandomAccessFile raf = null; + final String mode = "r"; + + SeekableInputStream(String file) throws java.io.IOException { + raf = new java.io.RandomAccessFile(file, mode); + } + + public int read() throws java.io.IOException { + return raf.read(); + } + + public int read(byte[] buf) throws java.io.IOException { + return raf.read(buf); + } + + public int read(byte[] buf, int s, int len) throws java.io.IOException { + return raf.read(buf, s, len); + } + + public long skip(long n) throws java.io.IOException { + return (long) (raf.skipBytes((int) n)); + } + + public long getLength() throws java.io.IOException { + return raf.length(); + } + + public long tell() throws java.io.IOException { + return raf.getFilePointer(); + } + + public int available() throws java.io.IOException { + return (raf.length() == raf.getFilePointer()) ? 0 : 1; + } + + public void close() throws java.io.IOException { + raf.close(); + } + + public synchronized void mark(int m) { + } + + public synchronized void reset() throws java.io.IOException { + } + + public boolean markSupported() { + return false; + } + + public void seek(long pos) throws java.io.IOException { + raf.seek(pos); + } + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/OpenGLObjects.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/OpenGLObjects.java new file mode 100644 index 0000000..5d221ca --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/OpenGLObjects.java @@ -0,0 +1,228 @@ +package net.lax1dude.eaglercraft.v1_8.internal; + +import org.teavm.jso.webgl.WebGLBuffer; +import org.teavm.jso.webgl.WebGLFramebuffer; +import org.teavm.jso.webgl.WebGLProgram; +import org.teavm.jso.webgl.WebGLRenderbuffer; +import org.teavm.jso.webgl.WebGLShader; +import org.teavm.jso.webgl.WebGLTexture; +import org.teavm.jso.webgl.WebGLUniformLocation; + +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.WebGLQuery; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.WebGLVertexArray; + +/** + * Copyright (c) 2022-2023 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. + * + */ +class OpenGLObjects { + + static class BufferGL implements IBufferGL { + + private static int hashGen = 0; + final WebGLBuffer ptr; + final int hash; + + BufferGL(WebGLBuffer ptr) { + this.ptr = ptr; + this.hash = ++hashGen; + } + + public int hashCode() { + return hash; + } + + @Override + public void free() { + PlatformOpenGL._wglDeleteBuffers(this); + } + + } + + static class BufferArrayGL implements IBufferArrayGL { + + private static int hashGen = 0; + final WebGLVertexArray ptr; + final int hash; + + BufferArrayGL(WebGLVertexArray ptr) { + this.ptr = ptr; + this.hash = ++hashGen; + } + + public int hashCode() { + return hash; + } + + @Override + public void free() { + PlatformOpenGL._wglDeleteVertexArrays(this); + } + + } + + static class TextureGL implements ITextureGL { + + private static int hashGen = 0; + final WebGLTexture ptr; + final int hash; + + TextureGL(WebGLTexture ptr) { + this.ptr = ptr; + this.hash = ++hashGen; + } + + public int hashCode() { + return hash; + } + + @Override + public void free() { + PlatformOpenGL._wglDeleteTextures(this); + } + + } + + static class ProgramGL implements IProgramGL { + + private static int hashGen = 0; + final WebGLProgram ptr; + final int hash; + + ProgramGL(WebGLProgram ptr) { + this.ptr = ptr; + this.hash = ++hashGen; + } + + public int hashCode() { + return hash; + } + + @Override + public void free() { + PlatformOpenGL._wglDeleteProgram(this); + } + + } + + static class UniformGL implements IUniformGL { + + private static int hashGen = 0; + final WebGLUniformLocation ptr; + final int hash; + + UniformGL(WebGLUniformLocation ptr) { + this.ptr = ptr; + this.hash = ++hashGen; + } + + public int hashCode() { + return hash; + } + + @Override + public void free() { + } + + } + + static class ShaderGL implements IShaderGL { + + private static int hashGen = 0; + final WebGLShader ptr; + final int hash; + + ShaderGL(WebGLShader ptr) { + this.ptr = ptr; + this.hash = ++hashGen; + } + + public int hashCode() { + return hash; + } + + @Override + public void free() { + PlatformOpenGL._wglDeleteShader(this); + } + + } + + static class FramebufferGL implements IFramebufferGL { + + private static int hashGen = 0; + final WebGLFramebuffer ptr; + final int hash; + + FramebufferGL(WebGLFramebuffer ptr) { + this.ptr = ptr; + this.hash = ++hashGen; + } + + public int hashCode() { + return hash; + } + + @Override + public void free() { + PlatformOpenGL._wglDeleteFramebuffer(this); + } + + } + + static class RenderbufferGL implements IRenderbufferGL { + + private static int hashGen = 0; + final WebGLRenderbuffer ptr; + final int hash; + + RenderbufferGL(WebGLRenderbuffer ptr) { + this.ptr = ptr; + this.hash = ++hashGen; + } + + public int hashCode() { + return hash; + } + + @Override + public void free() { + PlatformOpenGL._wglDeleteRenderbuffer(this); + } + + } + + static class QueryGL implements IQueryGL { + + private static int hashGen = 0; + final WebGLQuery ptr; + final int hash; + + QueryGL(WebGLQuery ptr) { + this.ptr = ptr; + this.hash = ++hashGen; + } + + public int hashCode() { + return hash; + } + + @Override + public void free() { + PlatformOpenGL._wglDeleteQueries(this); + } + + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformApplication.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformApplication.java new file mode 100644 index 0000000..5210999 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformApplication.java @@ -0,0 +1,308 @@ +package net.lax1dude.eaglercraft.v1_8.internal; + +import java.net.URI; +import java.net.URISyntaxException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.teavm.interop.Import; +import org.teavm.jso.JSBody; +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; +import org.teavm.jso.browser.Storage; +import org.teavm.jso.browser.Window; +import org.teavm.jso.canvas.CanvasRenderingContext2D; +import org.teavm.jso.canvas.ImageData; +import org.teavm.jso.core.JSString; +import org.teavm.jso.dom.html.HTMLCanvasElement; +import org.teavm.jso.dom.html.HTMLDocument; +import org.teavm.jso.typedarrays.ArrayBuffer; +import org.teavm.jso.typedarrays.Uint8Array; +import org.teavm.jso.typedarrays.Uint8ClampedArray; + +import net.lax1dude.eaglercraft.v1_8.Base64; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCBufferAllocator; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCDirectArrayConverter; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.BetterJSStringConverter; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.TeaVMUtils; + +/** + * Copyright (c) 2022-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. + * + */ +public class PlatformApplication { + + public static void openLink(String url) { + if(url.indexOf(':') == -1) { + url = "http://" + url; + } + URI parsedURL; + try { + parsedURL = new URI(url); + }catch(URISyntaxException ex) { + PlatformRuntime.logger.error("Refusing to open invalid URL: {}", url); + return; + } + try { + PlatformRuntime.win.open(parsedURL.toString(), "_blank", "noopener,noreferrer"); + }catch(Throwable t) { + PlatformRuntime.logger.error("Exception opening link!"); + } + } + + public static void setClipboard(String text) { + long start = PlatformRuntime.steadyTimeMillis(); + boolean b = false; + try { + b = setClipboard0(BetterJSStringConverter.stringToJS(text)); + }catch(Throwable t) { + PlatformRuntime.logger.error("Exception setting clipboard data"); + } + if(!b) { + try { + Window.prompt("Here is the text you're trying to copy:", text); + }catch(Throwable t2) { + } + } + if(PlatformRuntime.steadyTimeMillis() - start > 500l) { + PlatformInput.unpressCTRL = true; + } + } + + @Import(module = "platformApplication", name = "setClipboard") + private static native boolean setClipboard0(JSString str); + + public static String getClipboard() { + long start = PlatformRuntime.steadyTimeMillis(); + String ret = null; + try { + ret = BetterJSStringConverter.stringFromJS(getClipboard0()); + }catch(Throwable t) { + PlatformRuntime.logger.error("Exception getting clipboard data"); + } + if(ret == null) { + try { + ret = Window.prompt("Please enter the text to paste:"); + }catch(Throwable t2) { + } + } + if(PlatformRuntime.steadyTimeMillis() - start > 500l) { + PlatformInput.unpressCTRL = true; + } + return ret != null ? ret : ""; + } + + @Import(module = "platformApplication", name = "getClipboard") + private static native JSString getClipboard0(); + + public static void setLocalStorage(String name, byte[] data) { + setLocalStorage(name, data, true); + } + + public static void setLocalStorage(String name, byte[] data, boolean hooks) { + IClientConfigAdapter adapter = PlatformRuntime.getClientConfigAdapter(); + String eagName = adapter.getLocalStorageNamespace() + "." + name; + String b64 = data != null ? Base64.encodeBase64String(data) : null; + try { + Storage s = Window.current().getLocalStorage(); + if(s != null) { + if(b64 != null) { + s.setItem(eagName, b64); + }else { + s.removeItem(eagName); + } + } + }catch(Throwable t) { + } + if(hooks) { + adapter.getHooks().callLocalStorageSavedHook(name, b64); + } + } + + public static byte[] getLocalStorage(String name) { + return getLocalStorage(name, true); + } + + public static byte[] getLocalStorage(String name, boolean hooks) { + IClientConfigAdapter adapter = PlatformRuntime.getClientConfigAdapter(); + String eagName = adapter.getLocalStorageNamespace() + "." + name; + byte[] hooked = null; + if(hooks) { + String hookedStr = adapter.getHooks().callLocalStorageLoadHook(eagName); + if(hookedStr != null) { + try { + hooked = Base64.decodeBase64(hookedStr); + }catch(Throwable t) { + PlatformRuntime.logger.error("Invalid Base64 recieved from local storage hook!"); + hooked = null; + } + } + } + if(hooked == null) { + try { + Storage s = Window.current().getLocalStorage(); + if(s != null) { + String str = s.getItem(eagName); + if(str != null) { + return Base64.decodeBase64(str); + }else { + return null; + } + }else { + return null; + } + }catch(Throwable t) { + return null; + } + }else { + return hooked; + } + } + + public static final void displayFileChooser(String mime, String ext) { + displayFileChooser0(BetterJSStringConverter.stringToJS(mime), BetterJSStringConverter.stringToJS(ext)); + } + + @Import(module = "platformApplication", name = "displayFileChooser") + private static native void displayFileChooser0(JSString mime, JSString ext); + + @Import(module = "platformApplication", name = "fileChooserHasResult") + public static native boolean fileChooserHasResult(); + + public static FileChooserResult getFileChooserResult() { + JSFileChooserResult jsResult = getFileChooserResult0(); + if(jsResult != null) { + return new FileChooserResult(jsResult.getFileName(), + WASMGCDirectArrayConverter.externU8ArrayToByteArray(new Uint8Array(jsResult.getFileData()))); + }else { + return null; + } + } + + private interface JSFileChooserResult extends JSObject { + + @JSProperty + String getFileName(); + + @JSProperty + ArrayBuffer getFileData(); + + } + + @Import(module = "platformApplication", name = "getFileChooserResult") + private static native JSFileChooserResult getFileChooserResult0(); + + @Import(module = "platformApplication", name = "clearFileChooserResult") + public static native void clearFileChooserResult(); + + @Import(module = "platformApplication", name = "getFaviconURL") + public static native JSString faviconURLTeaVM(); + + public static void showPopup(String msg) { + Window.alert(msg); + } + + @JSBody(params = { "doc", "str" }, script = "doc.write(str);doc.close();") + private static native void documentWrite(HTMLDocument doc, String str); + + public static void openCreditsPopup(String text) { + Window currentWin = PlatformRuntime.win; + + int w = (int)(850 * PlatformInput.getDPI()); + int h = (int)(700 * PlatformInput.getDPI()); + + int x = (currentWin.getScreen().getWidth() - w) / 2; + int y = (currentWin.getScreen().getHeight() - h) / 2; + + Window newWin = Window.current().open("", "_blank", "top=" + y + ",left=" + x + ",width=" + w + ",height=" + h + ",menubar=0,status=0,titlebar=0,toolbar=0"); + if(newWin == null || TeaVMUtils.isNotTruthy(newWin)) { + Window.alert("ERROR: Popup blocked!\n\nPlease make sure you have popups enabled for this site!"); + return; + } + + newWin.focus(); + documentWrite(newWin.getDocument(), "" + + "EaglercraftX 1.8 Credits" + + "" + + "
" + text + "
"); + } + + public static void downloadFileWithName(String str, byte[] dat) { + ByteBuffer buf = WASMGCDirectArrayConverter.byteArrayToBuffer(dat); + try { + downloadFileWithNameTeaVM(BetterJSStringConverter.stringToJS(str), WASMGCBufferAllocator.getUnsignedByteBufferView(buf)); + }finally { + PlatformRuntime.freeByteBuffer(buf); + } + } + + @Import(module = "platformApplication", name = "downloadFileWithNameU8") + public static native void downloadFileWithNameTeaVM(JSString str, Uint8Array dat); + + @Import(module = "platformApplication", name = "downloadFileWithNameA") + public static native void downloadFileWithNameTeaVM(JSString str, ArrayBuffer dat); + + private static final DateFormat dateFormatSS = new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss"); + + @JSBody(params = { }, script = "return { willReadFrequently: true };") + static native JSObject youEagler(); + + public static String saveScreenshot() { + PlatformOpenGL._wglBindFramebuffer(0x8D40, null); + int w = PlatformInput.getWindowWidth(); + int h = PlatformInput.getWindowHeight(); + ByteBuffer buf = PlatformRuntime.allocateByteBuffer(w * h * 4); + PlatformOpenGL._wglReadPixels(0, 0, w, h, 6408, 5121, buf); + for(int i = 3, l = buf.remaining(); i < l; i += 4) { + buf.put(i, (byte)0xFF); + } + String name = "screenshot_" + dateFormatSS.format(new Date()).toString() + ".png"; + HTMLCanvasElement copyCanvas = (HTMLCanvasElement) Window.current().getDocument().createElement("canvas"); + copyCanvas.setWidth(w); + copyCanvas.setHeight(h); + CanvasRenderingContext2D ctx = (CanvasRenderingContext2D) copyCanvas.getContext("2d", youEagler()); + ImageData imgData = ctx.createImageData(w, h); + Uint8ClampedArray dest = imgData.getData(); + int ww = w * 4; + for(int i = 0, j; i < h; ++i) { + j = (h - i - 1) * ww; + buf.limit(j + ww); + buf.position(j); + dest.set(WASMGCBufferAllocator.getUnsignedClampedByteBufferView(buf), i * ww); + } + ctx.putImageData(imgData, 0, 0); + PlatformRuntime.freeByteBuffer(buf); + downloadScreenshotWithNameTeaVM(BetterJSStringConverter.stringToJS(name), copyCanvas); + return name; + } + + @Import(module = "platformApplication", name = "downloadScreenshot") + private static native void downloadScreenshotWithNameTeaVM(JSString str, HTMLCanvasElement cvs); + + @Import(module = "platformApplication", name = "showDebugConsole") + public static native void showDebugConsole(); + + public static void addLogMessage(String text, boolean err) { + } + + @Import(module = "platformApplication", name = "isShowingDebugConsole") + public static native boolean isShowingDebugConsole(); + + @JSBody(params = { "str" }, script = "window.minecraftServer = str;") + public static native void setMCServerWindowGlobal(String str); + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformAssets.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformAssets.java new file mode 100644 index 0000000..6f21b59 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformAssets.java @@ -0,0 +1,220 @@ +package net.lax1dude.eaglercraft.v1_8.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import org.teavm.interop.Address; +import org.teavm.interop.Import; +import org.teavm.interop.Unmanaged; +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; +import org.teavm.jso.core.JSString; +import org.teavm.jso.typedarrays.Uint8Array; +import org.teavm.jso.typedarrays.Uint8ClampedArray; + +import net.lax1dude.eaglercraft.v1_8.EaglerInputStream; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCBufferAllocator; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCDirectArrayConverter; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.BetterJSStringConverter; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.ClientMain; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.EPKLoader; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; +import net.lax1dude.eaglercraft.v1_8.opengl.ImageData; + +/** + * Copyright (c) 2022-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. + * + */ +public class PlatformAssets { + + static final Logger logger = LogManager.getLogger("PlatformAssets"); + + private static final byte[] MISSING_FILE = new byte[0]; + + static Map assets = new HashMap<>(); + + public static boolean getResourceExists(String path) { + if(path.startsWith("/")) { + path = path.substring(1); + } + byte[] ret = assets.get(path); + if(ret != null && ret != MISSING_FILE) { + return true; + }else { + if(path.startsWith("assets/minecraft/lang/") && !path.endsWith(".mcmeta")) { + byte[] file = PlatformRuntime.downloadRemoteURIByteArray( + ClientMain.configLocalesFolder + "/" + path.substring(22)); + if(file != null) { + assets.put(path, file); + return true; + }else { + assets.put(path, MISSING_FILE); + return false; + } + }else { + return false; + } + } + } + + public static byte[] getResourceBytes(String path) { + if(path.startsWith("/")) { + path = path.substring(1); + } + byte[] data = assets.get(path); + if(data == null && path.startsWith("assets/minecraft/lang/") && !path.endsWith(".mcmeta")) { + byte[] file = PlatformRuntime.downloadRemoteURIByteArray( + ClientMain.configLocalesFolder + "/" + path.substring(22)); + if(file != null) { + assets.put(path, file); + return data; + }else { + assets.put(path, MISSING_FILE); + return null; + } + }else { + return data == MISSING_FILE ? null : data; + } + } + + static void readAssetsTeaVM() { + if(!assets.isEmpty()) { + assets = new HashMap<>(); + } + + int epkCount = getEPKFileCount(); + + logger.info("Reading {} EPK files", epkCount); + + for(int i = 0; i < epkCount; ++i) { + JSEPKFileEntry etr = getEPKFileData(i); + String name = etr.getName(); + String path = etr.getPath(); + Uint8Array data = etr.getData(); + int dataLen = data.getLength(); + + logger.info("Reading: \"{}\" @ {}", name, path.startsWith("/") ? path : ("/" + path)); + + ByteBuffer buf = PlatformRuntime.allocateByteBuffer(dataLen); + try { + WASMGCBufferAllocator.getUnsignedByteBufferView(buf).set(data); + EPKLoader.loadEPK(buf, path, assets); + }catch(IOException e) { + logger.error("Failed to load the EPK file!"); + logger.error(e); + throw new RuntimeInitializationFailureException("Failed to read EPK file \"" + name + "\"!"); + }finally { + PlatformRuntime.freeByteBuffer(buf); + } + } + + logger.info("Loaded {} assets from EPK(s)", assets.size()); + } + + private interface JSEPKFileEntry extends JSObject { + + @JSProperty + String getName(); + + @JSProperty + String getPath(); + + @JSProperty + Uint8Array getData(); + + } + + @Import(module = "platformAssets", name = "getEPKFileData") + private static native JSEPKFileEntry getEPKFileData(int idx); + + @Import(module = "platformAssets", name = "getEPKFileCount") + private static native int getEPKFileCount(); + + public static ImageData loadImageFile(InputStream data) { + return loadImageFile(data, "image/png"); + } + + public static ImageData loadImageFile(InputStream data, String mime) { + byte[] b = EaglerInputStream.inputStreamToBytesQuiet(data); + if(b != null) { + return loadImageFile(b, mime); + }else { + return null; + } + } + + public static ImageData loadImageFile(byte[] data) { + return loadImageFile(data, "image/png"); + } + + public static ImageData loadImageFile(byte[] data, String mime) { + ByteBuffer buf = WASMGCDirectArrayConverter.byteArrayToBuffer(data); + JSImageLoadResult asyncResult; + try { + asyncResult = loadImageFile0(WASMGCBufferAllocator.getUnsignedByteBufferView(buf), BetterJSStringConverter.stringToJS(mime)); + }finally { + PlatformRuntime.freeByteBuffer(buf); + } + + if(asyncResult == null) { + return null; + } + + int w = asyncResult.getWidth(); + int h = asyncResult.getHeight(); + int len = w * h; + + ByteBuffer dataDest = PlatformRuntime.allocateByteBuffer(len << 2); + try { + loadImageFile1(asyncResult, WASMGCBufferAllocator.getUnsignedClampedByteBufferView(dataDest)); + int[] pixelsArray = new int[len]; + copyPixelArrayFast(pixelsArray, WASMGCBufferAllocator.getByteBufferAddress(dataDest), len); + return new ImageData(w, h, pixelsArray, true); + }finally { + PlatformRuntime.freeByteBuffer(dataDest); + } + } + + private interface JSImageLoadResult extends JSObject { + + @JSProperty + int getWidth(); + + @JSProperty + int getHeight(); + + } + + @Import(module = "platformAssets", name = "loadImageFile0") + private static native JSImageLoadResult loadImageFile0(Uint8Array bufferData, JSString mime); + + @Import(module = "platformAssets", name = "loadImageFile1") + private static native void loadImageFile1(JSImageLoadResult imageLoadResult, Uint8ClampedArray dataDest); + + @Unmanaged + private static void copyPixelArrayFast(int[] pixelsArray, Address addr, int count) { + Address addrEnd = addr.add(count << 2); + int dstOffset = 0; + while(addr.isLessThan(addrEnd)) { + pixelsArray[dstOffset] = addr.getInt(); + ++dstOffset; + addr = addr.add(4); + } + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformAudio.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformAudio.java new file mode 100644 index 0000000..0e2da77 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformAudio.java @@ -0,0 +1,530 @@ +package net.lax1dude.eaglercraft.v1_8.internal; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.teavm.interop.Import; +import org.teavm.jso.JSBody; +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; +import org.teavm.jso.core.JSString; +import org.teavm.jso.typedarrays.Float32Array; +import org.teavm.jso.typedarrays.Uint8Array; +import org.teavm.jso.webaudio.AudioBuffer; +import org.teavm.jso.webaudio.AudioBufferSourceNode; +import org.teavm.jso.webaudio.AudioContext; +import org.teavm.jso.webaudio.AudioListener; +import org.teavm.jso.webaudio.GainNode; +import org.teavm.jso.webaudio.MediaStream; +import org.teavm.jso.webaudio.MediaStreamAudioDestinationNode; +import org.teavm.jso.webaudio.PannerNode; + +import net.lax1dude.eaglercraft.v1_8.EagRuntime; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCBufferAllocator; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCDirectArrayConverter; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.BetterJSStringConverter; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.JOrbisAudioBufferDecoder; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.WASMGCClientConfigAdapter; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; +import net.minecraft.util.MathHelper; + +/** + * Copyright (c) 2022-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. + * + */ +public class PlatformAudio { + + static final Logger logger = LogManager.getLogger("BrowserAudio"); + + static AudioContext audioctx = null; + static MediaStreamAudioDestinationNode recDestNode = null; + static MediaStream recDestMediaStream = null; + static AudioBuffer silence = null; + static AudioBufferSourceNode recDestSilenceNode = null; + static GainNode micRecGain = null; + static GainNode gameRecGain = null; + private static final Map soundCache = new HashMap<>(); + private static final List activeSounds = new LinkedList<>(); + + private static long cacheFreeTimer = 0l; + private static long activeFreeTimer = 0l; + private static boolean oggSupport = false; + + static void initialize() { + oggSupport = false; + + audioctx = getContext(); + if(audioctx == null) { + logger.error("Could not initialize audio context!"); + return; + } + + if(((WASMGCClientConfigAdapter)PlatformRuntime.getClientConfigAdapter()).isUseJOrbisAudioDecoderTeaVM()) { + logger.info("Note: Using embedded JOrbis OGG decoder"); + }else { + byte[] fileData = EagRuntime.getRequiredResourceBytes("/assets/eagler/audioctx_test_ogg.dat"); + ByteBuffer buf = WASMGCDirectArrayConverter.byteArrayToBuffer(fileData); + try { + AudioBuffer audioBuffer = decodeAudioBrowserAsync(WASMGCBufferAllocator.getUnsignedByteBufferView(buf), + BetterJSStringConverter.stringToJS("audioctx_test_ogg.dat")); + if(audioBuffer != null && audioBuffer.getLength() > 0) { + oggSupport = true; + } + }catch(Throwable t) { + }finally { + PlatformRuntime.freeByteBuffer(buf); + } + if(!oggSupport) { + logger.error("OGG file support detected as false! Using embedded JOrbis OGG decoder"); + } + } + } + + @Import(module = "platformAudio", name = "getContext") + private static native AudioContext getContext(); + + protected static class BrowserAudioResource implements IAudioResource { + + protected AudioBuffer buffer; + protected long cacheHit = 0l; + + protected BrowserAudioResource(AudioBuffer buffer) { + this.buffer = buffer; + } + + } + + protected interface JSBrowserAudioHandleCB extends JSObject { + + @JSProperty + boolean getIsEnded(); + + @JSProperty + void setIsEnded(boolean b); + + } + + @JSBody(script = "return { isEnded: false };") + protected static native JSBrowserAudioHandleCB createHandleCBInstance(); + + protected static class BrowserAudioHandle implements IAudioHandle { + + protected final BrowserAudioResource resource; + protected AudioBufferSourceNode source; + protected final PannerNode panner; + protected final GainNode gain; + protected float pitch; + protected boolean repeat; + protected boolean isPaused = false; + protected boolean isDisposed = false; + protected JSBrowserAudioHandleCB isEnded; + + protected BrowserAudioHandle(BrowserAudioResource resource, AudioBufferSourceNode source, PannerNode panner, + GainNode gain, float pitch, boolean repeat) { + this.resource = resource; + this.source = source; + this.panner = panner; + this.gain = gain; + this.pitch = pitch; + this.repeat = repeat; + this.isEnded = createHandleCBInstance(); + registerIsEndedHandler(source, isEnded); + } + + @Override + public void pause(boolean setPaused) { + if(setPaused) { + if(!isPaused) { + isPaused = true; + source.getPlaybackRate().setValue(0.0f); + } + }else { + if(isPaused) { + isPaused = false; + source.getPlaybackRate().setValue(pitch); + } + } + } + + @Override + public void repeat(boolean en) { + repeat = en; + if(!isEnded.getIsEnded()) { + source.setLoop(en); + } + } + + @Override + public void restart() { + if(isEnded.getIsEnded()) { + isEnded.setIsEnded(false); + registerIsEndedHandler(source, isEnded); + isPaused = false; + AudioBufferSourceNode src = audioctx.createBufferSource(); + resource.cacheHit = PlatformRuntime.steadyTimeMillis(); + src.setBuffer(resource.buffer); + src.getPlaybackRate().setValue(pitch); + source.disconnect(); + src.connect(panner == null ? gain : panner); + if(isDisposed) { + isDisposed = false; + activeSounds.add(this); + gain.connect(audioctx.getDestination()); + if(gameRecGain != null) { + gain.connect(gameRecGain); + } + } + source = src; + source.start(); + }else { + isPaused = false; + source.getPlaybackRate().setValue(pitch); + source.start(0.0); + } + } + + @Override + public void move(float x, float y, float z) { + if(panner != null) { + panner.setPosition(x, y, z); + } + } + + @Override + public void pitch(float f) { + pitch = f; + if(!isPaused) { + source.getPlaybackRate().setValue(pitch); + } + } + + @Override + public void gain(float f) { + if(panner != null) { + float v1 = f * 16.0f; + if(v1 < 16.0f) v1 = 16.0f; + panner.setMaxDistance(v1); + } + float v2 = f; + if(v2 > 1.0f) v2 = 1.0f; + gain.getGain().setValue(v2); + } + + @Override + public void end() { + if(!isEnded.getIsEnded()) { + isEnded.setIsEnded(true); + releaseIsEndedHandler(source, isEnded); + source.stop(); + } + } + + @Override + public boolean shouldFree() { + return isEnded.getIsEnded(); + } + + private void dispose() { + if(!isDisposed) { + isDisposed = true; + gain.disconnect(); + } + } + } + + @Import(module = "platformAudio", name = "registerIsEndedHandler") + protected static native void registerIsEndedHandler(AudioBufferSourceNode source, JSBrowserAudioHandleCB isEnded); + + @Import(module = "platformAudio", name = "releaseIsEndedHandler") + protected static native void releaseIsEndedHandler(AudioBufferSourceNode source, JSBrowserAudioHandleCB isEnded); + + public static IAudioResource loadAudioData(String filename, boolean holdInCache) { + BrowserAudioResource buffer = soundCache.get(filename); + if(buffer == null) { + byte[] file = PlatformAssets.getResourceBytes(filename); + if(file == null) return null; + buffer = new BrowserAudioResource(decodeAudioData(file, filename)); + if(holdInCache) { + soundCache.put(filename, buffer); + } + } + if(buffer.buffer != null) { + buffer.cacheHit = PlatformRuntime.steadyTimeMillis(); + return buffer; + }else { + return null; + } + } + + public static IAudioResource loadAudioDataNew(String filename, boolean holdInCache, IAudioCacheLoader loader) { + BrowserAudioResource buffer = soundCache.get(filename); + if(buffer == null) { + byte[] file = loader.loadFile(filename); + if(file == null) return null; + buffer = new BrowserAudioResource(decodeAudioData(file, filename)); + if(holdInCache) { + soundCache.put(filename, buffer); + } + } + if(buffer.buffer != null) { + buffer.cacheHit = PlatformRuntime.steadyTimeMillis(); + return buffer; + }else { + return null; + } + } + + private static AudioBuffer decodeAudioData(byte[] data, String errorFileName) { + if(data == null) { + return null; + } + if(oggSupport) { + ByteBuffer buf = WASMGCDirectArrayConverter.byteArrayToBuffer(data); + try { + return decodeAudioBrowserAsync(WASMGCBufferAllocator.getUnsignedByteBufferView(buf), + BetterJSStringConverter.stringToJS(errorFileName)); + }finally { + PlatformRuntime.freeByteBuffer(buf); + } + }else { + if (data.length > 4 && data[0] == (byte) 0x4F && data[1] == (byte) 0x67 && data[2] == (byte) 0x67 + && data[3] == (byte) 0x53) { + return JOrbisAudioBufferDecoder.decodeAudioJOrbis(data, errorFileName); + }else { + ByteBuffer buf = WASMGCDirectArrayConverter.byteArrayToBuffer(data); + try { + return decodeAudioBrowserAsync(WASMGCBufferAllocator.getUnsignedByteBufferView(buf), + BetterJSStringConverter.stringToJS(errorFileName)); + }finally { + PlatformRuntime.freeByteBuffer(buf); + } + } + } + } + + @Import(module = "platformAudio", name = "decodeAudioBrowser") + public static native AudioBuffer decodeAudioBrowserAsync(Uint8Array array, JSString errorFileName); + + public static AudioBuffer decodeAudioBufferPCMBrowser(Float32Array array, int ch, int len, int rate) { + AudioBuffer buffer = audioctx.createBuffer(ch, len, rate); + for(int i = 0; i < ch; ++i) { + buffer.copyToChannel(new Float32Array(array.getBuffer(), array.getByteOffset() + ((i * len) << 2), len), i); + } + return buffer; + } + + public static void clearAudioCache() { + long millis = PlatformRuntime.steadyTimeMillis(); + if(millis - cacheFreeTimer > 30000l) { + cacheFreeTimer = millis; + Iterator itr = soundCache.values().iterator(); + while(itr.hasNext()) { + if(millis - itr.next().cacheHit > 600000l) { // 10 minutes + itr.remove(); + } + } + } + if(millis - activeFreeTimer > 700l) { + activeFreeTimer = millis; + Iterator itr = activeSounds.iterator(); + while(itr.hasNext()) { + BrowserAudioHandle h = itr.next(); + if(h.shouldFree()) { + itr.remove(); + h.dispose(); + } + } + } + } + + public static void flushAudioCache() { + soundCache.clear(); + Iterator itr = activeSounds.iterator(); + while(itr.hasNext()) { + itr.next().dispose(); + } + activeSounds.clear(); + } + + public static boolean available() { + return audioctx != null; + } + + @JSBody(params = { "node" }, script = "node.distanceModel = \"linear\";") + static native void setDistanceModelLinearFast(PannerNode node) ; + + @JSBody(params = { "node" }, script = "node.panningModel = \"HRTF\";") + static native void setPanningModelHRTFFast(PannerNode node) ; + + public static IAudioHandle beginPlayback(IAudioResource track, float x, float y, float z, + float volume, float pitch, boolean repeat) { + BrowserAudioResource internalTrack = (BrowserAudioResource) track; + internalTrack.cacheHit = PlatformRuntime.steadyTimeMillis(); + + AudioBufferSourceNode src = audioctx.createBufferSource(); + src.setBuffer(internalTrack.buffer); + src.getPlaybackRate().setValue(pitch); + src.setLoop(repeat); + + PannerNode panner = audioctx.createPanner(); + panner.setPosition(x, y, z); + float v1 = volume * 16.0f; + if(v1 < 16.0f) v1 = 16.0f; + panner.setMaxDistance(v1); + panner.setRolloffFactor(1.0f); + setDistanceModelLinearFast(panner); + setPanningModelHRTFFast(panner); + panner.setConeInnerAngle(360.0f); + panner.setConeOuterAngle(0.0f); + panner.setConeOuterGain(0.0f); + panner.setOrientation(0.0f, 1.0f, 0.0f); + + GainNode gain = audioctx.createGain(); + float v2 = volume; + if(v2 > 1.0f) v2 = 1.0f; + gain.getGain().setValue(v2); + + src.connect(panner); + panner.connect(gain); + gain.connect(audioctx.getDestination()); + if(gameRecGain != null) { + gain.connect(gameRecGain); + } + + src.start(); + + BrowserAudioHandle ret = new BrowserAudioHandle(internalTrack, src, panner, gain, pitch, repeat); + activeSounds.add(ret); + return ret; + } + + public static IAudioHandle beginPlaybackStatic(IAudioResource track, float volume, float pitch, boolean repeat) { + BrowserAudioResource internalTrack = (BrowserAudioResource) track; + internalTrack.cacheHit = PlatformRuntime.steadyTimeMillis(); + + AudioBufferSourceNode src = audioctx.createBufferSource(); + src.setBuffer(internalTrack.buffer); + src.getPlaybackRate().setValue(pitch); + src.setLoop(repeat); + + GainNode gain = audioctx.createGain(); + float v2 = volume; + if(v2 > 1.0f) v2 = 1.0f; + gain.getGain().setValue(v2); + + src.connect(gain); + gain.connect(audioctx.getDestination()); + if(gameRecGain != null) { + gain.connect(gameRecGain); + } + + src.start(); + + BrowserAudioHandle ret = new BrowserAudioHandle(internalTrack, src, null, gain, pitch, repeat); + activeSounds.add(ret); + return ret; + } + + public static void setListener(float x, float y, float z, float pitchDegrees, float yawDegrees) { + float var2 = MathHelper.cos(-yawDegrees * 0.017453292F); + float var3 = MathHelper.sin(-yawDegrees * 0.017453292F); + float var4 = -MathHelper.cos(pitchDegrees * 0.017453292F); + float var5 = MathHelper.sin(pitchDegrees * 0.017453292F); + AudioListener l = audioctx.getListener(); + l.setPosition(x, y, z); + l.setOrientation(-var3 * var4, -var5, -var2 * var4, 0.0f, 1.0f, 0.0f); + } + + static MediaStream initRecordingStream(float gameVol, float micVol) { + if(recDestMediaStream != null) { + return recDestMediaStream; + } + try { + if(silence == null) { + silence = audioctx.createBuffer(1, 1, 48000); + silence.copyToChannel(new Float32Array(1), 0); + } + recDestNode = audioctx.createMediaStreamDestination(); + recDestSilenceNode = audioctx.createBufferSource(); + recDestSilenceNode.setBuffer(silence); + recDestSilenceNode.setLoop(true); + recDestSilenceNode.start(); + recDestSilenceNode.connect(recDestNode); + if(micVol > 0.0f) { + MediaStream mic = PlatformScreenRecord.getMic(); + if (mic != null) { + micRecGain = audioctx.createGain(); + micRecGain.getGain().setValue(micVol); + audioctx.createMediaStreamSource(mic).connect(micRecGain); + micRecGain.connect(recDestNode); + } + } + gameRecGain = audioctx.createGain(); + gameRecGain.getGain().setValue(gameVol); + for(BrowserAudioHandle handle : activeSounds) { + if(handle.panner != null) { + handle.panner.connect(gameRecGain); + }else { + handle.gain.connect(gameRecGain); + } + } + PlatformVoiceClient.addRecordingDest(gameRecGain); + gameRecGain.connect(recDestNode); + recDestMediaStream = recDestNode.getStream(); + return recDestMediaStream; + }catch(Throwable t) { + destroyRecordingStream(); + throw t; + } + } + + static void destroyRecordingStream() { + if(recDestSilenceNode != null) { + try { + recDestSilenceNode.disconnect(); + }catch(Throwable t) { + } + recDestSilenceNode = null; + } + if(micRecGain != null) { + try { + micRecGain.disconnect(); + }catch(Throwable t) { + } + micRecGain = null; + } + if(gameRecGain != null) { + try { + gameRecGain.disconnect(); + }catch(Throwable t) { + } + for(BrowserAudioHandle handle : activeSounds) { + try { + handle.gain.disconnect(gameRecGain); + }catch(Throwable t) { + } + } + PlatformVoiceClient.removeRecordingDest(gameRecGain); + gameRecGain = null; + } + recDestNode = null; + recDestMediaStream = null; + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformFilesystem.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformFilesystem.java new file mode 100644 index 0000000..0b6a416 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformFilesystem.java @@ -0,0 +1,49 @@ +package net.lax1dude.eaglercraft.v1_8.internal; + +import net.lax1dude.eaglercraft.v1_8.internal.vfs2.EaglerFileSystemException; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.IndexedDBFilesystem; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; + +/** + * 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. + * + */ +public class PlatformFilesystem { + + private static final Logger logger = LogManager.getLogger("PlatformFilesystem"); + + public static IEaglerFilesystem initializePersist(String dbName) { + try { + return IndexedDBFilesystem.createFilesystem(dbName); + }catch(Throwable t) { + logger.error("Could not open IndexedDB filesystem: {}", dbName); + logger.error(t); + return null; + } + } + + public static class FilesystemDatabaseLockedException extends EaglerFileSystemException { + public FilesystemDatabaseLockedException(String message) { + super(message); + } + } + + public static class FilesystemDatabaseInitializationException extends EaglerFileSystemException { + public FilesystemDatabaseInitializationException(String message) { + super(message); + } + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformInput.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformInput.java new file mode 100644 index 0000000..91a5ff9 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformInput.java @@ -0,0 +1,1306 @@ +package net.lax1dude.eaglercraft.v1_8.internal; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.teavm.interop.Import; +import org.teavm.jso.JSBody; +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; +import org.teavm.jso.browser.Navigator; +import org.teavm.jso.browser.Window; +import org.teavm.jso.core.JSArrayReader; +import org.teavm.jso.core.JSString; +import org.teavm.jso.dom.html.HTMLCanvasElement; +import org.teavm.jso.dom.html.HTMLElement; +import org.teavm.jso.gamepad.Gamepad; +import org.teavm.jso.gamepad.GamepadButton; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; + +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.BetterJSStringConverter; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.LegacyKeycodeTranslator; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.SortedTouchEvent; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.WASMGCClientConfigAdapter; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.TeaVMUtils; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.WebGLBackBuffer; + +/** + * 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. + * + */ +public class PlatformInput { + + private static Window win = null; + private static HTMLElement parent = null; + private static HTMLCanvasElement canvas = null; + private static int touchOpenZoneX = 0; + private static int touchOpenZoneY = 0; + private static int touchOpenZoneW = 0; + private static int touchOpenZoneH = 0; + + private static int windowWidth = -1; + private static int windowHeight = -1; + private static float windowDPI = 1.0f; + private static int visualViewportX = -1; + private static int visualViewportY = -1; + private static int visualViewportW = -1; + private static int visualViewportH = -1; + private static int lastWasResizedWindowWidth = -2; + private static int lastWasResizedWindowHeight = -2; + private static float lastWasResizedWindowDPI = -2.0f; + private static int lastWasResizedVisualViewportX = -2; + private static int lastWasResizedVisualViewportY = -2; + private static int lastWasResizedVisualViewportW = -2; + private static int lastWasResizedVisualViewportH = -2; + + private static final List mouseEvents = new LinkedList<>(); + private static final List keyEvents = new LinkedList<>(); + private static final List touchEvents = new LinkedList<>(); + private static final List pastedStrings = new LinkedList<>(); + private static VMouseEvent currentMouseEvent = null; + private static VKeyEvent currentKeyEvent = null; + private static SortedTouchEvent currentTouchEvent = null; + private static boolean[] buttonStates = new boolean[8]; + private static boolean[] keyStates = new boolean[256]; + private static SortedTouchEvent currentTouchState = null; + + private static boolean gamepadSupported = false; + private static final List gamepadList = new ArrayList<>(); + private static Gamepad selectedGamepad = null; + private static String selectedGamepadName = null; + private static double gamepadTimestamp = -1.0; + private static final boolean[] gamepadButtonStates = new boolean[24]; + private static final float[] gamepadAxisStates = new float[4]; + + private static int functionKeyModifier = KeyboardConstants.KEY_F; + + private static int mouseX = 0; + private static int mouseY = 0; + private static float mouseDX = 0.0f; + private static float mouseDY = 0.0f; + private static float mouseDWheel = 0.0f; + private static boolean enableRepeatEvents = true; + private static boolean isWindowFocused = true; + private static boolean isMouseOverWindow = true; + private static boolean isMouseGrabSupported = false; + static boolean unpressCTRL = false; + + private static boolean fullscreenSupported = false; + + static boolean useVisualViewport = false; + + public static boolean keyboardLockSupported = false; + public static boolean lockKeys = false; + + static boolean vsync = true; + static boolean vsyncSupport = false; + + private static Map keyCodeTranslatorMap = null; + + static void initContext(Window window, HTMLElement parent_, HTMLCanvasElement canvaz) { + win = window; + parent = parent_; + canvas = canvaz; + canvas.getStyle().setProperty("cursor", "default"); + isMouseGrabSupported = mouseGrabSupported0(); + fullscreenSupported = supportsFullscreen0(); + vsyncSupport = isVSyncSupported0(); + WASMGCClientConfigAdapter conf = (WASMGCClientConfigAdapter)PlatformRuntime.getClientConfigAdapter(); + useVisualViewport = conf.isUseVisualViewportTeaVM() && isVisualViewport0(); + lastWasResizedWindowWidth = -2; + lastWasResizedWindowHeight = -2; + lastWasResizedWindowDPI = -2.0f; + lastWasResizedVisualViewportX = -2; + lastWasResizedVisualViewportY = -2; + lastWasResizedVisualViewportW = -2; + lastWasResizedVisualViewportH = -2; + + PlatformRuntime.logger.info("Loading keyboard layout data"); + + LegacyKeycodeTranslator keycodeTranslator = new LegacyKeycodeTranslator(); + if(checkKeyboardLayoutSupported()) { + JSArrayReader arr = iterateKeyboardLayout(); + for(int i = 0, l = arr.getLength(); i < l; ++i) { + JSKeyboardLayoutEntry etr = arr.get(i); + keycodeTranslator.addBrowserLayoutMapping(etr.getKey(), etr.getValue()); + } + int cnt = keycodeTranslator.getRemappedKeyCount(); + if(cnt > 0) { + PlatformRuntime.logger.info("KeyboardLayoutMap remapped {} keys from their default codes", cnt); + } + } + keyCodeTranslatorMap = keycodeTranslator.buildLayoutTable(); + + gamepadSupported = isGamepadSupported(); + } + + private interface JSKeyboardLayoutEntry extends JSObject { + + @JSProperty + String getKey(); + + @JSProperty + String getValue(); + + } + + @Import(module = "platformInput", name = "keyboardLayoutSupported") + private static native boolean checkKeyboardLayoutSupported(); + + @Import(module = "platformInput", name = "iterateKeyboardLayout") + private static native JSArrayReader iterateKeyboardLayout(); + + private static final int EVENT_MOUSE_DOWN = 0; + private static final int EVENT_MOUSE_UP = 1; + private static final int EVENT_MOUSE_MOVE = 2; + private static final int EVENT_MOUSE_WHEEL = 3; + + private static class VMouseEvent { + + private final int posX; + private final int posY; + private final int button; + private final float wheel; + private final int type; + + private VMouseEvent(int posX, int posY, int button, float wheel, int type) { + this.posX = posX; + this.posY = posY; + this.button = button; + this.wheel = wheel; + this.type = type; + } + + } + + private interface JSMouseEvent extends JSObject { + + @JSProperty + int getEventType(); + + @JSProperty + int getPosX(); + + @JSProperty + int getPosY(); + + @JSProperty + int getMovementX(); + + @JSProperty + int getMovementY(); + + @JSProperty + int getButton(); + + @JSProperty + float getWheel(); + + } + + private static final int EVENT_KEY_DOWN = 0; + private static final int EVENT_KEY_UP = 1; + private static final int EVENT_KEY_REPEAT = 2; + + private static final int EVENT_KEY_MOD_CONTROL = 1; + private static final int EVENT_KEY_MOD_SHIFT = 2; + private static final int EVENT_KEY_MOD_ALT = 4; + + private static class VKeyEvent { + + private final int keyCode; + private final int location; + private final int eagKey; + private final char keyChar; + private final int type; + + private VKeyEvent(int keyCode, int location, int eagKey, char keyChar, int type) { + this.keyCode = keyCode; + this.location = location; + this.eagKey = eagKey; + this.keyChar = keyChar; + this.type = type; + } + + } + + private interface JSKeyEvent extends JSObject { + + @JSProperty + int getEventType(); + + @JSProperty + String getKeyCode(); + + @JSProperty + String getKeyName(); + + @JSProperty + int getLegacyCode(); + + @JSProperty + int getLocation(); + + @JSProperty + int getMods(); + + } + + private static final int EVENT_TOUCH_START = 0; + private static final int EVENT_TOUCH_MOVE = 1; + private static final int EVENT_TOUCH_END = 2; + + private interface JSTouchEvent extends JSObject { + + @JSProperty + int getEventType(); + + @JSProperty + JSArrayReader getChangedTouches(); + + @JSProperty + JSArrayReader getTargetTouches(); + + } + + private static final int EVENT_TOUCH_KEYBOARD_ABSOLUTE = 0; + private static final int EVENT_TOUCH_KEYBOARD_CODEPOINTS = 1; + + private interface JSTouchKeyboardEvent extends JSObject { + + @JSProperty + int getEventType(); + + @JSProperty + int getAbsoluteCode(); + + @JSProperty + char getAbsoluteChar(); + + @JSProperty + JSString getCodepoints(); + + } + + private static final int EVENT_RESIZE_WINDOW = 1; + private static final int EVENT_RESIZE_VISUAL_VIEWPORT = 2; + private static final int EVENT_RESIZE_DPI = 4; + + private interface JSWindowResizeEvent extends JSObject { + + @JSProperty + int getEventTypeMask(); + + @JSProperty + int getWindowWidth(); + + @JSProperty + int getWindowHeight(); + + @JSProperty + int getVisualViewportX(); + + @JSProperty + int getVisualViewportY(); + + @JSProperty + int getVisualViewportW(); + + @JSProperty + int getVisualViewportH(); + + @JSProperty + float getWindowDPI(); + + } + + private static final int EVENT_GAMEPAD_CONNECTED = 0; + private static final int EVENT_GAMEPAD_DISCONNECTED = 1; + + private interface JSGamepadEvent extends JSObject { + + @JSProperty + int getEventType(); + + @JSProperty + Gamepad getGamepad(); + + } + + private static final BiMap touchIDtoUID = HashBiMap.create(); + private static int touchUIDnum = 0; + + private static final SortedTouchEvent.ITouchUIDMapper touchUIDMapperCreate = (idx) -> { + Integer ret = touchIDtoUID.get(idx); + if(ret != null) return ret.intValue(); + int r = touchUIDnum++; + touchIDtoUID.put(idx, r); + return r; + }; + + private static final SortedTouchEvent.ITouchUIDMapper touchUIDMapper = (idx) -> { + Integer ret = touchIDtoUID.get(idx); + return ret != null ? ret.intValue() : -1; + }; + + private static final int EVENT_INPUT_MOUSE = 0; + private static final int EVENT_INPUT_KEYBOARD = 1; + private static final int EVENT_INPUT_TOUCH = 2; + private static final int EVENT_INPUT_TOUCH_KEYBOARD = 3; + private static final int EVENT_INPUT_TOUCH_PASTE = 4; + private static final int EVENT_INPUT_FOCUS = 5; + private static final int EVENT_INPUT_BLUR = 6; + private static final int EVENT_INPUT_MOUSE_ENTER = 7; + private static final int EVENT_INPUT_MOUSE_EXIT = 8; + private static final int EVENT_INPUT_WINDOW_RESIZE = 9; + private static final int EVENT_INPUT_GAMEPAD = 10; + + static void handleJSEvent(PlatformRuntime.JSEagRuntimeEvent evt) { + switch(evt.getEventType() & 31) { + case EVENT_INPUT_MOUSE: { + JSMouseEvent obj = evt.getEventObj(); + int type = obj.getEventType(); + int posX = (int)(obj.getPosX() * windowDPI); + int posY = windowHeight - (int)(obj.getPosY() * windowDPI) - 1; + switch(type) { + case EVENT_MOUSE_DOWN: { + int button = obj.getButton(); + button = button == 1 ? 2 : (button == 2 ? 1 : button); + if(button >= 0 && button < buttonStates.length) { + buttonStates[button] = true; + } + mouseEvents.add(new VMouseEvent(posX, posY, button, 0.0f, EVENT_MOUSE_DOWN)); + break; + } + case EVENT_MOUSE_UP: { + int button = obj.getButton(); + button = button == 1 ? 2 : (button == 2 ? 1 : button); + if(button >= 0 && button < buttonStates.length) { + buttonStates[button] = false; + } + mouseEvents.add(new VMouseEvent(posX, posY, button, 0.0f, EVENT_MOUSE_UP)); + break; + } + case EVENT_MOUSE_MOVE: { + if(isMouseGrabbed()) { + mouseDX += obj.getMovementX(); + mouseDY -= obj.getMovementY(); + } + mouseX = posX; + mouseY = posY; + mouseEvents.add(new VMouseEvent(posX, posY, -1, 0.0f, EVENT_MOUSE_MOVE)); + break; + } + case EVENT_MOUSE_WHEEL: { + float wheel = -obj.getWheel(); + mouseDWheel += wheel; + mouseEvents.add(new VMouseEvent(posX, posY, -1, wheel, EVENT_MOUSE_WHEEL)); + break; + } + } + break; + } + case EVENT_INPUT_KEYBOARD: { + JSKeyEvent obj = evt.getEventObj(); + int type = obj.getEventType(); + if(!enableRepeatEvents && type == EVENT_KEY_REPEAT) break; + String keyCodeStr = obj.getKeyCode(); + LegacyKeycodeTranslator.LegacyKeycode keyCode = null; + if(keyCodeTranslatorMap != null && keyCodeStr != null) { + keyCode = keyCodeTranslatorMap.get(keyCodeStr); + } + int w; + int loc; + if(keyCode != null) { + w = keyCode.keyCode; + loc = keyCode.location; + }else { + w = obj.getLegacyCode(); + loc = obj.getLocation(); + } + int ww = processFunctionKeys(w); + int eag = KeyboardConstants.getEaglerKeyFromBrowser(ww, ww == w ? loc : 0); + if(eag != 0) { + keyStates[eag] = type != EVENT_KEY_UP; + if(eag == functionKeyModifier) { + for(int key = KeyboardConstants.KEY_F1; key <= KeyboardConstants.KEY_F10; ++key) { + keyStates[key] = false; + } + } + } + char c; + String s = obj.getKeyName(); + if(s == null || s.length() == 0) { + c = keyToAsciiLegacy(w, (obj.getMods() & EVENT_KEY_MOD_SHIFT) != 0); + }else { + if(s.length() == 1) { + c = s.charAt(0); + }else if(s.equals("Unidentified")) { + break; + }else { + c = '\0'; + } + } + keyEvents.add(new VKeyEvent(ww, loc, eag, c, type)); + break; + } + case EVENT_INPUT_TOUCH: { + JSTouchEvent obj = evt.getEventObj(); + switch(obj.getEventType()) { + case EVENT_TOUCH_START: + touchEvents.add(currentTouchState = SortedTouchEvent.createTouchEvent(EnumTouchEvent.TOUCHSTART, obj.getChangedTouches(), + obj.getTargetTouches(), touchUIDMapperCreate, windowHeight, windowDPI)); + break; + case EVENT_TOUCH_MOVE: + touchEvents.add(currentTouchState = SortedTouchEvent.createTouchEvent(EnumTouchEvent.TOUCHMOVE, obj.getChangedTouches(), + obj.getTargetTouches(), touchUIDMapper, windowHeight, windowDPI)); + break; + case EVENT_TOUCH_END: + currentTouchState = SortedTouchEvent.createTouchEvent(EnumTouchEvent.TOUCHEND, obj.getChangedTouches(), + obj.getTargetTouches(), touchUIDMapper, windowHeight, windowDPI); + List lst = currentTouchState.getEventTouches(); + int len = lst.size(); + BiMap inv = touchIDtoUID.inverse(); + for (int i = 0; i < len; ++i) { + inv.remove(lst.get(i).uid); + } + touchEvents.add(currentTouchState); + break; + } + break; + } + case EVENT_INPUT_TOUCH_KEYBOARD: { + JSTouchKeyboardEvent obj = evt.getEventObj(); + switch(obj.getEventType()) { + case EVENT_TOUCH_KEYBOARD_ABSOLUTE: { + int code = obj.getAbsoluteCode(); + char character = obj.getAbsoluteChar(); + keyboardFireEvent(EnumFireKeyboardEvent.KEY_DOWN, code, character); + keyboardFireEvent(EnumFireKeyboardEvent.KEY_UP, code, character); + break; + } + case EVENT_TOUCH_KEYBOARD_CODEPOINTS: { + JSString eventsToGenerate = obj.getCodepoints(); + for(int i = 0, l = eventsToGenerate.getLength(); i < l; ++i) { + char c = (char)eventsToGenerate.charCodeAt(i); + int eag = KeyboardConstants.getEaglerKeyFromBrowser(asciiUpperToKeyLegacy(Character.toUpperCase(c)), 0); + keyboardFireEvent(EnumFireKeyboardEvent.KEY_DOWN, eag, c); + keyboardFireEvent(EnumFireKeyboardEvent.KEY_UP, eag, c); + } + break; + } + } + break; + } + case EVENT_INPUT_TOUCH_PASTE: { + pastedStrings.add(BetterJSStringConverter.stringFromJS(evt.getEventObj())); + break; + } + case EVENT_INPUT_FOCUS: { + isWindowFocused = true; + break; + } + case EVENT_INPUT_BLUR: { + isWindowFocused = false; + Arrays.fill(buttonStates, false); + Arrays.fill(keyStates, false); + break; + } + case EVENT_INPUT_MOUSE_ENTER: { + isMouseOverWindow = true; + break; + } + case EVENT_INPUT_MOUSE_EXIT: { + isMouseOverWindow = false; + break; + } + case EVENT_INPUT_WINDOW_RESIZE: { + JSWindowResizeEvent obj = evt.getEventObj(); + int mask = obj.getEventTypeMask(); + if((mask & EVENT_RESIZE_DPI) != 0) { + windowDPI = obj.getWindowDPI(); + } + if((mask & EVENT_RESIZE_WINDOW) != 0) { + windowWidth = obj.getWindowWidth(); + windowHeight = obj.getWindowHeight(); + if(!useVisualViewport) { + visualViewportX = 0; + visualViewportY = 0; + visualViewportW = windowWidth; + visualViewportH = windowHeight; + } + } + if((mask & EVENT_RESIZE_VISUAL_VIEWPORT) != 0) { + if(useVisualViewport) { + visualViewportX = obj.getVisualViewportX(); + visualViewportY = obj.getVisualViewportY(); + visualViewportW = obj.getVisualViewportW(); + visualViewportH = obj.getVisualViewportH(); + if(visualViewportW < 1) visualViewportW = 1; + if(visualViewportH < 1) visualViewportH = 1; + if(visualViewportX < 0) { + visualViewportW += visualViewportX; + visualViewportX = 0; + }else if(visualViewportX >= windowWidth) { + visualViewportX = windowWidth - 1; + } + if(visualViewportY < 0) { + visualViewportH += visualViewportY; + visualViewportY = 0; + }else if(visualViewportY >= windowHeight) { + visualViewportY = windowHeight - 1; + } + if((visualViewportX + visualViewportW) > windowWidth) { + visualViewportW = windowWidth - visualViewportX; + } + if((visualViewportY + visualViewportH) > windowHeight) { + visualViewportH = windowHeight - visualViewportY; + } + } + } + break; + } + case EVENT_INPUT_GAMEPAD: { + JSGamepadEvent obj = evt.getEventObj(); + switch(obj.getEventType()) { + case EVENT_GAMEPAD_CONNECTED: { + enumerateGamepads(); + break; + } + case EVENT_GAMEPAD_DISCONNECTED: { + if(selectedGamepad != null && obj.getGamepad().getIndex() == selectedGamepad.getIndex()) { + selectedGamepad = null; + } + enumerateGamepads(); + break; + } + } + break; + } + } + } + + public static int getWindowWidth() { + return windowWidth; + } + + public static int getWindowHeight() { + return windowHeight; + } + + public static int getVisualViewportX() { + return visualViewportX; + } + + public static int getVisualViewportY() { + return visualViewportY; + } + + public static int getVisualViewportW() { + return visualViewportW; + } + + public static int getVisualViewportH() { + return visualViewportH; + } + + public static boolean getWindowFocused() { + return isWindowFocused || isPointerLocked(); + } + + public static boolean isCloseRequested() { + return false; + } + + public static void setVSync(boolean enable) { + vsync = enable; + } + + public static void update() { + update(0); + } + + public static void update(int fpsLimit) { + if(windowWidth > 0 && windowHeight > 0) { + updateCanvasSize(windowWidth, windowHeight); + WebGLBackBuffer.flipBuffer(windowWidth, windowHeight); + PlatformScreenRecord.captureFrameHook(); + } + + updatePlatformAndSleep(fpsLimit, vsync && vsyncSupport);// && vsync + PlatformRuntime.pollJSEventsAfterSleep(); + } + + @Import(module = "platformInput", name = "updateCanvasSize") + private static native void updateCanvasSize(int width, int height); + + @Import(module = "platformInput", name = "updatePlatformAndSleep") + private static native void updatePlatformAndSleep(int fpsLimit, boolean vsync); + + public static boolean isVSyncSupported() { + return vsyncSupport; + } + + @Import(module = "platformInput", name = "isVSyncSupported") + private static native boolean isVSyncSupported0(); + + public static boolean wasResized() { + if (windowWidth != lastWasResizedWindowWidth || windowHeight != lastWasResizedWindowHeight + || windowDPI != lastWasResizedWindowDPI) { + lastWasResizedWindowWidth = windowWidth; + lastWasResizedWindowHeight = windowHeight; + lastWasResizedWindowDPI = windowDPI; + return true; + }else { + return false; + } + } + + public static boolean wasVisualViewportResized() { + if (visualViewportX != lastWasResizedVisualViewportX || visualViewportY != lastWasResizedVisualViewportY + || visualViewportW != lastWasResizedVisualViewportW + || visualViewportH != lastWasResizedVisualViewportH) { + lastWasResizedVisualViewportX = visualViewportX; + lastWasResizedVisualViewportY = visualViewportY; + lastWasResizedVisualViewportW = visualViewportW; + lastWasResizedVisualViewportH = visualViewportH; + return true; + }else { + return false; + } + } + + public static boolean keyboardNext() { + if(unpressCTRL) { //un-press ctrl after copy/paste permission + keyEvents.clear(); + currentKeyEvent = null; + keyStates[29] = false; + keyStates[157] = false; + keyStates[28] = false; + keyStates[219] = false; + keyStates[220] = false; + unpressCTRL = false; + return false; + } + currentKeyEvent = null; + return !keyEvents.isEmpty() && (currentKeyEvent = keyEvents.remove(0)) != null; + } + + public static void keyboardFireEvent(EnumFireKeyboardEvent eventType, int eagKey, char keyChar) { + switch(eventType) { + case KEY_DOWN: + keyEvents.add(new VKeyEvent(-1, 0, eagKey, keyChar, EVENT_KEY_DOWN)); + break; + case KEY_UP: + keyEvents.add(new VKeyEvent(-1, 0, eagKey, '\0', EVENT_KEY_UP)); + break; + case KEY_REPEAT: + keyEvents.add(new VKeyEvent(-1, 0, eagKey, keyChar, EVENT_KEY_REPEAT)); + break; + default: + throw new UnsupportedOperationException(); + } + } + + public static boolean keyboardGetEventKeyState() { + return currentKeyEvent == null ? false : (currentKeyEvent.type != EVENT_KEY_UP); + } + + public static int keyboardGetEventKey() { + return currentKeyEvent == null ? -1 : currentKeyEvent.eagKey; + } + + private static char keyToAsciiLegacy(int whichIn, boolean shiftUp) { + switch(whichIn) { + case 188: whichIn = 44; break; + case 109: whichIn = 45; break; + case 190: whichIn = 46; break; + case 191: whichIn = 47; break; + case 192: whichIn = 96; break; + case 220: whichIn = 92; break; + case 222: whichIn = 39; break; + case 221: whichIn = 93; break; + case 219: whichIn = 91; break; + case 173: whichIn = 45; break; + case 187: whichIn = 61; break; + case 186: whichIn = 59; break; + case 189: whichIn = 45; break; + default: break; + } + if(shiftUp) { + switch(whichIn) { + case 96: return '~'; + case 49: return '!'; + case 50: return '@'; + case 51: return '#'; + case 52: return '$'; + case 53: return '%'; + case 54: return '^'; + case 55: return '&'; + case 56: return '*'; + case 57: return '('; + case 48: return ')'; + case 45: return '_'; + case 61: return '+'; + case 91: return '{'; + case 93: return '}'; + case 92: return '|'; + case 59: return ':'; + case 39: return '\"'; + case 44: return '<'; + case 46: return '>'; + case 47: return '?'; + default: return (char)whichIn; + } + }else { + if(whichIn >= 65 && whichIn <= 90) { + return (char)(whichIn + 32); + }else { + return (char)whichIn; + } + } + } + + private static int asciiUpperToKeyLegacy(char charIn) { + switch(charIn) { + case '\n': return 17; + case '~': return 192; + case '!': return 49; + case '@': return 50; + case '#': return 51; + case '$': return 52; + case '%': return 53; + case '^': return 54; + case '&': return 55; + case '*': return 56; + case '(': return 57; + case ')': return 48; + case '_': return 173; + case '+': return 187; + case '{': return 219; + case '}': return 221; + case '|': return 220; + case ':': return 186; + case '\"': return 222; + case '<': return 188; + case '>': return 190; + case '?': return 191; + case '.': return 190; + case '\'': return 222; + case ';': return 186; + case '[': return 219; + case ']': return 221; + case ',': return 188; + case '/': return 191; + case '\\': return 220; + case '-': return 189; + case '`': return 192; + default: return (int)charIn; + } + } + + public static char keyboardGetEventCharacter() { + return currentKeyEvent == null ? '\0' : currentKeyEvent.keyChar; + } + + public static boolean keyboardIsKeyDown(int key) { + if(unpressCTRL) { //un-press ctrl after copy/paste permission + keyStates[28] = false; + keyStates[29] = false; + keyStates[157] = false; + keyStates[219] = false; + keyStates[220] = false; + } + return key < 0 || key >= keyStates.length ? false : keyStates[key]; + } + + public static boolean keyboardIsRepeatEvent() { + return currentKeyEvent == null ? false : (currentKeyEvent.type == EVENT_KEY_REPEAT); + } + + public static void keyboardEnableRepeatEvents(boolean b) { + enableRepeatEvents = b; + } + + public static boolean keyboardAreKeysLocked() { + return lockKeys; + } + + public static boolean mouseNext() { + currentMouseEvent = null; + return !mouseEvents.isEmpty() && (currentMouseEvent = mouseEvents.remove(0)) != null; + } + + public static void mouseFireMoveEvent(EnumFireMouseEvent eventType, int posX, int posY) { + if(eventType == EnumFireMouseEvent.MOUSE_MOVE) { + mouseEvents.add(new VMouseEvent(posX, posY, -1, 0.0f, EVENT_MOUSE_MOVE)); + }else { + throw new UnsupportedOperationException(); + } + } + + public static void mouseFireButtonEvent(EnumFireMouseEvent eventType, int posX, int posY, int button) { + switch(eventType) { + case MOUSE_DOWN: + mouseEvents.add(new VMouseEvent(posX, posY, button, 0.0f, EVENT_MOUSE_DOWN)); + break; + case MOUSE_UP: + mouseEvents.add(new VMouseEvent(posX, posY, button, 0.0f, EVENT_MOUSE_UP)); + break; + default: + throw new UnsupportedOperationException(); + } + } + + public static void mouseFireWheelEvent(EnumFireMouseEvent eventType, int posX, int posY, float wheel) { + if(eventType == EnumFireMouseEvent.MOUSE_WHEEL) { + mouseEvents.add(new VMouseEvent(posX, posY, -1, wheel, EVENT_MOUSE_WHEEL)); + }else { + throw new UnsupportedOperationException(); + } + } + + public static boolean mouseGetEventButtonState() { + return currentMouseEvent == null ? false : (currentMouseEvent.type == EVENT_MOUSE_DOWN); + } + + public static int mouseGetEventButton() { + if(currentMouseEvent == null || (currentMouseEvent.type == EVENT_MOUSE_MOVE)) return -1; + return currentMouseEvent.button; + } + + public static int mouseGetEventX() { + return currentMouseEvent == null ? -1 : currentMouseEvent.posX; + } + + public static int mouseGetEventY() { + return currentMouseEvent == null ? -1 : currentMouseEvent.posY; + } + + public static int mouseGetEventDWheel() { + return (currentMouseEvent.type == EVENT_MOUSE_WHEEL) ? fixWheel(currentMouseEvent.wheel) : 0; + } + + private static int fixWheel(float val) { + return (val > 0.0f ? 1 : (val < 0.0f ? -1 : 0)); + } + + public static int mouseGetX() { + return mouseX; + } + + public static int mouseGetY() { + return mouseY; + } + + public static boolean mouseIsButtonDown(int i) { + return (i < 0 || i >= buttonStates.length) ? false : buttonStates[i]; + } + + public static int mouseGetDWheel() { + int ret = (int)mouseDWheel; + mouseDWheel -= ret; + return ret; + } + + public static boolean mouseGrabSupported() { + return isMouseGrabSupported; + } + + public static void mouseSetGrabbed(boolean grab) { + if(isMouseGrabSupported) { + mouseSetGrabbed0(grab); + } + } + + @Import(module = "platformInput", name = "mouseSetGrabbed") + private static native void mouseSetGrabbed0(boolean grab); + + @Import(module = "platformInput", name = "isMouseGrabSupported") + private static native boolean mouseGrabSupported0(); + + @Import(module = "platformInput", name = "isMouseGrabbed") + public static native boolean isMouseGrabbed(); + + @Import(module = "platformInput", name = "isPointerLocked") + public static native boolean isPointerLocked(); + + public static int mouseGetDX() { + int ret = (int)mouseDX; + mouseDX = 0.0f; + return ret; + } + + public static int mouseGetDY() { + int ret = (int)mouseDY; + mouseDY = 0.0f; + return ret; + } + + public static void mouseSetCursorPosition(int x, int y) { + // obsolete + } + + public static boolean mouseIsInsideWindow() { + return isMouseOverWindow; + } + + @Import(module = "platformOpenGL", name = "isContextLost") + public static native boolean contextLost(); + + private static int processFunctionKeys(int key) { + if(keyboardIsKeyDown(functionKeyModifier)) { + if(key >= 49 && key <= 57) { + key = key - 49 + 112; + } + } + return key; + } + + public static void setFunctionKeyModifier(int key) { + functionKeyModifier = key; + } + + public static void clearEvenBuffers() { + mouseEvents.clear(); + keyEvents.clear(); + //touchEvents.clear(); + net.lax1dude.eaglercraft.v1_8.Gamepad.clearEventBuffer(); + } + + @Import(module = "platformInput", name = "supportsFullscreen") + private static native boolean supportsFullscreen0(); + + public static boolean supportsFullscreen() { + return fullscreenSupported; + } + + public static void toggleFullscreen() { + if(fullscreenSupported) { + toggleFullscreen0(); + } + } + + @Import(module = "platformInput", name = "toggleFullscreen") + private static native void toggleFullscreen0(); + + public static boolean isFullscreen() { + return fullscreenSupported && isFullscreen0(); + } + + @Import(module = "platformInput", name = "isFullscreen") + private static native boolean isFullscreen0(); + + @Import(module = "platformInput", name = "isVisualViewport") + private static native boolean isVisualViewport0(); + + public static void showCursor(EnumCursorType cursor) { + switch(cursor) { + case DEFAULT: + default: + canvas.getStyle().setProperty("cursor", "default"); + break; + case HAND: + canvas.getStyle().setProperty("cursor", "pointer"); + break; + case TEXT: + canvas.getStyle().setProperty("cursor", "text"); + break; + } + } + + public static boolean touchNext() { + currentTouchEvent = null; + return !touchEvents.isEmpty() && (currentTouchEvent = touchEvents.remove(0)) != null; + } + + public static EnumTouchEvent touchGetEventType() { + return currentTouchEvent != null ? currentTouchEvent.type : null; + } + + public static int touchGetEventTouchPointCount() { + return currentTouchEvent != null ? currentTouchEvent.getEventTouches().size() : 0; + } + + public static int touchGetEventTouchX(int pointId) { + return currentTouchEvent != null ? currentTouchEvent.getEventTouches().get(pointId).pointX : 0; + } + + public static int touchGetEventTouchY(int pointId) { + return currentTouchEvent != null ? currentTouchEvent.getEventTouches().get(pointId).pointY : 0; + } + + public static float touchGetEventTouchRadiusMixed(int pointId) { + if(currentTouchEvent != null) { + return currentTouchEvent.getEventTouches().get(pointId).radius; + }else { + return 1.0f; + } + } + + public static float touchGetEventTouchForce(int pointId) { + return currentTouchEvent != null ? (float)currentTouchEvent.getEventTouches().get(pointId).force : 0.0f; + } + + public static int touchGetEventTouchPointUID(int pointId) { + return currentTouchEvent != null ? currentTouchEvent.getEventTouches().get(pointId).uid : -1; + } + + public static int touchPointCount() { + return currentTouchState != null ? currentTouchState.getTargetTouchesSize() : 0; + } + + public static int touchPointX(int pointId) { + return currentTouchState != null ? currentTouchState.getTargetTouches().get(pointId).pointX : -1; + } + + public static int touchPointY(int pointId) { + return currentTouchState != null ? currentTouchState.getTargetTouches().get(pointId).pointY : -1; + } + + public static float touchRadiusMixed(int pointId) { + if(currentTouchState != null) { + return currentTouchState.getTargetTouches().get(pointId).radius; + }else { + return 1.0f; + } + } + + public static float touchForce(int pointId) { + return currentTouchState != null ? (float)currentTouchState.getTargetTouches().get(pointId).force : 0.0f; + } + + public static int touchPointUID(int pointId) { + return currentTouchState != null ? currentTouchState.getTargetTouches().get(pointId).uid : -1; + } + + public static String touchGetPastedString() { + return pastedStrings.isEmpty() ? null : pastedStrings.remove(0); + } + + @Import(module = "platformInput", name = "touchSetOpenKeyboardZone") + public static native void touchSetOpenKeyboardZone(int x, int y, int w, int h); + + @Import(module = "platformInput", name = "touchCloseDeviceKeyboard") + public static native void touchCloseDeviceKeyboard(); + + @Import(module = "platformInput", name = "touchIsDeviceKeyboardOpenMAYBE") + public static native boolean touchIsDeviceKeyboardOpenMAYBE(); + + private static void enumerateGamepads() { + if(!gamepadSupported) return; + if(selectedGamepad != null) { + selectedGamepad = updateGamepad(selectedGamepad); + if(selectedGamepad == null || !TeaVMUtils.isTruthy(selectedGamepad) || !selectedGamepad.isConnected()) { + selectedGamepad = null; + } + } + List oldList = null; + if(!gamepadList.isEmpty()) { + oldList = new ArrayList<>(gamepadList); + gamepadList.clear(); + } + Gamepad[] gamepads = Navigator.getGamepads(); + if(gamepads != null && gamepads.length > 0) { + for(int i = 0; i < gamepads.length; ++i) { + Gamepad g = gamepads[i]; + if(TeaVMUtils.isTruthy(g) && g.isConnected() && "standard".equals(g.getMapping())) { + gamepadList.add(g); + } + } + } + if(selectedGamepad != null) { + selectedGamepadName = selectedGamepad.getId(); + } + if(oldList == null) { + if(!gamepadList.isEmpty()) { + for(int i = 0, l = gamepadList.size(); i < l; ++i) { + PlatformRuntime.logger.info("Found controller: {}", gamepadList.get(i).getId()); + } + } + }else { + if(gamepadList.isEmpty()) { + for(int i = 0, l = oldList.size(); i < l; ++i) { + PlatformRuntime.logger.info("Lost controller: {}", oldList.get(i).getId()); + } + }else { + Map oldDevCounts = new HashMap<>(); + for(Gamepad gg : oldList) { + String s = gg.getId(); + Integer i = oldDevCounts.get(s); + if(i != null) { + oldDevCounts.put(s, Integer.valueOf(i.intValue() + 1)); + }else { + oldDevCounts.put(s, Integer.valueOf(1)); + } + } + Map newDevCounts = new HashMap<>(); + for(Gamepad gg : gamepadList) { + String s = gg.getId(); + Integer i = newDevCounts.get(s); + if(i != null) { + newDevCounts.put(s, Integer.valueOf(i.intValue() + 1)); + }else { + newDevCounts.put(s, Integer.valueOf(1)); + } + } + for(Entry etr : oldDevCounts.entrySet()) { + Integer i = newDevCounts.get(etr.getKey()); + if(i == null) { + for(int j = 0, l = etr.getValue().intValue(); j < l; ++j) { + PlatformRuntime.logger.info("Lost controller: {}", etr.getKey()); + } + }else { + int j = i.intValue(); + int k = etr.getValue().intValue(); + if(k != j) { + if(k < j) { + for(int m = 0, l = j - k; m < l; ++m) { + PlatformRuntime.logger.info("Found controller: {}", etr.getKey()); + } + }else { + for(int m = 0, l = k - j; m < l; ++m) { + PlatformRuntime.logger.info("Lost controller: {}", etr.getKey()); + } + } + } + } + } + for(Entry etr : newDevCounts.entrySet()) { + Integer i = oldDevCounts.get(etr.getKey()); + if(i == null) { + for(int j = 0, l = etr.getValue().intValue(); j < l; ++j) { + PlatformRuntime.logger.info("Found controller: {}", etr.getKey()); + } + } + } + } + + } + } + + public static int gamepadGetValidDeviceCount() { + if(!gamepadSupported) return 0; + return gamepadList.size(); + } + + public static String gamepadGetDeviceName(int deviceId) { + if(gamepadSupported && deviceId >= 0 && deviceId < gamepadList.size()) { + return gamepadList.get(deviceId).getId(); + }else { + return "Unknown"; + } + } + + public static void gamepadSetSelectedDevice(int deviceId) { + if(!gamepadSupported) return; + gamepadReset(); + if(deviceId >= 0 && deviceId < gamepadList.size()) { + selectedGamepad = gamepadList.get(deviceId); + gamepadTimestamp = -1.0; + if(!TeaVMUtils.isTruthy(selectedGamepad) || !selectedGamepad.isConnected()) { + selectedGamepad = null; + } + }else { + selectedGamepad = null; + } + } + + private static void gamepadReset() { + for(int i = 0; i < gamepadButtonStates.length; ++i) { + gamepadButtonStates[i] = false; + } + for(int i = 0; i < gamepadAxisStates.length; ++i) { + gamepadAxisStates[i] = 0.0f; + } + } + + @JSBody(params = { }, script = "return (typeof navigator.getGamepads === \"function\");") + private static native boolean isGamepadSupported(); + + @JSBody(params = { "gp" }, script = "return navigator.getGamepads()[gp.index];") + private static native Gamepad updateGamepad(Gamepad gp); + + public static void gamepadUpdate() { + if(!gamepadSupported) return; + if(selectedGamepad != null) { + selectedGamepad = updateGamepad(selectedGamepad); + if(selectedGamepad == null || !TeaVMUtils.isTruthy(selectedGamepad) || !selectedGamepad.isConnected()) { + gamepadReset(); + selectedGamepad = null; + return; + } + double ts = selectedGamepad.getTimestamp(); + if(ts != gamepadTimestamp) { + gamepadTimestamp = ts; + gamepadReset(); + GamepadButton[] btns = selectedGamepad.getButtons(); + for(int i = 0; i < btns.length; ++i) { + int j = GamepadConstants.getEaglerButtonFromBrowser(i); + if(j >= 0 && j < gamepadButtonStates.length) { + gamepadButtonStates[j] = btns[i].isPressed(); + } + } + double[] axes = selectedGamepad.getAxes(); + for(int i = 0; i < axes.length; ++i) { + if(i >= 4) { + break; + } + gamepadAxisStates[i] = (float)axes[i]; + } + } + }else { + gamepadReset(); + } + } + + public static boolean gamepadIsValid() { + if(!gamepadSupported) return false; + return selectedGamepad != null; + } + + public static String gamepadGetName() { + return gamepadSupported && selectedGamepad != null ? selectedGamepadName : "Unknown"; + } + + public static boolean gamepadGetButtonState(int button) { + return gamepadSupported && selectedGamepad != null && button >= 0 && button < gamepadButtonStates.length ? gamepadButtonStates[button] : false; + } + + public static float gamepadGetAxis(int axis) { + return gamepadSupported && selectedGamepad != null && axis >= 0 && axis < gamepadAxisStates.length ? gamepadAxisStates[axis] : 0.0f; + } + + public static float getDPI() { + return windowDPI; + } + + static void initWindowSize(int sw, int sh, float dpi) { + windowWidth = sw; + windowHeight = sh; + windowDPI = dpi; + visualViewportX = 0; + visualViewportY = 0; + visualViewportW = sw; + visualViewportH = sh; + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformNetworking.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformNetworking.java new file mode 100644 index 0000000..1916232 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformNetworking.java @@ -0,0 +1,72 @@ +package net.lax1dude.eaglercraft.v1_8.internal; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import org.teavm.interop.Import; +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; +import org.teavm.jso.core.JSString; +import org.teavm.jso.typedarrays.ArrayBuffer; +import org.teavm.jso.typedarrays.Uint8Array; + +import net.lax1dude.eaglercraft.v1_8.EagUtils; +import net.lax1dude.eaglercraft.v1_8.EaglerInputStream; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCBufferAllocator; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCDirectArrayConverter; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.BetterJSStringConverter; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.WASMGCWebSocketClient; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; + +/** + * 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. + * + */ +public class PlatformNetworking { + + private static final Logger logger = LogManager.getLogger("PlatformNetworking"); + + public static IWebSocketClient openWebSocket(String socketURI) { + IWebSocketClient client = openWebSocketImpl(socketURI); + if(client == null) { + logger.error("Could not open WebSocket to \"{}\"!", socketURI); + } + return client; + } + + public static IWebSocketClient openWebSocketUnsafe(String socketURI) { + IWebSocketClient client = openWebSocketImpl(socketURI); + if(client == null) { + throw new IllegalArgumentException("Could not open WebSocket to \"" + socketURI + "\"!"); + } + return client; + } + + public static IWebSocketClient openWebSocketImpl(String socketURI) { + WASMGCWebSocketClient.JSWebSocketClientHandle handle = createWebSocketHandle( + BetterJSStringConverter.stringToJS(socketURI)); + if(handle != null) { + return new WASMGCWebSocketClient(handle, socketURI); + }else { + return null; + } + } + + @Import(module = "platformNetworking", name = "createWebSocketHandle") + private static native WASMGCWebSocketClient.JSWebSocketClientHandle createWebSocketHandle(JSString socketURI); + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java new file mode 100644 index 0000000..753c284 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java @@ -0,0 +1,850 @@ +package net.lax1dude.eaglercraft.v1_8.internal; + +import java.util.Arrays; +import java.util.List; + +import org.teavm.interop.Import; +import org.teavm.jso.JSBody; +import org.teavm.jso.core.JSArray; +import org.teavm.jso.core.JSNumber; +import org.teavm.jso.core.JSString; +import org.teavm.jso.typedarrays.ArrayBufferView; +import org.teavm.jso.typedarrays.Float32Array; +import org.teavm.jso.webgl.WebGLBuffer; +import org.teavm.jso.webgl.WebGLFramebuffer; +import org.teavm.jso.webgl.WebGLProgram; +import org.teavm.jso.webgl.WebGLRenderbuffer; +import org.teavm.jso.webgl.WebGLShader; +import org.teavm.jso.webgl.WebGLTexture; +import org.teavm.jso.webgl.WebGLUniformLocation; + +import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.FloatBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.IntBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCBufferAllocator; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.BetterJSStringConverter; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.WebGLBackBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.WebGLQuery; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.WebGLVertexArray; + +/** + * 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. + * + */ +public class PlatformOpenGL { + + static int glesVers = -1; + + static final int VAO_IMPL_NONE = -1; + static final int VAO_IMPL_CORE = 0; + static final int VAO_IMPL_OES = 1; + static int vertexArrayImpl = VAO_IMPL_NONE; + + static final int INSTANCE_IMPL_NONE = -1; + static final int INSTANCE_IMPL_CORE = 0; + static final int INSTANCE_IMPL_ANGLE = 1; + static int instancingImpl = INSTANCE_IMPL_NONE; + + static final int CAP_A_BIT_EXT_GPU_SHADER5 = 1; + static final int CAP_A_BIT_OES_GPU_SHADER5 = 2; + static final int CAP_A_BIT_FBO_RENDER_MIPMAP = 4; + static final int CAP_A_BIT_TEXTURE_LOD_CAPABLE = 8; + static final int CAP_A_BIT_NPOT_CAPABLE = 16; + static final int CAP_A_BIT_HDR_FBO16F = 32; + static final int CAP_A_BIT_HDR_FBO32F = 64; + static final int CAP_A_BIT_ANISOTROPIC = 128; + static int capABits = 0; + + static final int CAP_B_BIT_HDR_LINEAR16F = 1; + static final int CAP_B_BIT_HDR_LINEAR32F = 2; + static int capBBits = 0; + + static void initContext() { + glesVers = getCapBits(0); + vertexArrayImpl = getCapBits(1); + instancingImpl = getCapBits(2); + capABits = getCapBits(3); + capBBits = getCapBits(4); + _wglClearColor(1.0f, 1.0f, 1.0f, 1.0f); + } + + @Import(module = "platformOpenGL", name = "getCapBits") + static native int getCapBits(int idx); + + @Import(module = "platformOpenGL", name = "glEnable") + public static native void _wglEnable(int glEnum); + + @Import(module = "platformOpenGL", name = "glDisable") + public static native void _wglDisable(int glEnum); + + @Import(module = "platformOpenGL", name = "glClearColor") + public static native void _wglClearColor(float r, float g, float b, float a); + + @Import(module = "platformOpenGL", name = "glClearDepth") + public static native void _wglClearDepth(float f); + + @Import(module = "platformOpenGL", name = "glClear") + public static native void _wglClear(int bits); + + @Import(module = "platformOpenGL", name = "glDepthFunc") + public static native void _wglDepthFunc(int glEnum); + + @Import(module = "platformOpenGL", name = "glDepthMask") + public static native void _wglDepthMask(boolean mask); + + @Import(module = "platformOpenGL", name = "glCullFace") + public static native void _wglCullFace(int glEnum); + + @Import(module = "platformOpenGL", name = "glViewport") + public static native void _wglViewport(int x, int y, int w, int h); + + @Import(module = "platformOpenGL", name = "glBlendFunc") + public static native void _wglBlendFunc(int src, int dst); + + @Import(module = "platformOpenGL", name = "glBlendFuncSeparate") + public static native void _wglBlendFuncSeparate(int srcColor, int dstColor, int srcAlpha, int dstAlpha); + + @Import(module = "platformOpenGL", name = "glBlendEquation") + public static native void _wglBlendEquation(int glEnum); + + @Import(module = "platformOpenGL", name = "glBlendColor") + public static native void _wglBlendColor(float r, float g, float b, float a); + + @Import(module = "platformOpenGL", name = "glColorMask") + public static native void _wglColorMask(boolean r, boolean g, boolean b, boolean a); + + private static final JSArray drawBuffers = new JSArray<>(); + + @JSBody(params = { "arr", "idx", "num" }, script = "arr[idx] = num;") + private static native void setArrayInt(JSArray arr, int idx, int num); + + public static final void _wglDrawBuffers(int buffer) { + if(glesVers == 200) { + if(buffer != 0x8CE0) { // GL_COLOR_ATTACHMENT0 + throw new UnsupportedOperationException(); + } + }else { + drawBuffers.setLength(1); + setArrayInt(drawBuffers, 0, buffer); + _wglDrawBuffersN(drawBuffers); + } + } + + public static final void _wglDrawBuffers(int[] buffers) { + if(glesVers == 200) { + if(buffers.length != 1 || buffers[0] != 0x8CE0) { // GL_COLOR_ATTACHMENT0 + throw new UnsupportedOperationException(); + } + }else { + int cnt = buffers.length; + drawBuffers.setLength(cnt); + for(int i = 0; i < cnt; ++i) { + setArrayInt(drawBuffers, i, buffers[i]); + } + _wglDrawBuffersN(drawBuffers); + } + } + + @Import(module = "platformOpenGL", name = "glDrawBuffers") + private static native void _wglDrawBuffersN(JSArray buffers); + + @Import(module = "platformOpenGL", name = "glReadBuffer") + public static native void _wglReadBuffer(int glEnum); + + public static final void _wglReadPixels(int x, int y, int width, int height, int format, int type, ByteBuffer buffer) { + _wglReadPixelsN(x, y, width, height, format, type, WASMGCBufferAllocator.getUnsignedByteBufferView(buffer)); + } + + public static final void _wglReadPixels_u16(int x, int y, int width, int height, int format, int type, ByteBuffer buffer) { + _wglReadPixelsN(x, y, width, height, format, type, WASMGCBufferAllocator.getUnsignedShortBufferView(buffer)); + } + + public static final void _wglReadPixels(int x, int y, int width, int height, int format, int type, IntBuffer buffer) { + _wglReadPixelsN(x, y, width, height, format, type, WASMGCBufferAllocator.getIntBufferView(buffer)); + } + + public static final void _wglReadPixels(int x, int y, int width, int height, int format, int type, FloatBuffer buffer) { + _wglReadPixelsN(x, y, width, height, format, type, WASMGCBufferAllocator.getFloatBufferView(buffer)); + } + + @Import(module = "platformOpenGL", name = "glReadPixels") + static native void _wglReadPixelsN(int x, int y, int width, int height, int format, int type, ArrayBufferView array); + + @Import(module = "platformOpenGL", name = "glPolygonOffset") + public static native void _wglPolygonOffset(float f1, float f2); + + @Import(module = "platformOpenGL", name = "glLineWidth") + public static native void _wglLineWidth(float width); + + public static final IBufferGL _wglGenBuffers() { + return new OpenGLObjects.BufferGL(_wglGenBuffersN()); + } + + @Import(module = "platformOpenGL", name = "glGenBuffers") + static native WebGLBuffer _wglGenBuffersN(); + + public static final ITextureGL _wglGenTextures() { + return new OpenGLObjects.TextureGL(_wglGenTexturesN()); + } + + @Import(module = "platformOpenGL", name = "glGenTextures") + static native WebGLTexture _wglGenTexturesN(); + + public static final IBufferArrayGL _wglGenVertexArrays() { + return new OpenGLObjects.BufferArrayGL(_wglGenVertexArraysN()); + } + + @Import(module = "platformOpenGL", name = "glGenVertexArrays") + public static native WebGLVertexArray _wglGenVertexArraysN(); + + public static final IProgramGL _wglCreateProgram() { + return new OpenGLObjects.ProgramGL(_wglCreateProgramN()); + } + + @Import(module = "platformOpenGL", name = "glCreateProgram") + static native WebGLProgram _wglCreateProgramN(); + + public static final IShaderGL _wglCreateShader(int type) { + return new OpenGLObjects.ShaderGL(_wglCreateShaderN(type)); + } + + @Import(module = "platformOpenGL", name = "glCreateShader") + static native WebGLShader _wglCreateShaderN(int type); + + public static final IFramebufferGL _wglCreateFramebuffer() { + return new OpenGLObjects.FramebufferGL(_wglCreateFramebufferN()); + } + + @Import(module = "platformOpenGL", name = "glCreateFramebuffer") + static native WebGLFramebuffer _wglCreateFramebufferN(); + + public static final IRenderbufferGL _wglCreateRenderbuffer() { + return new OpenGLObjects.RenderbufferGL(_wglCreateRenderbufferN()); + } + + @Import(module = "platformOpenGL", name = "glCreateRenderbuffer") + static native WebGLRenderbuffer _wglCreateRenderbufferN(); + + public static final IQueryGL _wglGenQueries() { + return new OpenGLObjects.QueryGL(_wglGenQueriesN()); + } + + @Import(module = "platformOpenGL", name = "glGenQueries") + static native WebGLQuery _wglGenQueriesN(); + + public static final void _wglDeleteBuffers(IBufferGL objId) { + _wglDeleteBuffersN(((OpenGLObjects.BufferGL)objId).ptr); + } + + @Import(module = "platformOpenGL", name = "glDeleteBuffers") + static native void _wglDeleteBuffersN(WebGLBuffer objId); + + public static final void _wglDeleteTextures(ITextureGL objId) { + _wglDeleteTexturesN(((OpenGLObjects.TextureGL)objId).ptr); + } + + @Import(module = "platformOpenGL", name = "glDeleteTextures") + static native void _wglDeleteTexturesN(WebGLTexture objId); + + public static final void _wglDeleteVertexArrays(IBufferArrayGL objId) { + _wglDeleteVertexArraysN(((OpenGLObjects.BufferArrayGL)objId).ptr); + } + + @Import(module = "platformOpenGL", name = "glDeleteVertexArrays") + static native void _wglDeleteVertexArraysN(WebGLVertexArray objId); + + public static final void _wglDeleteProgram(IProgramGL objId) { + _wglDeleteProgramN(((OpenGLObjects.ProgramGL)objId).ptr); + } + + @Import(module = "platformOpenGL", name = "glDeleteProgram") + static native void _wglDeleteProgramN(WebGLProgram objId); + + public static final void _wglDeleteShader(IShaderGL objId) { + _wglDeleteShaderN(((OpenGLObjects.ShaderGL)objId).ptr); + } + + @Import(module = "platformOpenGL", name = "glDeleteShader") + static native void _wglDeleteShaderN(WebGLShader objId); + + public static final void _wglDeleteFramebuffer(IFramebufferGL objId) { + _wglDeleteFramebufferN(((OpenGLObjects.FramebufferGL)objId).ptr); + } + + @Import(module = "platformOpenGL", name = "glDeleteFramebuffer") + static native void _wglDeleteFramebufferN(WebGLFramebuffer objId); + + public static final void _wglDeleteRenderbuffer(IRenderbufferGL objId) { + _wglDeleteRenderbufferN(((OpenGLObjects.RenderbufferGL)objId).ptr); + } + + @Import(module = "platformOpenGL", name = "glDeleteRenderbuffer") + static native void _wglDeleteRenderbufferN(WebGLRenderbuffer objId); + + public static final void _wglDeleteQueries(IQueryGL objId) { + _wglDeleteQueriesN(((OpenGLObjects.QueryGL)objId).ptr); + } + + @Import(module = "platformOpenGL", name = "glDeleteQueries") + static native void _wglDeleteQueriesN(WebGLQuery objId); + + public static final void _wglBindBuffer(int target, IBufferGL bufObj) { + _wglBindBufferN(target, bufObj != null ? ((OpenGLObjects.BufferGL)bufObj).ptr : null); + } + + @Import(module = "platformOpenGL", name = "glBindBuffer") + static native void _wglBindBufferN(int target, WebGLBuffer bufObj); + + @Import(module = "platformOpenGL", name = "glBufferData") + public static native void _wglBufferData(int target, int size, int usage); + + public static final void _wglBufferData(int target, ByteBuffer buffer, int usage) { + _wglBufferDataN(target, WASMGCBufferAllocator.getUnsignedByteBufferView(buffer), usage); + } + + public static final void _wglBufferData(int target, IntBuffer buffer, int usage) { + _wglBufferDataN(target, WASMGCBufferAllocator.getIntBufferView(buffer), usage); + } + + public static final void _wglBufferData(int target, FloatBuffer buffer, int usage) { + _wglBufferDataN(target, WASMGCBufferAllocator.getFloatBufferView(buffer), usage); + } + + @Import(module = "platformOpenGL", name = "glBufferData") + static native void _wglBufferDataN(int target, ArrayBufferView typedArray, int usage); + + public static final void _wglBufferSubData(int target, int dstOffset, ByteBuffer buffer) { + _wglBufferSubDataN(target, dstOffset, WASMGCBufferAllocator.getUnsignedByteBufferView(buffer)); + } + + public static final void _wglBufferSubData(int target, int dstOffset, IntBuffer buffer) { + _wglBufferSubDataN(target, dstOffset, WASMGCBufferAllocator.getIntBufferView(buffer)); + } + + public static final void _wglBufferSubData(int target, int dstOffset, FloatBuffer buffer) { + _wglBufferSubDataN(target, dstOffset, WASMGCBufferAllocator.getFloatBufferView(buffer)); + } + + @Import(module = "platformOpenGL", name = "glBufferSubData") + static native void _wglBufferSubDataN(int target, int dstOffset, ArrayBufferView typedArray); + + public static final void _wglBindVertexArray(IBufferArrayGL objId) { + _wglBindVertexArrayN(objId != null ? ((OpenGLObjects.BufferArrayGL)objId).ptr : null); + } + + @Import(module = "platformOpenGL", name = "glBindVertexArray") + static native void _wglBindVertexArrayN(WebGLVertexArray objId); + + @Import(module = "platformOpenGL", name = "glEnableVertexAttribArray") + public static native void _wglEnableVertexAttribArray(int index); + + @Import(module = "platformOpenGL", name = "glDisableVertexAttribArray") + public static native void _wglDisableVertexAttribArray(int index); + + @Import(module = "platformOpenGL", name = "glVertexAttribPointer") + public static native void _wglVertexAttribPointer(int index, int size, int type, + boolean normalized, int stride, int offset); + + @Import(module = "platformOpenGL", name = "glVertexAttribDivisor") + public static native void _wglVertexAttribDivisor(int index, int divisor); + + @Import(module = "platformOpenGL", name = "glActiveTexture") + public static native void _wglActiveTexture(int texture); + + public static final void _wglBindTexture(int target, ITextureGL objId) { + _wglBindTextureN(target, objId != null ? ((OpenGLObjects.TextureGL)objId).ptr : null); + } + + @Import(module = "platformOpenGL", name = "glBindTexture") + static native void _wglBindTextureN(int target, WebGLTexture objId); + + @Import(module = "platformOpenGL", name = "glTexParameterf") + public static native void _wglTexParameterf(int target, int param, float value); + + @Import(module = "platformOpenGL", name = "glTexParameteri") + public static native void _wglTexParameteri(int target, int param, int value); + + public static final void _wglTexImage3D(int target, int level, int internalFormat, int width, int height, int depth, + int border, int format, int type, ByteBuffer data) { + _wglTexImage3DN(target, level, internalFormat, width, height, depth, border, format, type, + data != null ? WASMGCBufferAllocator.getUnsignedByteBufferView(data) : null); + } + + @Import(module = "platformOpenGL", name = "glTexImage3D") + static native void _wglTexImage3DN(int target, int level, int internalFormat, int width, int height, int depth, + int border, int format, int type, ArrayBufferView typedArray); + + public static final void _wglTexImage2D(int target, int level, int internalFormat, int width, int height, int border, + int format, int type, ByteBuffer data) { + _wglTexImage2DN(target, level, internalFormat, width, height, border, format, type, + data != null ? WASMGCBufferAllocator.getUnsignedByteBufferView(data) : null); + } + + public static final void _wglTexImage2Du16(int target, int level, int internalFormat, int width, int height, int border, + int format, int type, ByteBuffer data) { + _wglTexImage2DN(target, level, internalFormat, width, height, border, format, type, + data != null ? WASMGCBufferAllocator.getUnsignedShortBufferView(data) : null); + } + + public static final void _wglTexImage2Df32(int target, int level, int internalFormat, int width, int height, int border, + int format, int type, ByteBuffer data) { + _wglTexImage2DN(target, level, internalFormat, width, height, border, format, type, + data != null ? WASMGCBufferAllocator.getFloatBufferView(data) : null); + } + + public static final void _wglTexImage2D(int target, int level, int internalFormat, int width, + int height, int border, int format, int type, IntBuffer data) { + _wglTexImage2DN(target, level, internalFormat, width, height, border, format, type, + data != null ? WASMGCBufferAllocator.getUnsignedByteBufferView(data) : null); + } + + public static final void _wglTexImage2Df32(int target, int level, int internalFormat, int width, + int height, int border, int format, int type, FloatBuffer data) { + _wglTexImage2DN(target, level, internalFormat, width, height, border, format, type, + data != null ? WASMGCBufferAllocator.getFloatBufferView(data) : null); + } + + @Import(module = "platformOpenGL", name = "glTexImage2D") + static native void _wglTexImage2DN(int target, int level, int internalFormat, int width, int height, int border, + int format, int type, ArrayBufferView typedArray); + + public static final void _wglTexSubImage2D(int target, int level, int xoffset, int yoffset, + int width, int height, int format, int type, ByteBuffer data) { + _wglTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, + data != null ? WASMGCBufferAllocator.getUnsignedByteBufferView(data) : null); + } + + public static final void _wglTexSubImage2Du16(int target, int level, int xoffset, int yoffset, + int width, int height, int format, int type, ByteBuffer data) { + _wglTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, + data != null ? WASMGCBufferAllocator.getUnsignedShortBufferView(data) : null); + } + + public static final void _wglTexSubImage2D(int target, int level, int xoffset, int yoffset, + int width, int height, int format, int type, IntBuffer data) { + _wglTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, + data != null ? WASMGCBufferAllocator.getUnsignedByteBufferView(data) : null); + } + + public static final void _wglTexSubImage2Df32(int target, int level, int xoffset, int yoffset, + int width, int height, int format, int type, FloatBuffer data) { + _wglTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, + data != null ? WASMGCBufferAllocator.getFloatBufferView(data) : null); + } + + @Import(module = "platformOpenGL", name = "glTexSubImage2D") + static native void _wglTexSubImage2D(int target, int level, int offsetx, int offsety, int width, int height, + int format, int type, ArrayBufferView typedArray); + + @Import(module = "platformOpenGL", name = "glCopyTexSubImage2D") + public static native void _wglCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, + int x, int y, int width, int height); + + @Import(module = "platformOpenGL", name = "glTexStorage2D") + public static native void _wglTexStorage2D(int target, int levels, int internalFormat, int w, int h); + + @Import(module = "platformOpenGL", name = "glPixelStorei") + public static native void _wglPixelStorei(int pname, int value); + + @Import(module = "platformOpenGL", name = "glGenerateMipmap") + public static native void _wglGenerateMipmap(int target); + + public static final void _wglShaderSource(IShaderGL shader, String str) { + _wglShaderSourceN(((OpenGLObjects.ShaderGL)shader).ptr, BetterJSStringConverter.stringToJS(str)); + } + + @Import(module = "platformOpenGL", name = "glShaderSource") + static native void _wglShaderSourceN(WebGLShader shader, JSString str); + + public static final void _wglCompileShader(IShaderGL shader) { + _wglCompileShaderN(((OpenGLObjects.ShaderGL)shader).ptr); + } + + @Import(module = "platformOpenGL", name = "glCompileShader") + static native void _wglCompileShaderN(WebGLShader shader); + + public static final int _wglGetShaderi(IShaderGL shader, int param) { + return _wglGetShaderiN(((OpenGLObjects.ShaderGL)shader).ptr, param); + } + + @Import(module = "platformOpenGL", name = "glGetShaderi") + static native int _wglGetShaderiN(WebGLShader shader, int param); + + public static final String _wglGetShaderInfoLog(IShaderGL shader) { + return BetterJSStringConverter.stringFromJS(_wglGetShaderInfoLogN(((OpenGLObjects.ShaderGL)shader).ptr)); + } + + @Import(module = "platformOpenGL", name = "glGetShaderInfoLog") + static native JSString _wglGetShaderInfoLogN(WebGLShader shader); + + public static final void _wglUseProgram(IProgramGL prog) { + _wglUseProgramN(prog != null ? ((OpenGLObjects.ProgramGL)prog).ptr : null); + } + + @Import(module = "platformOpenGL", name = "glUseProgram") + static native void _wglUseProgramN(WebGLProgram prog); + + public static final void _wglAttachShader(IProgramGL prog, IShaderGL shader) { + _wglAttachShaderN(((OpenGLObjects.ProgramGL)prog).ptr, ((OpenGLObjects.ShaderGL)shader).ptr); + } + + @Import(module = "platformOpenGL", name = "glAttachShader") + static native void _wglAttachShaderN(WebGLProgram prog, WebGLShader shader); + + public static final void _wglDetachShader(IProgramGL prog, IShaderGL shader) { + _wglDetachShaderN(((OpenGLObjects.ProgramGL)prog).ptr, ((OpenGLObjects.ShaderGL)shader).ptr); + } + + @Import(module = "platformOpenGL", name = "glDetachShader") + public static native void _wglDetachShaderN(WebGLProgram prog, WebGLShader shader); + + public static final void _wglLinkProgram(IProgramGL prog) { + _wglLinkProgramN(((OpenGLObjects.ProgramGL)prog).ptr); + } + + @Import(module = "platformOpenGL", name = "glLinkProgram") + static native void _wglLinkProgramN(WebGLProgram prog); + + public static final int _wglGetProgrami(IProgramGL prog, int param) { + return _wglGetProgramiN(((OpenGLObjects.ProgramGL)prog).ptr, param); + } + + @Import(module = "platformOpenGL", name = "glGetProgrami") + static native int _wglGetProgramiN(WebGLProgram prog, int param); + + public static final String _wglGetProgramInfoLog(IProgramGL prog) { + return BetterJSStringConverter.stringFromJS(_wglGetProgramInfoLogN(((OpenGLObjects.ProgramGL)prog).ptr)); + } + + @Import(module = "platformOpenGL", name = "glGetProgramInfoLog") + static native JSString _wglGetProgramInfoLogN(WebGLProgram prog); + + @Import(module = "platformOpenGL", name = "glDrawArrays") + public static native void _wglDrawArrays(int mode, int first, int count); + + @Import(module = "platformOpenGL", name = "glDrawElements") + public static native void _wglDrawElements(int mode, int count, int type, int offset); + + @Import(module = "platformOpenGL", name = "glDrawArraysInstanced") + public static native void _wglDrawArraysInstanced(int mode, int first, int count, int instanced); + + @Import(module = "platformOpenGL", name = "glDrawElementsInstanced") + public static native void _wglDrawElementsInstanced(int mode, int count, int type, int offset, int instanced); + + public static final void _wglBindAttribLocation(IProgramGL prog, int index, String str) { + _wglBindAttribLocationN(((OpenGLObjects.ProgramGL)prog).ptr, index, BetterJSStringConverter.stringToJS(str)); + } + + @Import(module = "platformOpenGL", name = "glBindAttribLocation") + static native void _wglBindAttribLocationN(WebGLProgram prog, int index, JSString str); + + public static final int _wglGetAttribLocation(IProgramGL prog, String str) { + return _wglGetAttribLocationN(((OpenGLObjects.ProgramGL)prog).ptr, BetterJSStringConverter.stringToJS(str)); + } + + @Import(module = "platformOpenGL", name = "glGetAttribLocation") + static native int _wglGetAttribLocationN(WebGLProgram prog, JSString str); + + public static final IUniformGL _wglGetUniformLocation(IProgramGL prog, String str) { + WebGLUniformLocation ret = _wglGetUniformLocationN(((OpenGLObjects.ProgramGL)prog).ptr, BetterJSStringConverter.stringToJS(str)); + return ret != null ? new OpenGLObjects.UniformGL(ret) : null; + } + + @Import(module = "platformOpenGL", name = "glGetUniformLocation") + static native WebGLUniformLocation _wglGetUniformLocationN(WebGLProgram prog, JSString str); + + public static final int _wglGetUniformBlockIndex(IProgramGL prog, String str) { + return _wglGetUniformBlockIndexN(((OpenGLObjects.ProgramGL)prog).ptr, BetterJSStringConverter.stringToJS(str)); + } + + @Import(module = "platformOpenGL", name = "glGetUniformBlockIndex") + static native int _wglGetUniformBlockIndexN(WebGLProgram prog, JSString str); + + public static final void _wglBindBufferRange(int target, int index, IBufferGL bufferId, int offset, int size) { + _wglBindBufferRangeN(target, index, ((OpenGLObjects.BufferGL)bufferId).ptr, offset, size); + } + + @Import(module = "platformOpenGL", name = "glBindBufferRange") + static native void _wglBindBufferRangeN(int target, int index, WebGLBuffer bufferId, int offset, int size); + + public static final void _wglUniformBlockBinding(IProgramGL prog, int blockIndex, int bufferIndex) { + _wglUniformBlockBindingN(((OpenGLObjects.ProgramGL)prog).ptr, blockIndex, bufferIndex); + } + + @Import(module = "platformOpenGL", name = "glUniformBlockBinding") + static native void _wglUniformBlockBindingN(WebGLProgram prog, int blockIndex, int bufferIndex); + + public static final void _wglUniform1f(IUniformGL uniformIndex, float x) { + if(uniformIndex != null) _wglUniform1fN(((OpenGLObjects.UniformGL)uniformIndex).ptr, x); + } + + @Import(module = "platformOpenGL", name = "glUniform1f") + public static native void _wglUniform1fN(WebGLUniformLocation uniformIndex, float x); + + public static final void _wglUniform2f(IUniformGL uniformIndex, float x, float y) { + if(uniformIndex != null) _wglUniform2fN(((OpenGLObjects.UniformGL)uniformIndex).ptr, x, y); + } + + @Import(module = "platformOpenGL", name = "glUniform2f") + public static native void _wglUniform2fN(WebGLUniformLocation uniformIndex, float x, float y); + + public static final void _wglUniform3f(IUniformGL uniformIndex, float x, float y, float z) { + if(uniformIndex != null) _wglUniform3fN(((OpenGLObjects.UniformGL)uniformIndex).ptr, x, y, z); + } + + @Import(module = "platformOpenGL", name = "glUniform3f") + public static native void _wglUniform3fN(WebGLUniformLocation uniformIndex, float x, float y, float z); + + public static final void _wglUniform4f(IUniformGL uniformIndex, float x, float y, float z, float w) { + if(uniformIndex != null) _wglUniform4fN(((OpenGLObjects.UniformGL)uniformIndex).ptr, x, y, z, w); + } + + @Import(module = "platformOpenGL", name = "glUniform4f") + public static native void _wglUniform4fN(WebGLUniformLocation uniformIndex, float x, float y, float z, float w); + + public static final void _wglUniform1i(IUniformGL uniformIndex, int x) { + if(uniformIndex != null) _wglUniform1iN(((OpenGLObjects.UniformGL)uniformIndex).ptr, x); + } + + @Import(module = "platformOpenGL", name = "glUniform1i") + public static native void _wglUniform1iN(WebGLUniformLocation uniformIndex, int x); + + public static final void _wglUniform2i(IUniformGL uniformIndex, int x, int y) { + if(uniformIndex != null) _wglUniform2iN(((OpenGLObjects.UniformGL)uniformIndex).ptr, x, y); + } + + @Import(module = "platformOpenGL", name = "glUniform2i") + public static native void _wglUniform2iN(WebGLUniformLocation uniformIndex, int x, int y); + + public static final void _wglUniform3i(IUniformGL uniformIndex, int x, int y, int z) { + if(uniformIndex != null) _wglUniform3iN(((OpenGLObjects.UniformGL)uniformIndex).ptr, x, y, z); + } + + @Import(module = "platformOpenGL", name = "glUniform3i") + public static native void _wglUniform3iN(WebGLUniformLocation uniformIndex, int x, int y, int z); + + public static final void _wglUniform4i(IUniformGL uniformIndex, int x, int y, int z, int w) { + if(uniformIndex != null) _wglUniform4iN(((OpenGLObjects.UniformGL)uniformIndex).ptr, x, y, z, w); + } + + @Import(module = "platformOpenGL", name = "glUniform4i") + public static native void _wglUniform4iN(WebGLUniformLocation uniformIndex, int x, int y, int z, int w); + + public static final void _wglUniformMatrix2fv(IUniformGL uniformIndex, boolean transpose, FloatBuffer buffer) { + if (uniformIndex != null) + _wglUniformMatrix2fvN(((OpenGLObjects.UniformGL) uniformIndex).ptr, transpose, + WASMGCBufferAllocator.getFloatBufferView(buffer)); + } + + @Import(module = "platformOpenGL", name = "glUniformMatrix2fv") + static native void _wglUniformMatrix2fvN(WebGLUniformLocation uniformIndex, boolean transpose, Float32Array typedArray); + + public static final void _wglUniformMatrix3fv(IUniformGL uniformIndex, boolean transpose, FloatBuffer buffer) { + if (uniformIndex != null) + _wglUniformMatrix3fvN(((OpenGLObjects.UniformGL) uniformIndex).ptr, transpose, + WASMGCBufferAllocator.getFloatBufferView(buffer)); + } + + @Import(module = "platformOpenGL", name = "glUniformMatrix3fv") + static native void _wglUniformMatrix3fvN(WebGLUniformLocation uniformIndex, boolean transpose, Float32Array typedArray); + + public static final void _wglUniformMatrix4fv(IUniformGL uniformIndex, boolean transpose, FloatBuffer buffer) { + if (uniformIndex != null) + _wglUniformMatrix4fvN(((OpenGLObjects.UniformGL) uniformIndex).ptr, transpose, + WASMGCBufferAllocator.getFloatBufferView(buffer)); + } + + @Import(module = "platformOpenGL", name = "glUniformMatrix4fv") + static native void _wglUniformMatrix4fvN(WebGLUniformLocation uniformIndex, boolean transpose, Float32Array typedArray); + + public static final void _wglUniformMatrix3x2fv(IUniformGL uniformIndex, boolean transpose, FloatBuffer buffer) { + if (uniformIndex != null) + _wglUniformMatrix3x2fvN(((OpenGLObjects.UniformGL) uniformIndex).ptr, transpose, + WASMGCBufferAllocator.getFloatBufferView(buffer)); + } + + @Import(module = "platformOpenGL", name = "glUniformMatrix3x2fv") + static native void _wglUniformMatrix3x2fvN(WebGLUniformLocation uniformIndex, boolean transpose, Float32Array typedArray); + + public static final void _wglUniformMatrix4x2fv(IUniformGL uniformIndex, boolean transpose, FloatBuffer buffer) { + if (uniformIndex != null) + _wglUniformMatrix4x2fvN(((OpenGLObjects.UniformGL) uniformIndex).ptr, transpose, + WASMGCBufferAllocator.getFloatBufferView(buffer)); + } + + @Import(module = "platformOpenGL", name = "glUniformMatrix4x2fv") + static native void _wglUniformMatrix4x2fvN(WebGLUniformLocation uniformIndex, boolean transpose, Float32Array typedArray); + + public static final void _wglUniformMatrix4x3fv(IUniformGL uniformIndex, boolean transpose, FloatBuffer buffer) { + if (uniformIndex != null) + _wglUniformMatrix4x3fvN(((OpenGLObjects.UniformGL) uniformIndex).ptr, transpose, + WASMGCBufferAllocator.getFloatBufferView(buffer)); + } + + @Import(module = "platformOpenGL", name = "glUniformMatrix4x3fv") + static native void _wglUniformMatrix4x3fvN(WebGLUniformLocation uniformIndex, boolean transpose, Float32Array typedArray); + + public static final void _wglBindFramebuffer(int target, IFramebufferGL framebuffer) { + if(framebuffer == null) { + framebuffer = WebGLBackBuffer.getBackBuffer(); + } + _wglBindFramebufferN(target, ((OpenGLObjects.FramebufferGL)framebuffer).ptr); + } + + public static final void _wglBindFramebufferLow(int target, IFramebufferGL framebuffer) { + _wglBindFramebufferN(target, framebuffer != null ? ((OpenGLObjects.FramebufferGL)framebuffer).ptr : null); + } + + @Import(module = "platformOpenGL", name = "glBindFramebuffer") + static native void _wglBindFramebufferN(int target, WebGLFramebuffer framebuffer); + + @Import(module = "platformOpenGL", name = "glCheckFramebufferStatus") + public static native int _wglCheckFramebufferStatus(int target); + + @Import(module = "platformOpenGL", name = "glBlitFramebuffer") + public static native void _wglBlitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, + int dstX1, int dstY1, int bits, int filter); + + @Import(module = "platformOpenGL", name = "glRenderbufferStorage") + public static native void _wglRenderbufferStorage(int target, int internalformat, int width, int height); + + public static final void _wglFramebufferTexture2D(int target, int attachment, int texTarget, ITextureGL texObj, int level) { + _wglFramebufferTexture2DN(target, attachment, texTarget, ((OpenGLObjects.TextureGL)texObj).ptr, level); + } + + @Import(module = "platformOpenGL", name = "glFramebufferTexture2D") + static native void _wglFramebufferTexture2DN(int target, int attachment, int texTarget, WebGLTexture texObj, int level); + + public static final void _wglFramebufferTextureLayer(int target, int attachment, ITextureGL texObj, int level, int layer) { + _wglFramebufferTextureLayerN(target, attachment, ((OpenGLObjects.TextureGL)texObj).ptr, level, layer); + } + + @Import(module = "platformOpenGL", name = "glFramebufferTextureLayer") + static native void _wglFramebufferTextureLayerN(int target, int attachment, WebGLTexture texObj, int level, int layer); + + public static final void _wglBindRenderbuffer(int target, IRenderbufferGL renderbuffer) { + _wglBindRenderbufferN(target, renderbuffer != null ? ((OpenGLObjects.RenderbufferGL)renderbuffer).ptr : null); + } + + @Import(module = "platformOpenGL", name = "glBindRenderbuffer") + static native void _wglBindRenderbufferN(int target, WebGLRenderbuffer renderbuffer); + + public static final void _wglFramebufferRenderbuffer(int target, int attachment, int renderbufferTarget, + IRenderbufferGL renderbufferId) { + _wglFramebufferRenderbufferN(target, attachment, renderbufferTarget, + renderbufferId != null ? ((OpenGLObjects.RenderbufferGL) renderbufferId).ptr : null); + } + + @Import(module = "platformOpenGL", name = "glFramebufferRenderbuffer") + static native void _wglFramebufferRenderbufferN(int target, int attachment, int renderbufferTarget, WebGLRenderbuffer renderbufferId); + + public static final String _wglGetString(int param) { + return BetterJSStringConverter.stringFromJS(_wglGetStringN(param)); + } + + @Import(module = "platformOpenGL", name = "glGetString") + static native JSString _wglGetStringN(int param); + + @Import(module = "platformOpenGL", name = "glGetInteger") + public static native int _wglGetInteger(int param); + + @Import(module = "platformOpenGL", name = "glGetError") + public static native int _wglGetError(); + + public static final int checkOpenGLESVersion() { + return glesVers; + } + + public static final boolean checkEXTGPUShader5Capable() { + return (capABits & CAP_A_BIT_EXT_GPU_SHADER5) != 0; + } + + public static final boolean checkOESGPUShader5Capable() { + return (capABits & CAP_A_BIT_OES_GPU_SHADER5) != 0; + } + + public static final boolean checkFBORenderMipmapCapable() { + return (capABits & CAP_A_BIT_FBO_RENDER_MIPMAP) != 0; + } + + public static final boolean checkVAOCapable() { + return vertexArrayImpl != VAO_IMPL_NONE; + } + + public static final boolean checkInstancingCapable() { + return instancingImpl != INSTANCE_IMPL_NONE; + } + + public static final boolean checkTexStorageCapable() { + return glesVers >= 300; + } + + public static final boolean checkTextureLODCapable() { + return (capABits & CAP_A_BIT_TEXTURE_LOD_CAPABLE) != 0; + } + + public static final boolean checkNPOTCapable() { + return (capABits & CAP_A_BIT_NPOT_CAPABLE) != 0; + } + + public static final boolean checkHDRFramebufferSupport(int bits) { + switch(bits) { + case 16: + return (capABits & CAP_A_BIT_HDR_FBO16F) != 0; + case 32: + return (capABits & CAP_A_BIT_HDR_FBO32F) != 0; + default: + return false; + } + } + + public static final boolean checkLinearHDRFilteringSupport(int bits) { + switch(bits) { + case 16: + return (capBBits & CAP_B_BIT_HDR_LINEAR16F) != 0; + case 32: + return (capBBits & CAP_B_BIT_HDR_LINEAR32F) != 0; + default: + return false; + } + } + + // legacy + public static final boolean checkLinearHDR32FSupport() { + return (capBBits & CAP_B_BIT_HDR_LINEAR32F) != 0; + } + + public static final boolean checkAnisotropicFilteringSupport() { + return (capABits & CAP_A_BIT_ANISOTROPIC) != 0; + } + + public static final String[] getAllExtensions() { + return BetterJSStringConverter.stringArrayFromJS(getAllExtensions0()); + } + + @Import(module = "platformOpenGL", name = "getAllExtensions") + static native JSArray getAllExtensions0(); + + public static final List dumpActiveExtensions() { + return Arrays.asList(BetterJSStringConverter.stringArrayFromJS(dumpActiveExtensions0())); + } + + @Import(module = "platformOpenGL", name = "dumpActiveExtensions") + static native JSArray dumpActiveExtensions0(); + + public static final void enterVAOEmulationHook() { + WebGLBackBuffer.enterVAOEmulationPhase(); + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformRuntime.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformRuntime.java new file mode 100644 index 0000000..923b564 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformRuntime.java @@ -0,0 +1,491 @@ +package net.lax1dude.eaglercraft.v1_8.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +import org.teavm.interop.Import; +import org.teavm.jso.JSBody; +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; +import org.teavm.jso.browser.Window; +import org.teavm.jso.core.JSString; +import org.teavm.jso.dom.html.HTMLCanvasElement; +import org.teavm.jso.dom.html.HTMLDocument; +import org.teavm.jso.dom.html.HTMLElement; +import org.teavm.jso.typedarrays.ArrayBuffer; +import org.teavm.jso.typedarrays.Uint8Array; + +import com.jcraft.jzlib.DeflaterOutputStream; +import com.jcraft.jzlib.GZIPInputStream; +import com.jcraft.jzlib.GZIPOutputStream; +import com.jcraft.jzlib.InflaterInputStream; + +import net.lax1dude.eaglercraft.v1_8.Filesystem; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.FloatBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.IntBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCBufferAllocator; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCDirectArrayConverter; +import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFile2; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.BetterJSStringConverter; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.EarlyLoadScreen; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.WASMGCClientConfigAdapter; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.WebGLBackBuffer; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; +import net.lax1dude.eaglercraft.v1_8.minecraft.EaglerFolderResourcePack; +import net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums; + +/** + * Copyright (c) 2022-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. + * + */ +public class PlatformRuntime { + + static final Logger logger = LogManager.getLogger("RuntimeWASMGC"); + + public static Window win = null; + public static HTMLDocument doc = null; + public static HTMLElement root = null; + public static HTMLElement parent = null; + public static HTMLCanvasElement canvas = null; + public static boolean webglExperimental = false; + + public static void create() { + win = Window.current(); + doc = win.getDocument(); + root = getRootElement(); + parent = getParentElement(); + canvas = getCanvasElement(); + PlatformApplication.setMCServerWindowGlobal(null); + PlatformOpenGL.initContext(); + PlatformInput.initContext(win, parent, canvas); + + // Should contain an event to update the initial screen size + pollJSEventsAfterSleep(); + + WebGLBackBuffer.initBackBuffer(PlatformInput.getWindowWidth(), PlatformInput.getWindowHeight()); + + HTMLElement el = parent.querySelector("._eaglercraftX_early_splash_element"); + if(el != null) { + el.delete(); + } + + EarlyLoadScreen.extractingAssetsScreen(); + sleep(20); + + PlatformAssets.readAssetsTeaVM(); + + byte[] finalLoadScreen = PlatformAssets.getResourceBytes("/assets/eagler/eagtek.png"); + + if(finalLoadScreen != null) { + EarlyLoadScreen.loadFinal(finalLoadScreen); + EarlyLoadScreen.paintFinal(false); + }else { + PlatformOpenGL._wglClearColor(1.0f, 0.0f, 1.0f, 1.0f); + PlatformOpenGL._wglClear(RealOpenGLEnums.GL_COLOR_BUFFER_BIT); + PlatformInput.update(); + } + sleep(20); + + EarlyLoadScreen.destroy(); + + logger.info("Initializing filesystem..."); + + IEaglerFilesystem resourcePackFilesystem = Filesystem.getHandleFor(getClientConfigAdapter().getResourcePacksDB()); + VFile2.setPrimaryFilesystem(resourcePackFilesystem); + EaglerFolderResourcePack.setSupported(true); + + logger.info("Initializing sound engine..."); + + PlatformAudio.initialize(); + PlatformScreenRecord.initContext(win, canvas); + + PlatformWebRTC.initialize(); + PlatformVoiceClient.initialize(); + PlatformWebView.initialize(); + } + + @Import(module = "platformRuntime", name = "getRootElement") + private static native HTMLElement getRootElement(); + + @Import(module = "platformRuntime", name = "getParentElement") + private static native HTMLElement getParentElement(); + + @Import(module = "platformRuntime", name = "getCanvasElement") + private static native HTMLCanvasElement getCanvasElement(); + + public static void destroy() { + logger.fatal("Game tried to destroy the context! Browser runtime can't do that"); + } + + public static EnumPlatformType getPlatformType() { + return EnumPlatformType.WASM_GC; + } + + public static EnumPlatformAgent getPlatformAgent() { + return EnumPlatformAgent.getFromUA(getUserAgentString()); + } + + @JSBody(params = { }, script = "return navigator.userAgent||null;") + public static native String getUserAgentString(); + + public static EnumPlatformOS getPlatformOS() { + return EnumPlatformOS.getFromUA(getUserAgentString()); + } + + @JSBody(params = { }, script = "return location.protocol && location.protocol.toLowerCase() === \"https:\";") + public static native boolean requireSSL(); + + @JSBody(params = { }, script = "return location.protocol && location.protocol.toLowerCase() === \"file:\";") + public static native boolean isOfflineDownloadURL(); + + public static void requestANGLE(EnumPlatformANGLE plaf) { + } + + public static EnumPlatformANGLE getPlatformANGLE() { + return EnumPlatformANGLE.fromGLRendererString(getGLRenderer()); + } + + public static String getGLVersion() { + return PlatformOpenGL._wglGetString(RealOpenGLEnums.GL_VERSION); + } + + public static String getGLRenderer() { + return PlatformOpenGL._wglGetString(RealOpenGLEnums.GL_RENDERER); + } + + public static ByteBuffer allocateByteBuffer(int length) { + return WASMGCBufferAllocator.allocateByteBuffer(length); + } + + public static IntBuffer allocateIntBuffer(int length) { + return WASMGCBufferAllocator.allocateIntBuffer(length); + } + + public static FloatBuffer allocateFloatBuffer(int length) { + return WASMGCBufferAllocator.allocateFloatBuffer(length); + } + + public static ByteBuffer castPrimitiveByteArray(byte[] array) { + return null; + } + + public static IntBuffer castPrimitiveIntArray(int[] array) { + return null; + } + + public static FloatBuffer castPrimitiveFloatArray(float[] array) { + return null; + } + + public static byte[] castNativeByteBuffer(ByteBuffer buffer) { + return null; + } + + public static int[] castNativeIntBuffer(IntBuffer buffer) { + return null; + } + + public static float[] castNativeFloatBuffer(FloatBuffer buffer) { + return null; + } + + public static void freeByteBuffer(ByteBuffer byteBuffer) { + WASMGCBufferAllocator.freeByteBuffer(byteBuffer); + } + + public static void freeIntBuffer(IntBuffer intBuffer) { + WASMGCBufferAllocator.freeIntBuffer(intBuffer); + } + + public static void freeFloatBuffer(FloatBuffer floatBuffer) { + WASMGCBufferAllocator.freeFloatBuffer(floatBuffer); + } + + private interface JSAsyncDownloadEvent extends JSObject { + + @JSProperty + int getRequestId(); + + @JSProperty + ArrayBuffer getArrayBuffer(); + + } + + private static final int EVENT_TYPE_INPUT = 0; + private static final int EVENT_TYPE_RUNTIME = 1; + private static final int EVENT_TYPE_VOICE = 2; + private static final int EVENT_TYPE_WEBVIEW = 3; + + public interface JSEagRuntimeEvent extends JSObject { + + @JSProperty + int getEventType(); + + @JSProperty + T getEventObj(); + + } + + static void pollJSEvents() { + int cnt = getEventCount(); + while(cnt-- > 0) { + JSEagRuntimeEvent evt = getNextEvent(); + if(evt != null) { + switch(evt.getEventType() >>> 5) { + case EVENT_TYPE_INPUT: + PlatformInput.handleJSEvent(evt); + break; + case EVENT_TYPE_RUNTIME: + handleJSEvent(evt); + break; + case EVENT_TYPE_VOICE: + PlatformVoiceClient.handleJSEvent(evt); + break; + case EVENT_TYPE_WEBVIEW: + PlatformWebView.handleJSEvent(evt); + break; + default: + break; + } + }else { + break; + } + } + pollAsyncCallbacksTeaVM(); + } + + private static boolean isWakingUpFromSleep = false; + + static void pollJSEventsAfterSleep() { + if(!isWakingUpFromSleep) { + try { + isWakingUpFromSleep = true; + pollJSEvents(); + }finally { + isWakingUpFromSleep = false; + } + } + } + + private static final int EVENT_RUNTIME_ASYNC_DOWNLOAD = 0; + + private static void handleJSEvent(PlatformRuntime.JSEagRuntimeEvent evt) { + switch(evt.getEventType() & 31) { + case EVENT_RUNTIME_ASYNC_DOWNLOAD: { + JSAsyncDownloadEvent obj = evt.getEventObj(); + int id = obj.getRequestId(); + Consumer handler = waitingAsyncDownloads.get(id); + if(handler != null) { + handler.accept(obj.getArrayBuffer()); + }else { + logger.warn("Ignoring unknown async download result #{}", id); + } + break; + } + } + } + + @Import(module = "teavm", name = "pollAsyncCallbacks") + private static native JSEagRuntimeEvent pollAsyncCallbacksTeaVM(); + + @Import(module = "platformRuntime", name = "getEventCount") + private static native int getEventCount(); + + @Import(module = "platformRuntime", name = "getNextEvent") + private static native JSEagRuntimeEvent getNextEvent(); + + private static final Map> waitingAsyncDownloads = new HashMap<>(); + private static int asyncDownloadID = 0; + + private static void queueAsyncDownload(String uri, boolean forceCache, Consumer cb) { + int id = ++asyncDownloadID; + waitingAsyncDownloads.put(id, cb); + queueAsyncDownload0(BetterJSStringConverter.stringToJS(uri), forceCache, id); + } + + public static void downloadRemoteURIByteArray(String assetPackageURI, final Consumer cb) { + downloadRemoteURIByteArray(assetPackageURI, false, cb); + } + + public static void downloadRemoteURIByteArray(String assetPackageURI, boolean useCache, final Consumer cb) { + queueAsyncDownload(assetPackageURI, useCache, arr -> { + if(arr == null) { + cb.accept(null); + }else { + cb.accept(WASMGCDirectArrayConverter.externU8ArrayToByteArray(new Uint8Array(arr))); + } + }); + } + + public static void downloadRemoteURI(String assetPackageURI, Consumer cb) { + queueAsyncDownload(assetPackageURI, false, cb); + } + + public static void downloadRemoteURI(String assetPackageURI, boolean useCache, Consumer cb) { + queueAsyncDownload(assetPackageURI, useCache, cb); + } + + public static byte[] downloadRemoteURIByteArray(String assetPackageURI) { + return downloadRemoteURIByteArray(assetPackageURI, false); + } + + public static byte[] downloadRemoteURIByteArray(String assetPackageURI, boolean forceCache) { + ArrayBuffer arrayBuffer = downloadSync(BetterJSStringConverter.stringToJS(assetPackageURI), forceCache); + pollJSEventsAfterSleep(); + if(arrayBuffer == null) { + return null; + } + return WASMGCDirectArrayConverter.externU8ArrayToByteArray(new Uint8Array(arrayBuffer)); + } + + public static ArrayBuffer downloadRemoteURI(String assetPackageURI) { + ArrayBuffer ret = downloadSync(BetterJSStringConverter.stringToJS(assetPackageURI), false); + pollJSEventsAfterSleep(); + return ret; + } + + public static ArrayBuffer downloadRemoteURI(String assetPackageURI, boolean forceCache) { + ArrayBuffer ret = downloadSync(BetterJSStringConverter.stringToJS(assetPackageURI), forceCache); + pollJSEventsAfterSleep(); + return ret; + } + + @Import(module = "platformRuntime", name = "queueAsyncDownload") + private static native void queueAsyncDownload0(JSString uri, boolean forceCache, int id); + + @Import(module = "platformRuntime", name = "download") + private static native ArrayBuffer downloadSync(JSString uri, boolean forceCache); + + public static boolean isDebugRuntime() { + return false; + } + + public static void writeCrashReport(String crashDump) { + writeCrashReport0(BetterJSStringConverter.stringToJS(crashDump)); + } + + @Import(module = "platformRuntime", name = "writeCrashReport") + private static native void writeCrashReport0(JSString crashDump); + + public static void getStackTrace(Throwable t, Consumer ret) { + StackTraceElement[] el = t.getStackTrace(); + if(el.length > 0) { + for(int i = 0; i < el.length; ++i) { + ret.accept(el[i].toString()); + } + }else { + ret.accept("[no stack trace]"); + } + } + + public static boolean printJSExceptionIfBrowser(Throwable t) { + return false; + } + + private static String currentThreadName = "main"; + + public static String currentThreadName() { + return currentThreadName; + } + + public static void setThreadName(String string) { + currentThreadName = string; + } + + public static long maxMemory() { + return 1073741824l; + } + + public static long totalMemory() { + return 1073741824l; + } + + public static long freeMemory() { + return 1073741824l; + } + + public static String getCallingClass(int i) { + return null; + } + + public static long randomSeed() { + return (long)(Math.random() * 9007199254740991.0); + } + + public static void exit() { + logger.fatal("Game is attempting to exit!"); + } + + public static IClientConfigAdapter getClientConfigAdapter() { + return WASMGCClientConfigAdapter.instance; + } + + @Import(module = "platformRuntime", name = "steadyTimeMillis") + static native double steadyTimeMillis0(); + + public static long steadyTimeMillis() { + return (long)steadyTimeMillis0(); + } + + public static long nanoTime() { + return (long)(steadyTimeMillis0() * 1000000.0); + } + + public static void sleep(int millis) { + sleep0(millis); + pollJSEventsAfterSleep(); + } + + @Import(module = "platformRuntime", name = "sleep") + private static native void sleep0(int millis); + + public static void immediateContinue() { + immediateContinue0(); + pollJSEventsAfterSleep(); + } + + @Import(module = "platformRuntime", name = "immediateContinue") + private static native void immediateContinue0(); + + public static void postCreate() { + + } + + public static void setDisplayBootMenuNextRefresh(boolean en) { + + } + + public static OutputStream newDeflaterOutputStream(OutputStream os) throws IOException { + return new DeflaterOutputStream(os); + } + + public static OutputStream newGZIPOutputStream(OutputStream os) throws IOException { + return new GZIPOutputStream(os); + } + + public static InputStream newInflaterInputStream(InputStream is) throws IOException { + return new InflaterInputStream(is); + } + + public static InputStream newGZIPInputStream(InputStream is) throws IOException { + return new GZIPInputStream(is); + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformScreenRecord.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformScreenRecord.java new file mode 100644 index 0000000..07ae69e --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformScreenRecord.java @@ -0,0 +1,206 @@ +package net.lax1dude.eaglercraft.v1_8.internal; + +import java.util.EnumSet; +import java.util.Set; + +import org.teavm.interop.Import; +import org.teavm.jso.JSBody; +import org.teavm.jso.JSObject; +import org.teavm.jso.browser.Window; +import org.teavm.jso.canvas.CanvasRenderingContext2D; +import org.teavm.jso.core.JSString; +import org.teavm.jso.dom.html.HTMLCanvasElement; +import org.teavm.jso.webaudio.MediaStream; + +import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.BetterJSStringConverter; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; +import net.lax1dude.eaglercraft.v1_8.profile.EaglerProfile; +import net.lax1dude.eaglercraft.v1_8.recording.EnumScreenRecordingCodec; + +/** + * 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. + * + */ +public class PlatformScreenRecord { + + static final Logger logger = LogManager.getLogger("PlatformScreenRecord"); + + static Window win; + static HTMLCanvasElement canvas; + static boolean support; + static final Set supportedCodecs = EnumSet.noneOf(EnumScreenRecordingCodec.class); + static float currentGameVolume = 1.0f; + static float currentMicVolume = 0.0f; + static MediaStream recStream = null; + static HTMLCanvasElement downscaleCanvas = null; + static CanvasRenderingContext2D downscaleCanvasCtx = null; + static long lastDownscaleFrameCaptured = 0l; + static boolean currentMicLock = false; + static JSObject mediaRec = null; + static ScreenRecordParameters currentParameters = null; + + static void initContext(Window theWin, HTMLCanvasElement theCanvas) { + win = theWin; + canvas = theCanvas; + supportedCodecs.clear(); + support = hasMediaRecorder(theWin, theCanvas); + if(support) { + logger.info("MediaRecorder is supported, checking codecs..."); + EnumScreenRecordingCodec[] allCodecs = EnumScreenRecordingCodec.values(); + for(int i = 0; i < allCodecs.length; ++i) { + if(checkCodecSupported(theWin, allCodecs[i].mimeType)) { + supportedCodecs.add(allCodecs[i]); + } + } + if(!supportedCodecs.isEmpty()) { + logger.info("Found {} codecs that are probably supported!", supportedCodecs.size()); + }else { + logger.error("No supported codecs found!"); + support = false; + } + } + } + + @Import(module = "platformScreenRecord", name = "getMic") + public static native MediaStream getMic(); + + @JSBody(params = { "win", "canvas" }, script = "return (typeof win.MediaRecorder !== \"undefined\") && (typeof win.MediaRecorder.isTypeSupported === \"function\") && (typeof canvas.captureStream === \"function\");") + private static native boolean hasMediaRecorder(Window win, HTMLCanvasElement canvas); + + @JSBody(params = { "win", "mime" }, script = "return win.MediaRecorder.isTypeSupported(mime);") + private static native boolean checkCodecSupported(Window win, String mime); + + public static boolean isSupported() { + return support; + } + + public static boolean isCodecSupported(EnumScreenRecordingCodec codec) { + return supportedCodecs.contains(codec); + } + + static void captureFrameHook() { + if(mediaRec != null && currentParameters != null && currentParameters.resolutionDivisior > 1 && downscaleCanvas != null && downscaleCanvasCtx != null) { + if(currentParameters.captureFrameRate > 0) { + long curTime = PlatformRuntime.steadyTimeMillis(); + if(curTime - lastDownscaleFrameCaptured < (long)(1000 / currentParameters.captureFrameRate)) { + return; + } + lastDownscaleFrameCaptured = curTime; + } + int oldWidth = downscaleCanvas.getWidth(); + int oldHeight = downscaleCanvas.getHeight(); + float divisor = (float)Math.sqrt(1.0 / Math.pow(2.0, currentParameters.resolutionDivisior - 1)); + int newWidth = (int)(PlatformInput.getWindowWidth() * divisor); + int newHeight = (int)(PlatformInput.getWindowHeight() * divisor); + if(oldWidth != newWidth || oldHeight != newHeight) { + downscaleCanvas.setWidth(newWidth); + downscaleCanvas.setHeight(newHeight); + } + downscaleCanvasCtx.drawImage(canvas, 0, 0, newWidth, newHeight); + } + } + + public static void setGameVolume(float volume) { + currentGameVolume = volume; + if(PlatformAudio.gameRecGain != null) { + PlatformAudio.gameRecGain.getGain().setValue(volume); + } + } + + public static void setMicrophoneVolume(float volume) { + currentMicVolume = volume; + if(PlatformAudio.micRecGain != null) { + PlatformAudio.micRecGain.getGain().setValue(volume); + } + } + + @Import(module = "platformScreenRecord", name = "setDataAvailableHandler") + private static native void setupDataAvailableHandler(JSObject mediaRec, boolean isWebM, JSString nameStr); + + @JSBody(params = { }, script = "return { alpha: false, desynchronized: true };") + private static native JSObject youEagler(); + + @JSBody(params = { "canvas", "fps", "audio" }, script = "var stream = fps <= 0 ? canvas.captureStream() : canvas.captureStream(fps); stream.addTrack(audio.getTracks()[0]); return stream;") + private static native MediaStream captureStreamAndAddAudio(HTMLCanvasElement canvas, int fps, MediaStream audio); + + @JSBody(params = { "stream", "codec", "videoBitrate", "audioBitrate" }, script = "var rec = new MediaRecorder(stream, { mimeType: codec, videoBitsPerSecond: videoBitrate, audioBitsPerSecond: audioBitrate }); rec.start(); return rec;") + private static native JSObject createMediaRecorder(MediaStream stream, String codec, int videoBitrate, int audioBitrate); + + @JSBody(params = { "rec" }, script = "rec.stop();") + private static native void stopRec(JSObject rec); + + public static void startRecording(ScreenRecordParameters params) { + if(!support) { + throw new IllegalStateException("Screen recording is not supported"); + } + if(isRecording()) { + throw new IllegalStateException("Already recording!"); + } + if(params.captureFrameRate <= 0 && (!PlatformInput.vsync || !PlatformInput.vsyncSupport)) { + throw new IllegalStateException("V-Sync is not enabled, please enable it in \"Video Settings\""); + } + if(params.resolutionDivisior > 1) { + float divisor = (float)Math.sqrt(1.0 / Math.pow(2.0, params.resolutionDivisior - 1)); + int newWidth = (int)(PlatformInput.getWindowWidth() * divisor); + int newHeight = (int)(PlatformInput.getWindowHeight() * divisor); + if(downscaleCanvas == null) { + downscaleCanvas = (HTMLCanvasElement) win.getDocument().createElement("canvas"); + downscaleCanvas.setWidth(newWidth); + downscaleCanvas.setHeight(newHeight); + downscaleCanvasCtx = (CanvasRenderingContext2D) downscaleCanvas.getContext("2d", youEagler()); + if(downscaleCanvasCtx == null) { + downscaleCanvas = null; + throw new IllegalStateException("Could not create downscaler canvas!"); + } + }else { + downscaleCanvas.setWidth(newWidth); + downscaleCanvas.setHeight(newHeight); + } + } + currentMicLock = currentMicVolume <= 0.0f; + recStream = captureStreamAndAddAudio(params.resolutionDivisior > 1 ? downscaleCanvas : canvas, + Math.max(params.captureFrameRate, 0), + PlatformAudio.initRecordingStream(currentGameVolume, currentMicVolume)); + mediaRec = createMediaRecorder(recStream, params.codec.mimeType, params.videoBitsPerSecond * 1000, + params.audioBitsPerSecond * 1000); + currentParameters = params; + setupDataAvailableHandler(mediaRec, "video/webm".equals(params.codec.container), + BetterJSStringConverter.stringToJS(EaglercraftVersion.screenRecordingFilePrefix + " - " + EaglerProfile.getName() + + " - ${date}." + params.codec.fileExt)); + } + + public static void endRecording() { + if(mediaRec != null) { + stopRec(mediaRec); + mediaRec = null; + PlatformAudio.destroyRecordingStream(); + } + currentParameters = null; + } + + public static boolean isRecording() { + return mediaRec != null; + } + + public static boolean isMicVolumeLocked() { + return mediaRec != null && currentMicLock; + } + + public static boolean isVSyncLocked() { + return mediaRec != null && currentParameters != null && currentParameters.captureFrameRate == -1; + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformUpdateSvc.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformUpdateSvc.java new file mode 100644 index 0000000..f26d283 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformUpdateSvc.java @@ -0,0 +1,67 @@ +package net.lax1dude.eaglercraft.v1_8.internal; + +import net.lax1dude.eaglercraft.v1_8.update.UpdateCertificate; +import net.lax1dude.eaglercraft.v1_8.update.UpdateProgressStruct; +import net.lax1dude.eaglercraft.v1_8.update.UpdateResultObj; + +/** + * 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. + * + */ +public class PlatformUpdateSvc { + + private static final UpdateProgressStruct dummyStruct = new UpdateProgressStruct(); + + public static boolean supported() { + return false; + } + + public static void initialize() { + + } + + public static byte[] getClientSignatureData() { + return null; + } + + public static byte[] getClientBundleData() { + return null; + } + + public static void startClientUpdateFrom(UpdateCertificate clientUpdate) { + + } + + public static UpdateProgressStruct getUpdatingStatus() { + return dummyStruct; + } + + public static UpdateResultObj getUpdateResult() { + return null; + } + + public static void installSignedClient(UpdateCertificate clientCert, byte[] clientPayload, boolean setDefault, + boolean setTimeout) { + + } + + public static void quine(String filename, byte[] cert, byte[] data, String date) { + + } + + public static void quine(UpdateCertificate clientUpdate, byte[] data) { + + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformVoiceClient.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformVoiceClient.java new file mode 100644 index 0000000..50c9660 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformVoiceClient.java @@ -0,0 +1,473 @@ +package net.lax1dude.eaglercraft.v1_8.internal; + +import java.util.HashMap; +import java.util.Map; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.teavm.interop.Import; +import org.teavm.jso.JSBody; +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; +import org.teavm.jso.core.JSString; +import org.teavm.jso.typedarrays.Uint8Array; +import org.teavm.jso.webaudio.AnalyserNode; +import org.teavm.jso.webaudio.AudioContext; +import org.teavm.jso.webaudio.AudioNode; +import org.teavm.jso.webaudio.GainNode; +import org.teavm.jso.webaudio.MediaStream; +import org.teavm.jso.webaudio.MediaStreamAudioDestinationNode; +import org.teavm.jso.webaudio.MediaStreamAudioSourceNode; +import org.teavm.jso.webaudio.PannerNode; + +import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.BetterJSStringConverter; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; +import net.lax1dude.eaglercraft.v1_8.voice.EnumVoiceChannelReadyState; +import net.lax1dude.eaglercraft.v1_8.voice.EnumVoiceChannelType; +import net.lax1dude.eaglercraft.v1_8.voice.VoiceClientController; + +/** + * 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. + * + */ +public class PlatformVoiceClient { + + private static final Logger logger = LogManager.getLogger("PlatformVoiceClient"); + + static boolean support = false; + + private interface JSVoicePeerHandle extends JSObject { + + @JSProperty + int getObjId(); + + void setRemoteDescription(JSString desc); + + void addRemoteICECandidate(JSString ice); + + void closeHandle(); + + } + + private interface JSVoicePeerICEEvent extends JSObject { + + @JSProperty + int getObjId(); + + @JSProperty + JSString getData(); + + } + + private interface JSVoicePeerOpenEvent extends JSObject { + + @JSProperty + int getObjId(); + + @JSProperty + MediaStream getStream(); + + } + + private interface JSVoicePeerCloseEvent extends JSObject { + + @JSProperty + int getObjId(); + + } + + private static final int EVENT_VOICE_ICE = 0; + private static final int EVENT_VOICE_DESC = 1; + private static final int EVENT_VOICE_OPEN = 2; + private static final int EVENT_VOICE_CLOSE = 3; + + static void handleJSEvent(PlatformRuntime.JSEagRuntimeEvent evt) { + switch(evt.getEventType() & 31) { + case EVENT_VOICE_ICE: { + JSVoicePeerICEEvent obj = evt.getEventObj(); + VoicePeer peer = peerListI.get(obj.getObjId()); + if(peer != null) { + peer.handleEventLocalICECandidate(BetterJSStringConverter.stringFromJS(obj.getData())); + } + break; + } + case EVENT_VOICE_DESC: { + JSVoicePeerICEEvent obj = evt.getEventObj(); + VoicePeer peer = peerListI.get(obj.getObjId()); + if(peer != null) { + peer.handleEventLocalDescription(BetterJSStringConverter.stringFromJS(obj.getData())); + } + break; + } + case EVENT_VOICE_OPEN: { + JSVoicePeerOpenEvent obj = evt.getEventObj(); + VoicePeer peer = peerListI.get(obj.getObjId()); + if(peer != null) { + peer.handleEventOpened(obj.getStream()); + } + break; + } + case EVENT_VOICE_CLOSE: { + JSVoicePeerCloseEvent obj = evt.getEventObj(); + VoicePeer peer = peerListI.remove(obj.getObjId()); + if(peer != null) { + peerList.remove(peer.peerId); + peer.handleEventClosed(); + } + break; + } + } + } + + static final Map peerList = new HashMap<>(); + static final Map peerListI = new HashMap<>(); + + private static class VoicePeer { + + private final int objId; + private final EaglercraftUUID peerId; + private final JSVoicePeerHandle jsHandle; + + private MediaStream rawStream = null; + private AnalyserNode analyser = null; + private GainNode gain = null; + private PannerNode panner = null; + private AudioNode recNode = null; + + private boolean dead = false; + + private VoicePeer(int objId, EaglercraftUUID peerId, JSVoicePeerHandle jsHandle) { + this.objId = objId; + this.peerId = peerId; + this.jsHandle = jsHandle; + } + + private void handleEventLocalICECandidate(String data) { + VoiceClientController.sendPacketICE(peerId, data); + } + + private void handleEventLocalDescription(String data) { + VoiceClientController.sendPacketDesc(peerId, data); + } + + private void handlePacketRemoteDescription(String descJSON) { + jsHandle.setRemoteDescription(BetterJSStringConverter.stringToJS(descJSON)); + } + + private void handlePacketRemoteICECandidate(String candidate) { + jsHandle.addRemoteICECandidate(BetterJSStringConverter.stringToJS(candidate)); + } + + private void handleEventOpened(MediaStream stream) { + rawStream = stream; + MediaStreamAudioSourceNode audioNode = PlatformAudio.audioctx.createMediaStreamSource(stream); + AnalyserNode analyser = PlatformAudio.audioctx.createAnalyser(); + analyser.setSmoothingTimeConstant(0f); + analyser.setFftSize(32); + audioNode.connect(analyser); + if (VoiceClientController.getVoiceChannel() == EnumVoiceChannelType.GLOBAL) { + GainNode gain = PlatformAudio.audioctx.createGain(); + gain.getGain().setValue(VoiceClientController.getVoiceListenVolume()); + audioNode.connect(gain); + gain.connect(PlatformAudio.audioctx.getDestination()); + if(PlatformAudio.gameRecGain != null) { + gain.connect(PlatformAudio.gameRecGain); + } + VoiceClientController.getVoiceListening().add(peerId); + this.analyser = analyser; + this.gain = gain; + this.recNode = gain; + } else if (VoiceClientController.getVoiceChannel() == EnumVoiceChannelType.PROXIMITY) { + PannerNode panner = PlatformAudio.audioctx.createPanner(); + panner.setRolloffFactor(1f); + PlatformAudio.setDistanceModelLinearFast(panner); + PlatformAudio.setPanningModelHRTFFast(panner); + panner.setConeInnerAngle(360f); + panner.setConeOuterAngle(0f); + panner.setConeOuterGain(0f); + panner.setOrientation(0f, 1f, 0f); + panner.setPosition(0, 0, 0); + float vol = VoiceClientController.getVoiceListenVolume(); + panner.setMaxDistance(vol * 2 * VoiceClientController.getVoiceProximity() + 0.1f); + GainNode gain = PlatformAudio.audioctx.createGain(); + gain.getGain().setValue(vol); + audioNode.connect(gain); + gain.connect(panner); + panner.connect(PlatformAudio.audioctx.getDestination()); + if(PlatformAudio.gameRecGain != null) { + panner.connect(PlatformAudio.gameRecGain); + } + VoiceClientController.getVoiceListening().add(peerId); + this.analyser = analyser; + this.panner = panner; + this.gain = gain; + this.recNode = panner; + } + if (VoiceClientController.getVoiceMuted().contains(peerId)) mute(true); + } + + private void mute(boolean muted) { + if(rawStream != null) { + PlatformVoiceClient.mute(rawStream, muted); + } + } + + private void handlePacketClosed() { + closeHandle(true); + } + + private void handleEventClosed() { + if(!dead) { + dead = true; + cleanup(false); + } + } + + private void closeHandle(boolean quiet) { + if(!dead) { + dead = true; + jsHandle.closeHandle(); + peerListI.remove(objId); + peerList.remove(peerId); + cleanup(quiet); + } + } + + private void cleanup(boolean quiet) { + if(analyser != null) { + analyser.disconnect(); + analyser = null; + } + if(gain != null) { + gain.disconnect(); + gain = null; + } + if(panner != null) { + panner.disconnect(); + panner = null; + } + if(rawStream != null) { + rawStream = null; + } + VoiceClientController.getVoiceListening().remove(peerId); + if (!quiet) { + VoiceClientController.sendPacketDisconnectPeer(peerId); + } + } + + } + + static void initialize() { + support = PlatformWebRTC.supported() && isSupported0(); + peerList.clear(); + peerListI.clear(); + } + + public static boolean isSupported() { + return support; + } + + @Import(module = "platformVoiceClient", name = "isSupported") + private static native boolean isSupported0(); + + public static String iceServers = null; + public static boolean hasInit = false; + public static MediaStreamAudioDestinationNode localMediaStream; + public static GainNode localMediaStreamGain; + public static MediaStream localRawMediaStream; + public static EnumVoiceChannelReadyState readyState = EnumVoiceChannelReadyState.NONE; + public static AudioContext microphoneVolumeAudioContext = null; + + public static void setICEServers(String[] urls) { + if (urls == null || urls.length == 0) { + iceServers = null; + return; + } + JSONArray arr = new JSONArray(); + for (String url : urls) { + String[] etr = url.split(";"); + if (etr.length == 1) { + JSONObject m = new JSONObject(); + m.put("urls", etr[0]); + arr.put(m); + } else if (etr.length == 3) { + JSONObject m = new JSONObject(); + m.put("urls", etr[0]); + m.put("username", etr[1]); + m.put("credential", etr[2]); + arr.put(m); + } + } + iceServers = arr.toString(); + } + + @JSBody(params = { "rawStream", "muted" }, script = "return rawStream.getAudioTracks()[0].enabled = !muted;") + static native void mute(MediaStream rawStream, boolean muted); + + public static void activateVoice(boolean talk) { + if (hasInit) { + mute(localRawMediaStream, !talk); + } + } + + public static void initializeDevices() { + if (!hasInit) { + localRawMediaStream = PlatformScreenRecord.getMic(); + if (localRawMediaStream == null) { + readyState = EnumVoiceChannelReadyState.ABORTED; + return; + } + microphoneVolumeAudioContext = new AudioContext(); + mute(localRawMediaStream, true); + localMediaStream = microphoneVolumeAudioContext.createMediaStreamDestination(); + localMediaStreamGain = microphoneVolumeAudioContext.createGain(); + microphoneVolumeAudioContext.createMediaStreamSource(localRawMediaStream).connect(localMediaStreamGain); + localMediaStreamGain.connect(localMediaStream); + localMediaStreamGain.getGain().setValue(1.0F); + readyState = EnumVoiceChannelReadyState.DEVICE_INITIALIZED; + hasInit = true; + } else { + readyState = EnumVoiceChannelReadyState.DEVICE_INITIALIZED; + } + } + + public static void tickVoiceClient() { + for (VoicePeer voicePlayer : peerList.values()) { + AnalyserNode analyser = voicePlayer.analyser; + if(analyser != null) { + Uint8Array array = new Uint8Array(analyser.getFrequencyBinCount()); + analyser.getByteFrequencyData(array); + int len = array.getLength(); + for (int i = 0; i < len; i++) { + if (array.get(i) >= 0.1f) { + VoiceClientController.getVoiceSpeaking().add(voicePlayer.peerId); + break; + } + } + } + } + } + + public static void setMicVolume(float val) { + if (hasInit) { + if(val > 0.5F) val = 0.5F + (val - 0.5F) * 2.0F; + if(val > 1.5F) val = 1.5F; + if(val < 0.0F) val = 0.0F; + localMediaStreamGain.getGain().setValue(val * 2.0F); + } + } + + public static EnumVoiceChannelReadyState getReadyState() { + return readyState; + } + + public static void signalConnect(EaglercraftUUID peerId, boolean offer) { + if(iceServers == null) { + logger.error("No ICE servers provided for {}", peerId); + return; + } + JSVoicePeerHandle peerHandle = createRTCPeerConnection( + BetterJSStringConverter.stringToJS(iceServers), offer, + localMediaStream.getStream()); + if(peerHandle != null) { + int obj = peerHandle.getObjId(); + VoicePeer peer = new VoicePeer(obj, peerId, peerHandle); + peerList.put(peerId, peer); + peerListI.put(obj, peer); + }else { + logger.error("Could not create peer for {}", peerId); + } + } + + @Import(module = "platformVoiceClient", name = "createRTCPeerConnection") + private static native JSVoicePeerHandle createRTCPeerConnection(JSString iceServers, boolean offer, JSObject localStream); + + public static void signalDescription(EaglercraftUUID peerId, String descJSON) { + VoicePeer peer = peerList.get(peerId); + if (peer != null) { + peer.handlePacketRemoteDescription(descJSON); + } + } + + public static void signalDisconnect(EaglercraftUUID peerId, boolean quiet) { + VoicePeer peer = peerList.remove(peerId); + if (peer != null) { + peer.handlePacketClosed(); + } + } + + public static void setVoiceProximity(int prox) { + for (VoicePeer player : peerList.values()) { + if(player.panner != null) { + player.panner.setMaxDistance(VoiceClientController.getVoiceListenVolume() * 2 * prox + 0.1f); + } + } + } + + public static void updateVoicePosition(EaglercraftUUID uuid, double x, double y, double z) { + VoicePeer player = peerList.get(uuid); + if (player != null && player.panner != null) player.panner.setPosition((float) x, (float) y, (float) z); + } + + public static void mutePeer(EaglercraftUUID peerId, boolean muted) { + VoicePeer peer = peerList.get(peerId); + if (peer != null) { + peer.mute(muted); + } + } + + public static void signalICECandidate(EaglercraftUUID peerId, String candidate) { + VoicePeer peer = peerList.get(peerId); + if (peer != null) { + peer.handlePacketRemoteICECandidate(candidate); + } + } + + static void addRecordingDest(AudioNode destNode) { + for(VoicePeer peer : peerList.values()) { + if(peer.recNode != null) { + peer.recNode.connect(destNode); + } + } + } + + static void removeRecordingDest(AudioNode destNode) { + for(VoicePeer peer : peerList.values()) { + try { + if(peer.recNode != null) { + peer.recNode.disconnect(destNode); + } + }catch(Throwable t) { + } + } + } + + public static void setVoiceListenVolume(float f) { + for (VoicePeer peer : peerList.values()) { + if(peer.gain != null) { + float val = f; + if(val > 0.5f) val = 0.5f + (val - 0.5f) * 3.0f; + if(val > 2.0f) val = 2.0f; + if(val < 0.0f) val = 0.0f; + peer.gain.getGain().setValue(val * 2.0f); + } + if(peer.panner != null) { + peer.panner.setMaxDistance(f * 2 * VoiceClientController.getVoiceProximity() + 0.1f); + } + } + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformWebRTC.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformWebRTC.java new file mode 100644 index 0000000..31728ac --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformWebRTC.java @@ -0,0 +1,360 @@ +package net.lax1dude.eaglercraft.v1_8.internal; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.teavm.interop.Import; +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; +import org.teavm.jso.core.JSArray; +import org.teavm.jso.core.JSString; +import org.teavm.jso.typedarrays.Uint8Array; + +import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCBufferAllocator; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCDirectArrayConverter; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.BetterJSStringConverter; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; +import net.lax1dude.eaglercraft.v1_8.sp.lan.LANPeerEvent; + +/** + * 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. + * + */ +public class PlatformWebRTC { + + private static final Logger logger = LogManager.getLogger("PlatformWebRTC"); + + private static boolean isSupported = false; + + public static void initialize() { + isSupported = supported0(); + } + + public static boolean supported() { + return isSupported; + } + + @Import(module = "platformWebRTC", name = "supported") + private static native boolean supported0(); + + public static void runScheduledTasks() { + + } + + public static void startRTCLANClient() { + + } + + @Import(module = "platformWebRTC", name = "clientLANReadyState") + public static native int clientLANReadyState(); + + @Import(module = "platformWebRTC", name = "clientLANCloseConnection") + public static native void clientLANCloseConnection(); + + public static void clientLANSendPacket(byte[] pkt) { + ByteBuffer buf = WASMGCDirectArrayConverter.byteArrayToBuffer(pkt); + try { + clientLANSendPacket0(WASMGCBufferAllocator.getUnsignedByteBufferView(buf)); + }finally { + PlatformRuntime.freeByteBuffer(buf); + } + } + + @Import(module = "platformWebRTC", name = "clientLANSendPacket") + private static native void clientLANSendPacket0(Uint8Array pkt); + + public static byte[] clientLANReadPacket() { + Uint8Array arr = clientLANReadPacket0(); + if(arr != null) { + return WASMGCDirectArrayConverter.externU8ArrayToByteArray(arr); + }else { + return null; + } + } + + @Import(module = "platformWebRTC", name = "clientLANReadPacket") + private static native Uint8Array clientLANReadPacket0(); + + @Import(module = "platformWebRTC", name = "clientLANAvailable") + private static native int clientLANAvailable(); + + public static List clientLANReadAllPacket() { + int cnt = clientLANAvailable(); + if(cnt == 0) { + return null; + } + byte[][] ret = new byte[cnt][]; + for(int i = 0; i < cnt; ++i) { + ret[i] = clientLANReadPacket(); + } + return Arrays.asList(ret); + } + + public static void clientLANSetICEServersAndConnect(String[] servers) { + clientLANSetICEServersAndConnect0(BetterJSStringConverter.stringArrayToJS(servers)); + } + + @Import(module = "platformWebRTC", name = "clientLANSetICEServersAndConnect") + private static native void clientLANSetICEServersAndConnect0(JSArray servers); + + @Import(module = "platformWebRTC", name = "clearLANClientState") + public static native void clearLANClientState(); + + public static String clientLANAwaitICECandidate() { + return BetterJSStringConverter.stringFromJS(clientLANAwaitICECandidate0()); + } + + @Import(module = "platformWebRTC", name = "clientLANAwaitICECandidate") + private static native JSString clientLANAwaitICECandidate0(); + + public static String clientLANAwaitDescription() { + return BetterJSStringConverter.stringFromJS(clientLANAwaitDescription0()); + } + + @Import(module = "platformWebRTC", name = "clientLANAwaitDescription") + private static native JSString clientLANAwaitDescription0(); + + @Import(module = "platformWebRTC", name = "clientLANAwaitChannel") + public static native boolean clientLANAwaitChannel(); + + @Import(module = "platformWebRTC", name = "clientLANClosed") + public static native boolean clientLANClosed(); + + public static void clientLANSetICECandidate(String candidate) { + clientLANSetICECandidate0(BetterJSStringConverter.stringToJS(candidate)); + } + + @Import(module = "platformWebRTC", name = "clientLANSetICECandidate") + private static native void clientLANSetICECandidate0(JSString candidate); + + public static void clientLANSetDescription(String description) { + clientLANSetDescription0(BetterJSStringConverter.stringToJS(description)); + } + + @Import(module = "platformWebRTC", name = "clientLANSetDescription") + private static native void clientLANSetDescription0(JSString description); + + public static void startRTCLANServer() { + + } + + public static void serverLANInitializeServer(String[] servers) { + serverLANInitializeServer0(BetterJSStringConverter.stringArrayToJS(servers)); + } + + @Import(module = "platformWebRTC", name = "serverLANInitializeServer") + private static native void serverLANInitializeServer0(JSArray servers); + + @Import(module = "platformWebRTC", name = "serverLANCloseServer") + public static native void serverLANCloseServer(); + + private static final int EVENT_WEBRTC_ICE = 0; + private static final int EVENT_WEBRTC_DESC = 1; + private static final int EVENT_WEBRTC_OPEN = 2; + private static final int EVENT_WEBRTC_PACKET = 3; + private static final int EVENT_WEBRTC_CLOSE = 4; + + private interface JSLANPeerEvent extends JSObject { + + @JSProperty + int getType(); + + @JSProperty + T getData(); + + } + + private interface JSLANPeerHandle extends JSObject { + + @JSProperty + JSString getPeerId(); + + int countAvailableEvents(); + + JSLANPeerEvent nextEvent(); + + void writePacket(Uint8Array arr); + + void handleRemoteICECandidates(JSString iceCandidates); + + void handleRemoteDescription(JSString description); + + void mapIPC(String ipcChannel); + + void disconnect(); + + } + + private static class LANPeer { + + private final String peerId; + private final JSLANPeerHandle handle; + private boolean dead = false; + + private LANPeer(String peerId, JSLANPeerHandle handle) { + this.peerId = peerId; + this.handle = handle; + } + + private LANPeerEvent getEvent() { + if(dead) return null; + JSLANPeerEvent peerEvt = handle.nextEvent(); + switch(peerEvt.getType()) { + case EVENT_WEBRTC_ICE: { + return new LANPeerEvent.LANPeerICECandidateEvent(peerId, + BetterJSStringConverter.stringFromJS(peerEvt.getData())); + } + case EVENT_WEBRTC_DESC: { + return new LANPeerEvent.LANPeerDescriptionEvent(peerId, + BetterJSStringConverter.stringFromJS(peerEvt.getData())); + } + case EVENT_WEBRTC_OPEN: { + return new LANPeerEvent.LANPeerDataChannelEvent(peerId); + } + case EVENT_WEBRTC_PACKET: { + return new LANPeerEvent.LANPeerPacketEvent(peerId, + WASMGCDirectArrayConverter.externU8ArrayToByteArray(peerEvt.getData())); + } + case EVENT_WEBRTC_CLOSE: { + return new LANPeerEvent.LANPeerDisconnectEvent(peerId); + } + default: { + throw new IllegalStateException(); + } + } + } + + private List getAllEvent() { + if(dead) return null; + int cnt = handle.countAvailableEvents(); + if(cnt == 0) { + return null; + } + LANPeerEvent[] lst = new LANPeerEvent[cnt]; + for(int i = 0; i < cnt; ++i) { + lst[i] = getEvent(); + } + return Arrays.asList(lst); + } + + private void writePacket(byte[] pkt) { + if(dead) return; + ByteBuffer buf = WASMGCDirectArrayConverter.byteArrayToBuffer(pkt); + try { + handle.writePacket(WASMGCBufferAllocator.getUnsignedByteBufferView(buf)); + }finally { + PlatformRuntime.freeByteBuffer(buf); + } + } + + private void handleRemoteICECandidates(String iceCandidates) { + if(dead) return; + handle.handleRemoteICECandidates(BetterJSStringConverter.stringToJS(iceCandidates)); + } + + private void handleRemoteDescription(String description) { + if(dead) return; + handle.handleRemoteDescription(BetterJSStringConverter.stringToJS(description)); + } + + private void mapIPC(String ipcChannel) { + if(dead) return; + handle.mapIPC(ipcChannel); + } + + private void disconnect() { + if(!dead) { + dead = true; + handle.disconnect(); + } + } + + } + + private static final Map lanServerPeers = new HashMap<>(); + + public static void serverLANCreatePeer(String peer) { + JSLANPeerHandle handle = serverLANCreatePeer0(BetterJSStringConverter.stringToJS(peer)); + if(handle != null) { + lanServerPeers.put(peer, new LANPeer(peer, handle)); + }else { + logger.error("Failed to create peer for client \"{}\"!", peer); + } + } + + @Import(module = "platformWebRTC", name = "serverLANCreatePeer") + private static native JSLANPeerHandle serverLANCreatePeer0(JSString peer); + + public static LANPeerEvent serverLANGetEvent(String peer) { + LANPeer lanPeer = lanServerPeers.get(peer); + if(lanPeer != null) { + return lanPeer.getEvent(); + }else { + return null; + } + } + + public static List serverLANGetAllEvent(String peer) { + LANPeer lanPeer = lanServerPeers.get(peer); + if(lanPeer != null) { + return lanPeer.getAllEvent(); + }else { + return null; + } + } + + public static void serverLANWritePacket(String peer, byte[] data) { + LANPeer lanPeer = lanServerPeers.get(peer); + if(lanPeer != null) { + lanPeer.writePacket(data); + } + } + + public static void serverLANPeerICECandidates(String peer, String iceCandidates) { + LANPeer lanPeer = lanServerPeers.get(peer); + if(lanPeer != null) { + lanPeer.handleRemoteICECandidates(iceCandidates); + } + } + + public static void serverLANPeerDescription(String peer, String description) { + LANPeer lanPeer = lanServerPeers.get(peer); + if(lanPeer != null) { + lanPeer.handleRemoteDescription(description); + } + } + + public static void serverLANPeerMapIPC(String peer, String ipcChannel) { + LANPeer lanPeer = lanServerPeers.get(peer); + if(lanPeer != null) { + lanPeer.mapIPC(ipcChannel); + } + } + + public static void serverLANDisconnectPeer(String peer) { + LANPeer lanPeer = lanServerPeers.remove(peer); + if(lanPeer != null) { + lanPeer.disconnect(); + } + } + + public static int countPeers() { + return lanServerPeers.size(); + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformWebView.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformWebView.java new file mode 100644 index 0000000..cace030 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformWebView.java @@ -0,0 +1,426 @@ +package net.lax1dude.eaglercraft.v1_8.internal; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import org.teavm.interop.Import; +import org.teavm.jso.JSBody; +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; +import org.teavm.jso.core.JSString; +import org.teavm.jso.typedarrays.ArrayBuffer; +import org.teavm.jso.typedarrays.Uint8Array; + +import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime.JSEagRuntimeEvent; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCBufferAllocator; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCDirectArrayConverter; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.BetterJSStringConverter; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; +import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketWebViewMessageEnV4EAG; +import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.client.CPacketWebViewMessageV4EAG; +import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.SPacketWebViewMessageV4EAG; +import net.lax1dude.eaglercraft.v1_8.webview.PermissionsCache; +import net.lax1dude.eaglercraft.v1_8.webview.WebViewOverlayController.IPacketSendCallback; + +/** + * 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. + * + */ +public class PlatformWebView { + + private static final Logger logger = LogManager.getLogger("PlatformWebView"); + + private static String currentMessageChannelName = null; + + private static IPacketSendCallback packetSendCallback = null; + + private static boolean supportedState = false; + private static boolean showingState = false; + private static boolean supportForce = false; + private static boolean enableCSP = true; + private static boolean cspSupport = false; + + private static final int EVENT_CHANNEL_OPEN = 0; + private static final int EVENT_CHANNEL_CLOSE = 1; + + private interface JSWebViewChannelEvent extends JSObject { + + @JSProperty + int getEventType(); + + @JSProperty + JSString getChannelName(); + + } + + private static final int EVENT_MESSAGE_STRING = 0; + private static final int EVENT_MESSAGE_BINARY = 1; + + private interface JSWebViewMessageEvent extends JSObject { + + @JSProperty + int getEventType(); + + @JSProperty + JSString getChannelName(); + + @JSProperty("eventData") + JSString getEventDataString(); + + @JSProperty("eventData") + ArrayBuffer getEventDataBinary(); + + } + + private static final int EVENT_WEBVIEW_CHANNEL = 0; + private static final int EVENT_WEBVIEW_MESSAGE = 1; + private static final int EVENT_WEBVIEW_PERMISSION_ALLOW = 2; + private static final int EVENT_WEBVIEW_PERMISSION_BLOCK = 3; + private static final int EVENT_WEBVIEW_PERMISSION_CLEAR = 4; + + private static final List messageQueue = new LinkedList<>(); + + private static Runnable setJavaScriptAllowedCurrent = null; + private static Runnable setJavaScriptBlockedCurrent = null; + private static Runnable setJavaScriptClearedCurrent = null; + + private static int webviewResetSerial = 0; + + @Import(module = "platformWebView", name = "checkSupported") + private static native boolean checkSupported(); + + @Import(module = "platformWebView", name = "checkCSPSupported") + private static native boolean checkCSPSupported(); + + static void initialize() { + currentMessageChannelName = null; + packetSendCallback = null; + setJavaScriptAllowedCurrent = null; + setJavaScriptBlockedCurrent = null; + setJavaScriptClearedCurrent = null; + supportedState = checkSupported(); + cspSupport = checkCSPSupported(); + if(!supportedState) { + logger.error("This browser does not meet the safety requirements for webview support, this feature will be disabled"); + }else if(!cspSupport) { + logger.warn("This browser does not support CSP attribute on iframes! (try Chrome)"); + } + } + + public static boolean supported() { + return supportedState; + } + + public static boolean isShowing() { + return showingState; + } + + public static void setPacketSendCallback(IPacketSendCallback callback) { + packetSendCallback = callback; + } + + public static void handleJSEvent(JSEagRuntimeEvent evt) { + switch(evt.getEventType() & 31) { + case EVENT_WEBVIEW_CHANNEL: { + JSWebViewChannelEvent obj = evt.getEventObj(); + final String channel = BetterJSStringConverter.stringFromJS(obj.getChannelName()); + switch(obj.getEventType()) { + case EVENT_CHANNEL_OPEN: { + messageQueue.add(() -> { + sendMessageEnToServer(true, channel); + }); + break; + } + case EVENT_CHANNEL_CLOSE: { + messageQueue.add(() -> { + sendMessageEnToServer(false, channel); + }); + break; + } + } + break; + } + case EVENT_WEBVIEW_MESSAGE: { + JSWebViewMessageEvent obj = evt.getEventObj(); + final String channel = BetterJSStringConverter.stringFromJS(obj.getChannelName()); + switch(obj.getEventType()) { + case EVENT_MESSAGE_STRING: { + final String data = BetterJSStringConverter.stringFromJS(obj.getEventDataString()); + messageQueue.add(() -> { + sendMessageToServer(channel, CPacketWebViewMessageV4EAG.TYPE_STRING, data.getBytes(StandardCharsets.UTF_8)); + }); + break; + } + case EVENT_MESSAGE_BINARY: { + final byte[] dataArr = WASMGCDirectArrayConverter + .externU8ArrayToByteArray(new Uint8Array(obj.getEventDataBinary())); + messageQueue.add(() -> { + sendMessageToServer(channel, CPacketWebViewMessageV4EAG.TYPE_BINARY, dataArr); + }); + break; + } + } + break; + } + case EVENT_WEBVIEW_PERMISSION_ALLOW: { + if(setJavaScriptAllowedCurrent != null) { + messageQueue.add(setJavaScriptAllowedCurrent); + } + break; + } + case EVENT_WEBVIEW_PERMISSION_BLOCK: { + if(setJavaScriptBlockedCurrent != null) { + messageQueue.add(setJavaScriptBlockedCurrent); + } + break; + } + case EVENT_WEBVIEW_PERMISSION_CLEAR: { + if(setJavaScriptClearedCurrent != null) { + messageQueue.add(setJavaScriptClearedCurrent); + } + break; + } + } + } + + public static void runTick() { + if(!showingState) { + return; + } + List lst = null; + if(messageQueue.isEmpty()) { + return; + } + lst = new ArrayList<>(messageQueue); + messageQueue.clear(); + for(int i = 0, l = lst.size(); i < l; ++i) { + try { + lst.get(i).run(); + }catch(Throwable t) { + logger.error("Caught exception processing webview message!"); + logger.error(t); + } + } + } + + public static void handleMessageFromServer(SPacketWebViewMessageV4EAG packet) { + if(showingState && currentMessageChannelName != null) { + if(packet.type == SPacketWebViewMessageV4EAG.TYPE_STRING) { + sendStringMessage(BetterJSStringConverter.stringToJS(currentMessageChannelName), + BetterJSStringConverter.stringToJS(new String(packet.data, StandardCharsets.UTF_8))); + }else if(packet.type == SPacketWebViewMessageV4EAG.TYPE_BINARY) { + ByteBuffer buf = WASMGCDirectArrayConverter.byteArrayToBuffer(packet.data); + try { + sendBinaryMessage(BetterJSStringConverter.stringToJS(currentMessageChannelName), + WASMGCBufferAllocator.getUnsignedByteBufferView(buf)); + }finally { + PlatformRuntime.freeByteBuffer(buf); + } + } + }else { + logger.error("Server tried to send the WebView a message, but the message channel is not open!"); + } + } + + @Import(module = "platformWebView", name = "sendStringMessage") + private static native void sendStringMessage(JSString ch, JSString str); + + @Import(module = "platformWebView", name = "sendBinaryMessage") + private static native void sendBinaryMessage(JSString ch, Uint8Array bin); + + private static void sendMessageToServer(String channelName, int type, byte[] data) { + if(channelName.length() > 255) { + logger.error("WebView tried to send a message packet, but channel name is too long, max is 255 characters!"); + return; + } + if(!channelName.equals(currentMessageChannelName)) { + logger.error("WebView tried to send a message packet, but the channel is not open!"); + return; + } + if(packetSendCallback != null) { + if(!packetSendCallback.sendPacket(new CPacketWebViewMessageV4EAG(type, data))) { + logger.error("WebView tried to send a packet to the server, but the server does not support this protocol!"); + } + }else { + logger.error("WebView tried to send a message, but no callback for sending packets is set!"); + } + } + + private static void sendMessageEnToServer(boolean messageChannelOpen, String channelName) { + if(channelName.length() > 255) { + logger.error("WebView tried to {} a channel, but channel name is too long, max is 255 characters!", messageChannelOpen ? "open" : "close"); + return; + } + if(messageChannelOpen && currentMessageChannelName != null) { + logger.error("WebView tried to open channel, but a channel is already open!"); + sendMessageEnToServer(false, currentMessageChannelName); + } + if(!messageChannelOpen && currentMessageChannelName != null && !currentMessageChannelName.equals(channelName)) { + logger.error("WebView tried to close the wrong channel!"); + } + if(!messageChannelOpen && currentMessageChannelName == null) { + logger.error("WebView tried to close channel, but the channel is not open!"); + return; + } + if(packetSendCallback != null) { + if(!packetSendCallback.sendPacket(new CPacketWebViewMessageEnV4EAG(messageChannelOpen, messageChannelOpen ? channelName : null))) { + logger.error("WebView tried to send a packet to the server, but the server does not support this protocol!"); + return; + } + if(messageChannelOpen) { + logger.info("WebView opened message channel to server: \"{}\"", channelName); + currentMessageChannelName = channelName; + }else { + logger.info("WebView closed message channel to server: \"{}\"", currentMessageChannelName); + currentMessageChannelName = null; + } + }else { + logger.error("WebView tried to send a message, but no callback for sending packets is set!"); + } + } + + private interface JSWebViewOptions extends JSObject { + + @JSProperty("uri") + void setURI(JSString title); + + @JSProperty + void setBlob(Uint8Array data); + + } + + @JSBody(params = { "a", "b", "c", "d", "e" }, script = "return { contentMode: a, fallbackTitle: b, " + + "scriptEnabled: c, strictCSPEnable: d, serverMessageAPIEnabled: e};") + private static native JSWebViewOptions makeOptions(int contentMode, JSString fallbackTitle, boolean scriptEnabled, + boolean strictCSPEnable, boolean serverMessageAPIEnabled); + + private static int hashPermissionFlags(WebViewOptions opts) { + int i = (opts.scriptEnabled ? 1 : 0); + i |= ((enableCSP && cspSupport && opts.strictCSPEnable) ? 0 : 2); + i |= (opts.serverMessageAPIEnabled ? 4 : 0); + return i; + } + + public static void beginShowing(final WebViewOptions options, int x, int y, int w, int h) { + if(!supported()) { + return; + } + if(showingState) { + endShowing(); + } + showingState = true; + ++webviewResetSerial; + + messageQueue.clear(); + + int state = BEGIN_SHOWING_DIRECT; + if(options.scriptEnabled) { + PermissionsCache.Permission perm = PermissionsCache.getJavaScriptAllowed(options.permissionsOriginUUID, hashPermissionFlags(options)); + if(perm == null) { + state = BEGIN_SHOWING_ENABLE_JAVASCRIPT; + }else if(!perm.choice) { + state = BEGIN_SHOWING_CONTENT_BLOCKED; + } + } + + boolean isBlob = options.contentMode == EnumWebViewContentMode.BLOB_BASED; + JSWebViewOptions opts = makeOptions(isBlob ? 1 : 0, BetterJSStringConverter.stringToJS(options.fallbackTitle), + options.scriptEnabled, options.strictCSPEnable, options.serverMessageAPIEnabled); + if(isBlob) { + ByteBuffer buf = WASMGCDirectArrayConverter.byteArrayToBuffer(options.blob); + try { + opts.setBlob(WASMGCBufferAllocator.getUnsignedByteBufferView(buf)); + beginShowing0(state, opts, x, y, w, h); + }finally { + PlatformRuntime.freeByteBuffer(buf); + } + }else { + opts.setURI(BetterJSStringConverter.stringToJS(options.url.toString())); + beginShowing0(state, opts, x, y, w, h); + } + + final int serial = webviewResetSerial; + setJavaScriptAllowedCurrent = () -> { + if(serial == webviewResetSerial) { + PermissionsCache.setJavaScriptAllowed(options.permissionsOriginUUID, hashPermissionFlags(options), true); + } + }; + setJavaScriptBlockedCurrent = () -> { + if(serial == webviewResetSerial) { + PermissionsCache.setJavaScriptAllowed(options.permissionsOriginUUID, hashPermissionFlags(options), false); + } + }; + setJavaScriptClearedCurrent = () -> { + if(serial == webviewResetSerial) { + PermissionsCache.clearJavaScriptAllowed(options.permissionsOriginUUID); + } + }; + } + + private static final int BEGIN_SHOWING_DIRECT = 0; + private static final int BEGIN_SHOWING_ENABLE_JAVASCRIPT = 1; + private static final int BEGIN_SHOWING_CONTENT_BLOCKED = 2; + + @Import(module = "platformWebView", name = "beginShowing") + private static native void beginShowing0(int state, JSWebViewOptions options, int x, int y, int w, int h); + + @Import(module = "platformWebView", name = "resize") + public static native void resize(int x, int y, int w, int h); + + public static void endShowing() { + if(!supported()) { + return; + } + ++webviewResetSerial; + if(showingState) { + showingState = false; + endShowing0(); + } + if(currentMessageChannelName != null) { + sendMessageEnToServer(false, currentMessageChannelName); + } + messageQueue.clear(); + setJavaScriptAllowedCurrent = null; + setJavaScriptBlockedCurrent = null; + setJavaScriptClearedCurrent = null; + } + + @Import(module = "platformWebView", name = "endShowing") + private static native void endShowing0(); + + public static boolean fallbackSupported() { + return false; + } + + public static void launchFallback(WebViewOptions options) { + + } + + public static boolean fallbackRunning() { + return false; + } + + public static String getFallbackURL() { + return null; + } + + public static void endFallbackServer() { + + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/DirectMallocByteBuffer.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/DirectMallocByteBuffer.java new file mode 100644 index 0000000..28b09b7 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/DirectMallocByteBuffer.java @@ -0,0 +1,385 @@ +package net.lax1dude.eaglercraft.v1_8.internal.buffer; + +import org.teavm.interop.Address; +import org.teavm.interop.DirectMalloc; + +/** + * 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. + * + */ +public class DirectMallocByteBuffer implements ByteBuffer { + + final Address address; + final boolean original; + + private final int capacity; + private int position; + private int limit; + private int mark; + + DirectMallocByteBuffer(Address address, int capacity, boolean original) { + this(address, capacity, 0, capacity, -1, original); + } + + DirectMallocByteBuffer(Address address, int capacity, int position, int limit, int mark, boolean original) { + this.address = address; + this.capacity = capacity; + this.position = position; + this.limit = limit; + this.mark = mark; + this.original = original; + } + + @Override + public int capacity() { + return capacity; + } + + @Override + public int position() { + return position; + } + + @Override + public int limit() { + return limit; + } + + @Override + public int remaining() { + return limit - position; + } + + @Override + public boolean hasRemaining() { + return position < limit; + } + + @Override + public boolean hasArray() { + return false; + } + + @Override + public byte[] array() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDirect() { + return true; + } + + @Override + public ByteBuffer duplicate() { + return new DirectMallocByteBuffer(address, capacity, position, limit, mark, false); + } + + @Override + public byte get() { + if(position >= limit) throw Buffer.makeIOOBE(position); + return address.add(position++).getByte(); + } + + @Override + public ByteBuffer put(byte b) { + if(position >= limit) throw Buffer.makeIOOBE(position); + address.add(position++).putByte(b); + return this; + } + + @Override + public byte get(int index) { + if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index); + return address.add(index).getByte(); + } + + @Override + public ByteBuffer put(int index, byte b) { + if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index); + address.add(index).putByte(b); + return this; + } + + @Override + public ByteBuffer get(byte[] dst, int offset, int length) { + if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1); + if(offset < 0) throw Buffer.makeIOOBE(offset); + if(offset + length > dst.length) throw Buffer.makeIOOBE(offset + length - 1); + WASMGCDirectArrayCopy.memcpy(dst, offset, address.add(position), length); + position += length; + return this; + } + + @Override + public ByteBuffer get(byte[] dst) { + int dstLen = dst.length; + if(position + dstLen > limit) throw Buffer.makeIOOBE(position + dstLen - 1); + WASMGCDirectArrayCopy.memcpy(dst, 0, address.add(position), dstLen); + position += dstLen; + return this; + } + + @Override + public ByteBuffer put(ByteBuffer src) { + if(src instanceof DirectMallocByteBuffer) { + DirectMallocByteBuffer c = (DirectMallocByteBuffer)src; + int l = c.limit - c.position; + if(position + l > limit) throw Buffer.makeIOOBE(position + l - 1); + DirectMalloc.memcpy(address.add(position), c.address.add(c.position), l); + position += l; + c.position += l; + }else { + int l = src.remaining(); + if(position + l > limit) throw Buffer.makeIOOBE(position + l - 1); + Address addrBase = address.add(position); + for(int i = 0; i < l; ++i) { + addrBase.add(i).putByte(src.get()); + } + position += l; + } + return this; + } + + @Override + public ByteBuffer put(byte[] src, int offset, int length) { + if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1); + if(offset < 0) throw Buffer.makeIOOBE(offset); + if(offset + length > src.length) throw Buffer.makeIOOBE(offset + length - 1); + WASMGCDirectArrayCopy.memcpy(address.add(position), src, offset, length); + position += length; + return this; + } + + @Override + public ByteBuffer put(byte[] src) { + int srcLen = src.length; + if(position + srcLen > limit) throw Buffer.makeIOOBE(position + srcLen - 1); + WASMGCDirectArrayCopy.memcpy(address.add(position), src, 0, srcLen); + position += srcLen; + return this; + } + + @Override + public char getChar() { + if(position + 2 > limit) throw Buffer.makeIOOBE(position); + char c = address.add(position).getChar(); + position += 2; + return c; + } + + @Override + public ByteBuffer putChar(char value) { + if(position + 2 > limit) throw Buffer.makeIOOBE(position); + address.add(position).putChar(value); + position += 2; + return this; + } + + @Override + public char getChar(int index) { + if(index < 0 || index + 2 > limit) throw Buffer.makeIOOBE(index); + return address.add(index).getChar(); + } + + @Override + public ByteBuffer putChar(int index, char value) { + if(index < 0 || index + 2 > limit) throw Buffer.makeIOOBE(index); + address.add(index).putChar(value); + return this; + } + + @Override + public short getShort() { + if(position + 2 > limit) throw Buffer.makeIOOBE(position); + short s = address.add(position).getShort(); + position += 2; + return s; + } + + @Override + public ByteBuffer putShort(short value) { + if(position + 2 > limit) throw Buffer.makeIOOBE(position); + address.add(position).putShort(value); + position += 2; + return this; + } + + @Override + public short getShort(int index) { + if(index < 0 || index + 2 > limit) throw Buffer.makeIOOBE(index); + return address.add(index).getShort(); + } + + @Override + public ByteBuffer putShort(int index, short value) { + if(index < 0 || index + 2 > limit) throw Buffer.makeIOOBE(index); + address.add(index).putShort(value); + return this; + } + + @Override + public ShortBuffer asShortBuffer() { + return new DirectMallocShortBuffer(address, capacity >> 1, false); + } + + @Override + public int getInt() { + if(position + 4 > limit) throw Buffer.makeIOOBE(position); + int i = address.add(position).getInt(); + position += 4; + return i; + } + + @Override + public ByteBuffer putInt(int value) { + if(position + 4 > limit) throw Buffer.makeIOOBE(position); + address.add(position).putInt(value); + position += 4; + return this; + } + + @Override + public int getInt(int index) { + if(index < 0 || index + 4 > limit) throw Buffer.makeIOOBE(index); + return address.add(index).getInt(); + } + + @Override + public ByteBuffer putInt(int index, int value) { + if(index < 0 || index + 4 > limit) throw Buffer.makeIOOBE(index); + address.add(index).putInt(value); + return this; + } + + @Override + public IntBuffer asIntBuffer() { + return new DirectMallocIntBuffer(address, capacity >> 2, false); + } + + @Override + public long getLong() { + if(position + 8 > limit) throw Buffer.makeIOOBE(position); + long l = address.add(position).getLong(); + position += 8; + return l; + } + + @Override + public ByteBuffer putLong(long value) { + if(position + 8 > limit) throw Buffer.makeIOOBE(position); + address.add(position).putLong(value); + position += 8; + return this; + } + + @Override + public long getLong(int index) { + if(index < 0 || index + 8 > limit) throw Buffer.makeIOOBE(index); + return address.add(index).getLong(); + } + + @Override + public ByteBuffer putLong(int index, long value) { + if(index < 0 || index + 8 > limit) throw Buffer.makeIOOBE(index); + address.add(index).putLong(value); + return this; + } + + @Override + public float getFloat() { + if(position + 4 > limit) throw Buffer.makeIOOBE(position); + float f = address.add(position).getFloat(); + position += 4; + return f; + } + + @Override + public ByteBuffer putFloat(float value) { + if(position + 4 > limit) throw Buffer.makeIOOBE(position); + address.add(position).putFloat(value); + position += 4; + return this; + } + + @Override + public float getFloat(int index) { + if(index < 0 || index + 4 > limit) throw Buffer.makeIOOBE(index); + return address.add(index).getFloat(); + } + + @Override + public ByteBuffer putFloat(int index, float value) { + if(index < 0 || index + 4 > limit) throw Buffer.makeIOOBE(index); + address.add(index).putFloat(value); + return this; + } + + @Override + public FloatBuffer asFloatBuffer() { + return new DirectMallocFloatBuffer(address, capacity >> 2, false); + } + + @Override + public ByteBuffer mark() { + mark = position; + return this; + } + + @Override + public ByteBuffer reset() { + int m = mark; + if(m < 0) throw new IndexOutOfBoundsException("Invalid mark: " + m); + position = m; + return this; + } + + @Override + public ByteBuffer clear() { + position = 0; + limit = capacity; + mark = -1; + return this; + } + + @Override + public ByteBuffer flip() { + limit = position; + position = 0; + mark = -1; + return this; + } + + @Override + public ByteBuffer rewind() { + position = 0; + mark = -1; + return this; + } + + @Override + public ByteBuffer limit(int newLimit) { + if(newLimit < 0 || newLimit > capacity) throw Buffer.makeIOOBE(newLimit); + limit = newLimit; + return this; + } + + @Override + public ByteBuffer position(int newPosition) { + if(newPosition < 0 || newPosition > limit) throw Buffer.makeIOOBE(newPosition); + position = newPosition; + return this; + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/DirectMallocFloatBuffer.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/DirectMallocFloatBuffer.java new file mode 100644 index 0000000..a27cc15 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/DirectMallocFloatBuffer.java @@ -0,0 +1,238 @@ +package net.lax1dude.eaglercraft.v1_8.internal.buffer; + +import org.teavm.interop.Address; +import org.teavm.interop.DirectMalloc; + +/** + * 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. + * + */ +public class DirectMallocFloatBuffer implements FloatBuffer { + + final Address address; + final boolean original; + + private final int capacity; + private int position; + private int limit; + private int mark; + + private static final int SHIFT = 2; + + DirectMallocFloatBuffer(Address address, int capacity, boolean original) { + this(address, capacity, 0, capacity, -1, original); + } + + DirectMallocFloatBuffer(Address address, int capacity, int position, int limit, int mark, boolean original) { + this.address = address; + this.capacity = capacity; + this.position = position; + this.limit = limit; + this.mark = mark; + this.original = original; + } + + @Override + public int capacity() { + return capacity; + } + + @Override + public int position() { + return position; + } + + @Override + public int limit() { + return limit; + } + + @Override + public int remaining() { + return limit - position; + } + + @Override + public boolean hasRemaining() { + return position < limit; + } + + @Override + public boolean hasArray() { + return false; + } + + @Override + public float[] array() { + throw new UnsupportedOperationException(); + } + + @Override + public FloatBuffer duplicate() { + return new DirectMallocFloatBuffer(address, capacity, position, limit, mark, false); + } + + @Override + public float get() { + if(position >= limit) throw Buffer.makeIOOBE(position); + return address.add((position++) << SHIFT).getFloat(); + } + + @Override + public FloatBuffer put(float b) { + if(position >= limit) throw Buffer.makeIOOBE(position); + address.add((position++) << SHIFT).putFloat(b); + return this; + } + + @Override + public float get(int index) { + if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index); + return address.add(index << SHIFT).getFloat(); + } + + @Override + public FloatBuffer put(int index, float b) { + if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index); + address.add(index << SHIFT).putFloat(b); + return this; + } + + @Override + public float getElement(int index) { + if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index); + return address.add(index << SHIFT).getFloat(); + } + + @Override + public void putElement(int index, float value) { + if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index); + address.add(index << SHIFT).putFloat(value); + } + + @Override + public FloatBuffer get(float[] dst, int offset, int length) { + if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1); + if(offset < 0) throw Buffer.makeIOOBE(offset); + if(offset + length > dst.length) throw Buffer.makeIOOBE(offset + length - 1); + WASMGCDirectArrayCopy.memcpy(dst, offset, address.add(position << SHIFT), length); + position += length; + return this; + } + + @Override + public FloatBuffer get(float[] dst) { + int dstLen = dst.length; + if(position + dstLen > limit) throw Buffer.makeIOOBE(position + dstLen - 1); + WASMGCDirectArrayCopy.memcpy(dst, 0, address.add(position << SHIFT), dstLen); + position += dstLen; + return this; + } + + @Override + public FloatBuffer put(FloatBuffer src) { + if(src instanceof DirectMallocFloatBuffer) { + DirectMallocFloatBuffer c = (DirectMallocFloatBuffer)src; + int l = c.limit - c.position; + if(position + l > limit) throw Buffer.makeIOOBE(position + l - 1); + DirectMalloc.memcpy(address.add(position << SHIFT), c.address.add(c.position << SHIFT), l << SHIFT); + position += l; + c.position += l; + }else { + int l = src.remaining(); + if(position + l > limit) throw Buffer.makeIOOBE(position + l - 1); + Address addrBase = address.add(position << SHIFT); + for(int i = 0, ll = l << SHIFT; i < ll; i += 4) { + addrBase.add(i).putFloat(src.get()); + } + position += l; + } + return this; + } + + @Override + public FloatBuffer put(float[] src, int offset, int length) { + if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1); + if(offset < 0) throw Buffer.makeIOOBE(offset); + if(offset + length > src.length) throw Buffer.makeIOOBE(offset + length - 1); + WASMGCDirectArrayCopy.memcpy(address.add(position << SHIFT), src, offset, length); + position += length; + return this; + } + + @Override + public FloatBuffer put(float[] src) { + if(position + src.length > limit) throw Buffer.makeIOOBE(position + src.length - 1); + WASMGCDirectArrayCopy.memcpy(address.add(position << SHIFT), src, 0, src.length); + position += src.length; + return this; + } + + @Override + public boolean isDirect() { + return true; + } + + @Override + public FloatBuffer mark() { + mark = position; + return this; + } + + @Override + public FloatBuffer reset() { + int m = mark; + if(m < 0) throw new IndexOutOfBoundsException("Invalid mark: " + m); + position = m; + return this; + } + + @Override + public FloatBuffer clear() { + position = 0; + limit = capacity; + mark = -1; + return this; + } + + @Override + public FloatBuffer flip() { + limit = position; + position = 0; + mark = -1; + return this; + } + + @Override + public FloatBuffer rewind() { + position = 0; + mark = -1; + return this; + } + + @Override + public FloatBuffer limit(int newLimit) { + if(newLimit < 0 || newLimit > capacity) throw Buffer.makeIOOBE(newLimit); + limit = newLimit; + return this; + } + + @Override + public FloatBuffer position(int newPosition) { + if(newPosition < 0 || newPosition > limit) throw Buffer.makeIOOBE(newPosition); + position = newPosition; + return this; + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/DirectMallocIntBuffer.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/DirectMallocIntBuffer.java new file mode 100644 index 0000000..2550773 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/DirectMallocIntBuffer.java @@ -0,0 +1,239 @@ +package net.lax1dude.eaglercraft.v1_8.internal.buffer; + +import org.teavm.interop.Address; +import org.teavm.interop.DirectMalloc; + +/** + * 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. + * + */ +public class DirectMallocIntBuffer implements IntBuffer { + + final Address address; + final boolean original; + + private final int capacity; + private int position; + private int limit; + private int mark; + + private static final int SHIFT = 2; + + DirectMallocIntBuffer(Address address, int capacity, boolean original) { + this(address, capacity, 0, capacity, -1, original); + } + + DirectMallocIntBuffer(Address address, int capacity, int position, int limit, int mark, boolean original) { + this.address = address; + this.capacity = capacity; + this.position = position; + this.limit = limit; + this.mark = mark; + this.original = original; + } + + @Override + public int capacity() { + return capacity; + } + + @Override + public int position() { + return position; + } + + @Override + public int limit() { + return limit; + } + + @Override + public int remaining() { + return limit - position; + } + + @Override + public boolean hasRemaining() { + return position < limit; + } + + @Override + public boolean hasArray() { + return false; + } + + @Override + public int[] array() { + throw new UnsupportedOperationException(); + } + + @Override + public IntBuffer duplicate() { + return new DirectMallocIntBuffer(address, capacity, position, limit, mark, false); + } + + @Override + public int get() { + if(position >= limit) throw Buffer.makeIOOBE(position); + return address.add((position++) << SHIFT).getInt(); + } + + @Override + public IntBuffer put(int b) { + if(position >= limit) throw Buffer.makeIOOBE(position); + address.add((position++) << SHIFT).putInt(b); + return this; + } + + @Override + public int get(int index) { + if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index); + return address.add(index << SHIFT).getInt(); + } + + @Override + public IntBuffer put(int index, int b) { + if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index); + address.add(index << SHIFT).putInt(b); + return this; + } + + @Override + public int getElement(int index) { + if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index); + return address.add(index << SHIFT).getInt(); + } + + @Override + public void putElement(int index, int value) { + if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index); + address.add(index << SHIFT).putInt(value); + } + + @Override + public IntBuffer get(int[] dst, int offset, int length) { + if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1); + if(offset < 0) throw Buffer.makeIOOBE(offset); + if(offset + length > dst.length) throw Buffer.makeIOOBE(offset + length - 1); + WASMGCDirectArrayCopy.memcpy(dst, offset, address.add(position << SHIFT), length); + position += length; + return this; + } + + @Override + public IntBuffer get(int[] dst) { + int dstLen = dst.length; + if(position + dstLen > limit) throw Buffer.makeIOOBE(position + dstLen - 1); + WASMGCDirectArrayCopy.memcpy(dst, 0, address.add(position << SHIFT), dstLen); + position += dstLen; + return this; + } + + @Override + public IntBuffer put(IntBuffer src) { + if(src instanceof DirectMallocIntBuffer) { + DirectMallocIntBuffer c = (DirectMallocIntBuffer)src; + int l = c.limit - c.position; + if(position + l > limit) throw Buffer.makeIOOBE(position + l - 1); + DirectMalloc.memcpy(address.add(position << SHIFT), c.address.add(c.position << SHIFT), l << SHIFT); + position += l; + c.position += l; + }else { + int l = src.remaining(); + if(position + l > limit) throw Buffer.makeIOOBE(position + l - 1); + Address addrBase = address.add(position << SHIFT); + for(int i = 0, ll = l << SHIFT; i < ll; i += 4) { + addrBase.add(i).putInt(src.get()); + } + position += l; + } + return this; + } + + @Override + public IntBuffer put(int[] src, int offset, int length) { + if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1); + if(offset < 0) throw Buffer.makeIOOBE(offset); + if(offset + length > src.length) throw Buffer.makeIOOBE(offset + length - 1); + WASMGCDirectArrayCopy.memcpy(address.add(position << SHIFT), src, offset, length); + position += length; + return this; + } + + @Override + public IntBuffer put(int[] src) { + int srcLen = src.length; + if(position + srcLen > limit) throw Buffer.makeIOOBE(position + srcLen - 1); + WASMGCDirectArrayCopy.memcpy(address.add(position << SHIFT), src, 0, srcLen); + position += src.length; + return this; + } + + @Override + public boolean isDirect() { + return true; + } + + @Override + public IntBuffer mark() { + mark = position; + return this; + } + + @Override + public IntBuffer reset() { + int m = mark; + if(m < 0) throw new IndexOutOfBoundsException("Invalid mark: " + m); + position = m; + return this; + } + + @Override + public IntBuffer clear() { + position = 0; + limit = capacity; + mark = -1; + return this; + } + + @Override + public IntBuffer flip() { + limit = position; + position = 0; + mark = -1; + return this; + } + + @Override + public IntBuffer rewind() { + position = 0; + mark = -1; + return this; + } + + @Override + public IntBuffer limit(int newLimit) { + if(newLimit < 0 || newLimit > capacity) throw Buffer.makeIOOBE(newLimit); + limit = newLimit; + return this; + } + + @Override + public IntBuffer position(int newPosition) { + if(newPosition < 0 || newPosition > limit) throw Buffer.makeIOOBE(newPosition); + position = newPosition; + return this; + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/DirectMallocShortBuffer.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/DirectMallocShortBuffer.java new file mode 100644 index 0000000..cb79503 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/DirectMallocShortBuffer.java @@ -0,0 +1,239 @@ +package net.lax1dude.eaglercraft.v1_8.internal.buffer; + +import org.teavm.interop.Address; +import org.teavm.interop.DirectMalloc; + +/** + * 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. + * + */ +public class DirectMallocShortBuffer implements ShortBuffer { + + final Address address; + final boolean original; + + private final int capacity; + private int position; + private int limit; + private int mark; + + private static final int SHIFT = 1; + + DirectMallocShortBuffer(Address address, int capacity, boolean original) { + this(address, capacity, 0, capacity, -1, original); + } + + DirectMallocShortBuffer(Address address, int capacity, int position, int limit, int mark, boolean original) { + this.address = address; + this.capacity = capacity; + this.position = position; + this.limit = limit; + this.mark = mark; + this.original = original; + } + + @Override + public int capacity() { + return capacity; + } + + @Override + public int position() { + return position; + } + + @Override + public int limit() { + return limit; + } + + @Override + public int remaining() { + return limit - position; + } + + @Override + public boolean hasRemaining() { + return position < limit; + } + + @Override + public boolean hasArray() { + return false; + } + + @Override + public short[] array() { + throw new UnsupportedOperationException(); + } + + @Override + public ShortBuffer duplicate() { + return new DirectMallocShortBuffer(address, capacity, position, limit, mark, false); + } + + @Override + public short get() { + if(position >= limit) throw Buffer.makeIOOBE(position); + return address.add((position++) << SHIFT).getShort(); + } + + @Override + public ShortBuffer put(short b) { + if(position >= limit) throw Buffer.makeIOOBE(position); + address.add((position++) << SHIFT).putShort(b); + return this; + } + + @Override + public short get(int index) { + if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index); + return address.add(index << SHIFT).getShort(); + } + + @Override + public ShortBuffer put(int index, short b) { + if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index); + address.add(index << SHIFT).putShort(b); + return this; + } + + @Override + public short getElement(int index) { + if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index); + return address.add(index << SHIFT).getShort(); + } + + @Override + public void putElement(int index, short value) { + if(index < 0 || index >= limit) throw Buffer.makeIOOBE(index); + address.add(index << SHIFT).putShort(value); + } + + @Override + public ShortBuffer get(short[] dst, int offset, int length) { + if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1); + if(offset < 0) throw Buffer.makeIOOBE(offset); + if(offset + length > dst.length) throw Buffer.makeIOOBE(offset + length - 1); + WASMGCDirectArrayCopy.memcpy(dst, offset, address.add(position << SHIFT), length); + position += length; + return this; + } + + @Override + public ShortBuffer get(short[] dst) { + int dstLen = dst.length; + if(position + dstLen > limit) throw Buffer.makeIOOBE(position + dstLen - 1); + WASMGCDirectArrayCopy.memcpy(dst, 0, address.add(position << SHIFT), dstLen); + position += dstLen; + return this; + } + + @Override + public ShortBuffer put(ShortBuffer src) { + if(src instanceof DirectMallocShortBuffer) { + DirectMallocShortBuffer c = (DirectMallocShortBuffer)src; + int l = c.limit - c.position; + if(position + l > limit) throw Buffer.makeIOOBE(position + l - 1); + DirectMalloc.memcpy(address.add(position << SHIFT), c.address.add(c.position << SHIFT), l << SHIFT); + position += l; + c.position += l; + }else { + int l = src.remaining(); + if(position + l > limit) throw Buffer.makeIOOBE(position + l - 1); + Address addrBase = address.add(position << SHIFT); + for(int i = 0, ll = l << SHIFT; i < ll; i += 2) { + addrBase.add(i).putShort(src.get()); + } + position += l; + } + return this; + } + + @Override + public ShortBuffer put(short[] src, int offset, int length) { + if(position + length > limit) throw Buffer.makeIOOBE(position + length - 1); + if(offset < 0) throw Buffer.makeIOOBE(offset); + if(offset + length > src.length) throw Buffer.makeIOOBE(offset + length - 1); + WASMGCDirectArrayCopy.memcpy(address.add(position << SHIFT), src, offset, length); + position += length; + return this; + } + + @Override + public ShortBuffer put(short[] src) { + int srcLen = src.length; + if(position + srcLen > limit) throw Buffer.makeIOOBE(position + srcLen - 1); + WASMGCDirectArrayCopy.memcpy(address.add(position << SHIFT), src, 0, srcLen); + position += src.length; + return this; + } + + @Override + public boolean isDirect() { + return true; + } + + @Override + public ShortBuffer mark() { + mark = position; + return this; + } + + @Override + public ShortBuffer reset() { + int m = mark; + if(m < 0) throw new IndexOutOfBoundsException("Invalid mark: " + m); + position = m; + return this; + } + + @Override + public ShortBuffer clear() { + position = 0; + limit = capacity; + mark = -1; + return this; + } + + @Override + public ShortBuffer flip() { + limit = position; + position = 0; + mark = -1; + return this; + } + + @Override + public ShortBuffer rewind() { + position = 0; + mark = -1; + return this; + } + + @Override + public ShortBuffer limit(int newLimit) { + if(newLimit < 0 || newLimit > capacity) throw Buffer.makeIOOBE(newLimit); + limit = newLimit; + return this; + } + + @Override + public ShortBuffer position(int newPosition) { + if(newPosition < 0 || newPosition > limit) throw Buffer.makeIOOBE(newPosition); + position = newPosition; + return this; + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/WASMGCBufferAllocator.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/WASMGCBufferAllocator.java new file mode 100644 index 0000000..1226217 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/WASMGCBufferAllocator.java @@ -0,0 +1,269 @@ +package net.lax1dude.eaglercraft.v1_8.internal.buffer; + +import org.teavm.interop.Address; +import org.teavm.interop.DirectMalloc; +import org.teavm.interop.Import; +import org.teavm.jso.typedarrays.Float32Array; +import org.teavm.jso.typedarrays.Int16Array; +import org.teavm.jso.typedarrays.Int32Array; +import org.teavm.jso.typedarrays.Int8Array; +import org.teavm.jso.typedarrays.Uint16Array; +import org.teavm.jso.typedarrays.Uint8Array; +import org.teavm.jso.typedarrays.Uint8ClampedArray; + +/** + * 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. + * + */ +public class WASMGCBufferAllocator { + + private static final boolean enableBufferOverflowCheck = false; + + public static Address malloc(int size) { + if(size == 0) { + throw new IllegalArgumentException("Tried to allocate 0 bytes!"); + } + Address addr; + if(enableBufferOverflowCheck) { + addr = DirectMalloc.malloc(size + 12); + if(addr.toInt() == 0) { + throw new OutOfMemoryError("DirectMalloc returned null pointer!"); + } + int tag = (int)(Math.random() * 2147483647.0); + addr.putInt(size); + addr.add(4).putInt(tag); + addr.add(size + 8).putInt(tag); + addr = addr.add(8); + }else { + addr = DirectMalloc.malloc(size); + if(addr.toInt() == 0) { + throw new OutOfMemoryError("DirectMalloc returned null pointer!"); + } + } + return addr; + } + + public static Address calloc(int size) { + Address addr; + if(enableBufferOverflowCheck) { + if(size == 0) { + throw new OutOfMemoryError("Tried to allocate 0 bytes!"); + } + addr = DirectMalloc.calloc(size + 12); + if(addr.toInt() == 0) { + throw new OutOfMemoryError("DirectMalloc returned null pointer!"); + } + int tag = (int)(Math.random() * 2147483647.0); + addr.putInt(size); + addr.add(4).putInt(tag); + addr.add(size + 8).putInt(tag); + addr = addr.add(8); + }else { + addr = DirectMalloc.calloc(size); + if(addr.toInt() == 0) { + throw new OutOfMemoryError("DirectMalloc returned null pointer!"); + } + } + return addr; + } + + public static void free(Address ptr) { + if(ptr.toInt() != 0) { + if(enableBufferOverflowCheck) { + ptr = ptr.add(-8); + int size = ptr.getInt(); + int tag = ptr.add(4).getInt(); + if(tag != ptr.add(size + 8).getInt()) { + throw new RuntimeException("Detected a buffer write overflow"); + } + DirectMalloc.free(ptr); + }else { + DirectMalloc.free(ptr); + } + } + } + + public static ByteBuffer allocateByteBuffer(int size) { + return new DirectMallocByteBuffer(malloc(size), size, true); + } + + public static ShortBuffer allocateShortBuffer(int size) { + return new DirectMallocShortBuffer(malloc(size << 1), size, true); + } + + public static IntBuffer allocateIntBuffer(int size) { + return new DirectMallocIntBuffer(malloc(size << 2), size, true); + } + + public static FloatBuffer allocateFloatBuffer(int size) { + return new DirectMallocFloatBuffer(malloc(size << 2), size, true); + } + + public static void freeByteBuffer(ByteBuffer buffer) { + DirectMallocByteBuffer buf = (DirectMallocByteBuffer)buffer; + if(buf.original) { + WASMGCBufferAllocator.free(buf.address); + }else { + throwNotOriginal(buf); + } + } + + public static void freeShortBuffer(ShortBuffer buffer) { + DirectMallocShortBuffer buf = (DirectMallocShortBuffer)buffer; + if(buf.original) { + WASMGCBufferAllocator.free(buf.address); + }else { + throwNotOriginal(buf); + } + } + + public static void freeIntBuffer(IntBuffer buffer) { + DirectMallocIntBuffer buf = (DirectMallocIntBuffer)buffer; + if(buf.original) { + WASMGCBufferAllocator.free(buf.address); + }else { + throwNotOriginal(buf); + } + } + + public static void freeFloatBuffer(FloatBuffer buffer) { + DirectMallocFloatBuffer buf = (DirectMallocFloatBuffer)buffer; + if(buf.original) { + WASMGCBufferAllocator.free(buf.address); + }else { + throwNotOriginal(buf); + } + } + + public static Address getByteBufferAddress(ByteBuffer buffer) { + DirectMallocByteBuffer buf = (DirectMallocByteBuffer)buffer; + return buf.address.add(buf.position()); + } + + public static Address getShortBufferAddress(ShortBuffer buffer) { + DirectMallocShortBuffer buf = (DirectMallocShortBuffer)buffer; + return buf.address.add(buf.position() << 1); + } + + public static Address getIntBufferAddress(IntBuffer buffer) { + DirectMallocIntBuffer buf = (DirectMallocIntBuffer)buffer; + return buf.address.add(buf.position() << 2); + } + + public static Address getFloatBufferAddress(FloatBuffer buffer) { + DirectMallocFloatBuffer buf = (DirectMallocFloatBuffer)buffer; + return buf.address.add(buf.position() << 2); + } + + public static Int8Array getByteBufferView(ByteBuffer buffer) { + DirectMallocByteBuffer buf = (DirectMallocByteBuffer)buffer; + return getByteBufferView0(buf.address.add(buf.position()), buf.remaining()); + } + + @Import(module = "WASMGCBufferAllocator", name = "getByteBufferView") + public static native Int8Array getByteBufferView0(Address addr, int length); + + public static Uint8Array getUnsignedByteBufferView(ByteBuffer buffer) { + DirectMallocByteBuffer buf = (DirectMallocByteBuffer)buffer; + return getUnsignedByteBufferView0(buf.address.add(buf.position()), buf.remaining()); + } + + public static Uint8Array getUnsignedByteBufferView(ShortBuffer buffer) { + DirectMallocIntBuffer buf = (DirectMallocIntBuffer)buffer; + return getUnsignedByteBufferView0(buf.address.add(buf.position()), buf.remaining() << 1); + } + + public static Uint8Array getUnsignedByteBufferView(IntBuffer buffer) { + DirectMallocIntBuffer buf = (DirectMallocIntBuffer)buffer; + return getUnsignedByteBufferView0(buf.address.add(buf.position()), buf.remaining() << 2); + } + + @Import(module = "WASMGCBufferAllocator", name = "getUnsignedByteBufferView") + public static native Uint8Array getUnsignedByteBufferView0(Address addr, int length); + + public static Uint8ClampedArray getUnsignedClampedByteBufferView(ByteBuffer buffer) { + DirectMallocByteBuffer buf = (DirectMallocByteBuffer)buffer; + return getUnsignedClampedByteBufferView0(buf.address.add(buf.position()), buf.remaining()); + } + + @Import(module = "WASMGCBufferAllocator", name = "getUnsignedClampedByteBufferView") + public static native Uint8ClampedArray getUnsignedClampedByteBufferView0(Address addr, int length); + + public static Int16Array getShortBufferView(ShortBuffer buffer) { + DirectMallocShortBuffer buf = (DirectMallocShortBuffer)buffer; + return getShortBufferView0(buf.address.add(buf.position() << 1), buf.remaining()); + } + + public static Int16Array getShortBufferView(ByteBuffer buffer) { + DirectMallocByteBuffer buf = (DirectMallocByteBuffer)buffer; + return getShortBufferView0(buf.address.add(buf.position()), buf.remaining() >> 1); + } + + @Import(module = "WASMGCBufferAllocator", name = "getShortBufferView") + public static native Int16Array getShortBufferView0(Address addr, int length); + + public static Uint16Array getUnsignedShortBufferView(ShortBuffer buffer) { + DirectMallocShortBuffer buf = (DirectMallocShortBuffer)buffer; + return getUnsignedShortBufferView0(buf.address.add(buf.position() << 1), buf.remaining()); + } + + public static Uint16Array getUnsignedShortBufferView(ByteBuffer buffer) { + DirectMallocByteBuffer buf = (DirectMallocByteBuffer)buffer; + return getUnsignedShortBufferView0(buf.address.add(buf.position()), buf.remaining() >> 1); + } + + @Import(module = "WASMGCBufferAllocator", name = "getUnsignedShortBufferView") + public static native Uint16Array getUnsignedShortBufferView0(Address addr, int length); + + public static Int32Array getIntBufferView(IntBuffer buffer) { + DirectMallocIntBuffer buf = (DirectMallocIntBuffer)buffer; + return getIntBufferView0(buf.address.add(buf.position() << 2), buf.remaining()); + } + + public static Int32Array getIntBufferView(ByteBuffer buffer) { + DirectMallocByteBuffer buf = (DirectMallocByteBuffer)buffer; + return getIntBufferView0(buf.address.add(buf.position()), buf.remaining() >> 2); + } + + @Import(module = "WASMGCBufferAllocator", name = "getIntBufferView") + public static native Int32Array getIntBufferView0(Address addr, int length); + + public static Float32Array getFloatBufferView(FloatBuffer buffer) { + DirectMallocFloatBuffer buf = (DirectMallocFloatBuffer)buffer; + return getFloatBufferView0(buf.address.add(buf.position() << 2), buf.remaining()); + } + + public static Float32Array getFloatBufferView(ByteBuffer buffer) { + DirectMallocByteBuffer buf = (DirectMallocByteBuffer)buffer; + return getFloatBufferView0(buf.address.add(buf.position()), buf.remaining() >> 2); + } + + @Import(module = "WASMGCBufferAllocator", name = "getFloatBufferView") + public static native Float32Array getFloatBufferView0(Address addr, int length); + + private static void throwNotOriginal(Object clazz) { + throw notOriginal(clazz); + } + + public static class WrongBufferClassType extends RuntimeException { + public WrongBufferClassType(String msg) { + super(msg); + } + } + + private static WrongBufferClassType notOriginal(Object clazz) { + return new WrongBufferClassType("Tried to pass a " + clazz.getClass().getSimpleName() + " which was not the original buffer"); + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/WASMGCDirectArrayConverter.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/WASMGCDirectArrayConverter.java new file mode 100644 index 0000000..69a5be2 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/WASMGCDirectArrayConverter.java @@ -0,0 +1,397 @@ +package net.lax1dude.eaglercraft.v1_8.internal.buffer; + +import org.teavm.interop.Address; +import org.teavm.interop.DirectMalloc; +import org.teavm.jso.typedarrays.Float32Array; +import org.teavm.jso.typedarrays.Int16Array; +import org.teavm.jso.typedarrays.Int32Array; +import org.teavm.jso.typedarrays.Int8Array; +import org.teavm.jso.typedarrays.Uint16Array; +import org.teavm.jso.typedarrays.Uint8Array; +import org.teavm.jso.typedarrays.Uint8ClampedArray; + +/** + * 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. + * + */ +public class WASMGCDirectArrayConverter { + + public static ByteBuffer byteArrayToBuffer(byte[] byteArray) { + int len = byteArray.length; + Address ret = WASMGCBufferAllocator.malloc(len); + WASMGCDirectArrayCopy.memcpy(ret, byteArray, 0, len); + return new DirectMallocByteBuffer(ret, len, true); + } + + public static ByteBuffer byteArrayToBuffer(byte[] byteArray, int offset, int length) { + if(offset < 0) throw Buffer.makeIOOBE(offset); + if(offset + length > byteArray.length) throw Buffer.makeIOOBE(offset + length - 1); + Address ret = WASMGCBufferAllocator.malloc(length); + WASMGCDirectArrayCopy.memcpy(ret, byteArray, offset, length); + return new DirectMallocByteBuffer(ret, length, true); + } + + public static ShortBuffer shortArrayToBuffer(short[] shortArray) { + int len = shortArray.length; + Address ret = WASMGCBufferAllocator.malloc(len << 1); + WASMGCDirectArrayCopy.memcpy(ret, shortArray, 0, len); + return new DirectMallocShortBuffer(ret, len, true); + } + + public static ShortBuffer shortArrayToBuffer(short[] shortArray, int offset, int length) { + if(offset < 0) throw Buffer.makeIOOBE(offset); + if(offset + length > shortArray.length) throw Buffer.makeIOOBE(offset + length - 1); + Address ret = WASMGCBufferAllocator.malloc(length << 1); + WASMGCDirectArrayCopy.memcpy(ret, shortArray, offset, length); + return new DirectMallocShortBuffer(ret, length, true); + } + + public static IntBuffer intArrayToBuffer(int[] intArray) { + int len = intArray.length; + Address ret = WASMGCBufferAllocator.malloc(len << 2); + WASMGCDirectArrayCopy.memcpy(ret, intArray, 0, len); + return new DirectMallocIntBuffer(ret, len, true); + } + + public static IntBuffer intArrayToBuffer(int[] intArray, int offset, int length) { + if(offset < 0) throw Buffer.makeIOOBE(offset); + if(offset + length > intArray.length) throw Buffer.makeIOOBE(offset + length - 1); + Address ret = WASMGCBufferAllocator.malloc(length << 2); + WASMGCDirectArrayCopy.memcpy(ret, intArray, offset, length); + return new DirectMallocIntBuffer(ret, length, true); + } + + public static FloatBuffer floatArrayToBuffer(float[] floatArray) { + int len = floatArray.length; + Address ret = WASMGCBufferAllocator.malloc(len << 2); + WASMGCDirectArrayCopy.memcpy(ret, floatArray, 0, len); + return new DirectMallocFloatBuffer(ret, len, true); + } + + public static FloatBuffer floatArrayToBuffer(float[] floatArray, int offset, int length) { + if(offset < 0) throw Buffer.makeIOOBE(offset); + if(offset + length > floatArray.length) throw Buffer.makeIOOBE(offset + length - 1); + Address ret = WASMGCBufferAllocator.malloc(length << 2); + WASMGCDirectArrayCopy.memcpy(ret, floatArray, offset, length); + return new DirectMallocFloatBuffer(ret, length, true); + } + + private static final Uint8Array UINT8ZeroLength = new Uint8Array(0); + private static final Uint8ClampedArray UINT8CZeroLength = new Uint8ClampedArray(0); + private static final Int8Array INT8ZeroLength = new Int8Array(0); + private static final Uint16Array UINT16ZeroLength = new Uint16Array(0); + private static final Int16Array INT16ZeroLength = new Int16Array(0); + private static final Int32Array INT32ZeroLength = new Int32Array(0); + private static final Float32Array FLOAT32ZeroLength = new Float32Array(0); + + public static Uint8Array byteArrayToExternU8Array(byte[] byteArray) { + int len = byteArray.length; + if(len == 0) { + return UINT8ZeroLength; + } + Address addr = WASMGCBufferAllocator.malloc(len); + WASMGCDirectArrayCopy.memcpy(addr, byteArray, 0, len); + Uint8Array ret = new Uint8Array(len); + ret.set(WASMGCBufferAllocator.getUnsignedByteBufferView0(addr, len)); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static Uint8ClampedArray byteArrayToExternU8CArray(byte[] byteArray) { + int len = byteArray.length; + if(len == 0) { + return UINT8CZeroLength; + } + Address addr = WASMGCBufferAllocator.malloc(len); + WASMGCDirectArrayCopy.memcpy(addr, byteArray, 0, len); + Uint8ClampedArray ret = new Uint8ClampedArray(len); + ret.set(WASMGCBufferAllocator.getUnsignedClampedByteBufferView0(addr, len)); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static Int8Array byteArrayToExternI8Array(byte[] byteArray) { + int len = byteArray.length; + if(len == 0) { + return INT8ZeroLength; + } + Address addr = WASMGCBufferAllocator.malloc(len); + WASMGCDirectArrayCopy.memcpy(addr, byteArray, 0, len); + Int8Array ret = new Int8Array(len); + ret.set(WASMGCBufferAllocator.getByteBufferView0(addr, len)); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static Uint16Array byteArrayToExternU16Array(byte[] byteArray) { + int len = byteArray.length & 0xFFFFFFFE; + if(len == 0) { + return UINT16ZeroLength; + } + Address addr = WASMGCBufferAllocator.malloc(len); + WASMGCDirectArrayCopy.memcpy(addr, byteArray, 0, len); + len >>= 1; + Uint16Array ret = new Uint16Array(len); + ret.set(WASMGCBufferAllocator.getUnsignedShortBufferView0(addr, len)); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static Int16Array byteArrayToExternI16Array(byte[] byteArray) { + int len = byteArray.length & 0xFFFFFFFE; + if(len == 0) { + return INT16ZeroLength; + } + Address addr = WASMGCBufferAllocator.malloc(len); + WASMGCDirectArrayCopy.memcpy(addr, byteArray, 0, len); + len >>= 1; + Int16Array ret = new Int16Array(len); + ret.set(WASMGCBufferAllocator.getShortBufferView0(addr, len)); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static Uint16Array shortArrayToExternU16Array(short[] shortArray) { + int len = shortArray.length; + if(len == 0) { + return UINT16ZeroLength; + } + Address addr = WASMGCBufferAllocator.malloc(len << 1); + WASMGCDirectArrayCopy.memcpy(addr, shortArray, 0, len); + Uint16Array ret = new Uint16Array(len); + ret.set(WASMGCBufferAllocator.getUnsignedShortBufferView0(addr, len)); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static Int16Array shortArrayToExternI16Array(short[] shortArray) { + int len = shortArray.length; + if(len == 0) { + return INT16ZeroLength; + } + Address addr = WASMGCBufferAllocator.malloc(len << 1); + WASMGCDirectArrayCopy.memcpy(addr, shortArray, 0, len); + Int16Array ret = new Int16Array(len); + ret.set(WASMGCBufferAllocator.getShortBufferView0(addr, len)); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static Int32Array byteArrayToExternI32Array(byte[] byteArray) { + int len = byteArray.length & 0xFFFFFFFC; + if(len == 0) { + return INT32ZeroLength; + } + Address addr = WASMGCBufferAllocator.malloc(len); + WASMGCDirectArrayCopy.memcpy(addr, byteArray, 0, len); + len >>= 2; + Int32Array ret = new Int32Array(len); + ret.set(WASMGCBufferAllocator.getIntBufferView0(addr, len)); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static Int32Array intArrayToExternI32Array(int[] intArray) { + int len = intArray.length; + if(len == 0) { + return INT32ZeroLength; + } + Address addr = WASMGCBufferAllocator.malloc(len << 2); + WASMGCDirectArrayCopy.memcpy(addr, intArray, 0, len); + Int32Array ret = new Int32Array(len); + ret.set(WASMGCBufferAllocator.getIntBufferView0(addr, len)); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static Float32Array byteArrayToExternF32Array(byte[] byteArray) { + int len = byteArray.length & 0xFFFFFFFC; + if(len == 0) { + return FLOAT32ZeroLength; + } + Address addr = WASMGCBufferAllocator.malloc(len); + WASMGCDirectArrayCopy.memcpy(addr, byteArray, 0, len); + len >>= 2; + Float32Array ret = new Float32Array(len); + ret.set(WASMGCBufferAllocator.getFloatBufferView0(addr, len)); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static Float32Array floatArrayToExternF32Array(float[] floatArray) { + int len = floatArray.length; + if(len == 0) { + return FLOAT32ZeroLength; + } + Address addr = WASMGCBufferAllocator.malloc(len << 2); + WASMGCDirectArrayCopy.memcpy(addr, floatArray, 0, len); + Float32Array ret = new Float32Array(len); + ret.set(WASMGCBufferAllocator.getFloatBufferView0(addr, len)); + WASMGCBufferAllocator.free(addr); + return ret; + } + + private static final byte[] byteZeroLength = new byte[0]; + private static final short[] shortZeroLength = new short[0]; + private static final int[] intZeroLength = new int[0]; + private static final float[] floatZeroLength = new float[0]; + + public static byte[] externU8ArrayToByteArray(Uint8Array U8Array) { + int len = U8Array.getLength(); + if(len == 0) { + return byteZeroLength; + } + Address addr = WASMGCBufferAllocator.malloc(len); + WASMGCBufferAllocator.getUnsignedByteBufferView0(addr, len).set(U8Array); + byte[] ret = new byte[len]; + WASMGCDirectArrayCopy.memcpy(ret, 0, addr, len); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static byte[] externU8CArrayToByteArray(Uint8ClampedArray U8CArray) { + int len = U8CArray.getLength(); + if(len == 0) { + return byteZeroLength; + } + Address addr = WASMGCBufferAllocator.malloc(len); + WASMGCBufferAllocator.getUnsignedClampedByteBufferView0(addr, len).set(U8CArray); + byte[] ret = new byte[len]; + WASMGCDirectArrayCopy.memcpy(ret, 0, addr, len); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static byte[] externI8ArrayToByteArray(Int8Array I8Array) { + int len = I8Array.getLength(); + if(len == 0) { + return byteZeroLength; + } + Address addr = WASMGCBufferAllocator.malloc(len); + WASMGCBufferAllocator.getByteBufferView0(addr, len).set(I8Array); + byte[] ret = new byte[len]; + WASMGCDirectArrayCopy.memcpy(ret, 0, addr, len); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static byte[] externU16ArrayToByteArray(Uint16Array U16Array) { + int len = U16Array.getLength(); + if(len == 0) { + return byteZeroLength; + } + int len2 = len << 1; + Address addr = WASMGCBufferAllocator.malloc(len2); + WASMGCBufferAllocator.getUnsignedShortBufferView0(addr, len).set(U16Array); + byte[] ret = new byte[len2]; + WASMGCDirectArrayCopy.memcpy(ret, 0, addr, len2); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static byte[] externI16ArrayToByteArray(Int16Array I16Array) { + int len = I16Array.getLength(); + if(len == 0) { + return byteZeroLength; + } + int len2 = len << 1; + Address addr = WASMGCBufferAllocator.malloc(len2); + WASMGCBufferAllocator.getShortBufferView0(addr, len).set(I16Array); + byte[] ret = new byte[len2]; + WASMGCDirectArrayCopy.memcpy(ret, 0, addr, len2); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static short[] externU16ArrayToShortArray(Uint16Array U16Array) { + int len = U16Array.getLength(); + if(len == 0) { + return shortZeroLength; + } + Address addr = WASMGCBufferAllocator.malloc(len << 1); + WASMGCBufferAllocator.getUnsignedShortBufferView0(addr, len).set(U16Array); + short[] ret = new short[len]; + WASMGCDirectArrayCopy.memcpy(ret, 0, addr, len); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static short[] externI16ArrayToShortArray(Int16Array I16Array) { + int len = I16Array.getLength(); + if(len == 0) { + return shortZeroLength; + } + Address addr = WASMGCBufferAllocator.malloc(len << 1); + WASMGCBufferAllocator.getShortBufferView0(addr, len).set(I16Array); + short[] ret = new short[len]; + WASMGCDirectArrayCopy.memcpy(ret, 0, addr, len); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static byte[] externI32ArrayToByteArray(Int32Array I32Array) { + int len = I32Array.getLength(); + if(len == 0) { + return byteZeroLength; + } + int len2 = len << 2; + Address addr = WASMGCBufferAllocator.malloc(len2); + WASMGCBufferAllocator.getIntBufferView0(addr, len).set(I32Array); + byte[] ret = new byte[len2]; + WASMGCDirectArrayCopy.memcpy(ret, 0, addr, len2); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static int[] externI32ArrayToIntArray(Int32Array I32Array) { + int len = I32Array.getLength(); + if(len == 0) { + return intZeroLength; + } + Address addr = WASMGCBufferAllocator.malloc(len << 2); + WASMGCBufferAllocator.getIntBufferView0(addr, len).set(I32Array); + int[] ret = new int[len]; + WASMGCDirectArrayCopy.memcpy(ret, 0, addr, len); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static byte[] externF32ArrayToByteArray(Float32Array F32Array) { + int len = F32Array.getLength(); + if(len == 0) { + return byteZeroLength; + } + int len2 = len << 2; + Address addr = WASMGCBufferAllocator.malloc(len2); + WASMGCBufferAllocator.getFloatBufferView0(addr, len).set(F32Array); + byte[] ret = new byte[len2]; + WASMGCDirectArrayCopy.memcpy(ret, 0, addr, len2); + WASMGCBufferAllocator.free(addr); + return ret; + } + + public static float[] externF32ArrayToFloatArray(Float32Array F32Array) { + int len = F32Array.getLength(); + if(len == 0) { + return floatZeroLength; + } + Address addr = WASMGCBufferAllocator.malloc(len << 2); + WASMGCBufferAllocator.getFloatBufferView0(addr, len).set(F32Array); + float[] ret = new float[len]; + WASMGCDirectArrayCopy.memcpy(ret, 0, addr, len); + WASMGCBufferAllocator.free(addr); + return ret; + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/WASMGCDirectArrayCopy.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/WASMGCDirectArrayCopy.java new file mode 100644 index 0000000..0ad3f1d --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/buffer/WASMGCDirectArrayCopy.java @@ -0,0 +1,123 @@ +package net.lax1dude.eaglercraft.v1_8.internal.buffer; + +import org.teavm.interop.Address; +import org.teavm.interop.Unmanaged; + +/** + * 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. + * + */ +public class WASMGCDirectArrayCopy { + + @Unmanaged + public static void memcpy(Address dest, byte[] src, int srcOffset, int count) { + Address destEnd = dest.add(count); + while(dest.isLessThan(destEnd)) { + dest.putByte(src[srcOffset]); + ++srcOffset; + dest = dest.add(1); + } + } + + @Unmanaged + public static void memcpy(Address dest, short[] src, int srcOffset, int count) { + Address destEnd = dest.add(count << 1); + while(dest.isLessThan(destEnd)) { + dest.putShort(src[srcOffset]); + ++srcOffset; + dest = dest.add(2); + } + } + + @Unmanaged + public static void memcpy(Address dest, char[] src, int srcOffset, int count) { + Address destEnd = dest.add(count << 1); + while(dest.isLessThan(destEnd)) { + dest.putChar(src[srcOffset]); + ++srcOffset; + dest = dest.add(2); + } + } + + @Unmanaged + public static void memcpy(Address dest, int[] src, int srcOffset, int count) { + Address destEnd = dest.add(count << 2); + while(dest.isLessThan(destEnd)) { + dest.putInt(src[srcOffset]); + ++srcOffset; + dest = dest.add(4); + } + } + + @Unmanaged + public static void memcpy(Address dest, float[] src, int srcOffset, int count) { + Address destEnd = dest.add(count << 2); + while(dest.isLessThan(destEnd)) { + dest.putFloat(src[srcOffset]); + ++srcOffset; + dest = dest.add(4); + } + } + + @Unmanaged + public static void memcpy(byte[] dest, int destOffset, Address src, int count) { + Address srcEnd = src.add(count); + while(src.isLessThan(srcEnd)) { + dest[destOffset] = src.getByte(); + ++destOffset; + src = src.add(1); + } + } + + @Unmanaged + public static void memcpy(short[] dest, int destOffset, Address src, int count) { + Address srcEnd = src.add(count << 1); + while(src.isLessThan(srcEnd)) { + dest[destOffset] = src.getShort(); + ++destOffset; + src = src.add(2); + } + } + + @Unmanaged + public static void memcpy(char[] dest, int destOffset, Address src, int count) { + Address srcEnd = src.add(count << 1); + while(src.isLessThan(srcEnd)) { + dest[destOffset] = src.getChar(); + ++destOffset; + src = src.add(2); + } + } + + @Unmanaged + public static void memcpy(int[] dest, int destOffset, Address src, int count) { + Address srcEnd = src.add(count << 2); + while(src.isLessThan(srcEnd)) { + dest[destOffset] = src.getInt(); + ++destOffset; + src = src.add(4); + } + } + + @Unmanaged + public static void memcpy(float[] dest, int destOffset, Address src, int count) { + Address srcEnd = src.add(count << 2); + while(src.isLessThan(srcEnd)) { + dest[destOffset] = src.getFloat(); + ++destOffset; + src = src.add(4); + } + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/BetterJSStringConverter.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/BetterJSStringConverter.java new file mode 100644 index 0000000..b953903 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/BetterJSStringConverter.java @@ -0,0 +1,81 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import org.teavm.interop.Address; +import org.teavm.interop.DirectMalloc; +import org.teavm.interop.Import; +import org.teavm.interop.Unmanaged; +import org.teavm.jso.core.JSArray; +import org.teavm.jso.core.JSString; + +import net.lax1dude.eaglercraft.v1_8.EagUtils; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCBufferAllocator; + +/** + * 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. + * + */ +public class BetterJSStringConverter { + + private static final TextDecoder textDecoder = new TextDecoder("utf-16"); + + @Unmanaged + public static JSString stringToJS(String input) { + if(input == null) return null; + int len = input.length(); + Address tmpAddr = WASMGCBufferAllocator.malloc(len << 1); + for(int i = 0; i < len; ++i) { + tmpAddr.add(i << 1).putChar(input.charAt(i)); + } + JSString ret = textDecoder.decode(WASMGCBufferAllocator.getUnsignedByteBufferView0(tmpAddr, len << 1)); + WASMGCBufferAllocator.free(tmpAddr); + return ret; + } + + @Unmanaged + public static JSArray stringArrayToJS(String[] input) { + if(input == null) return null; + int len = input.length; + JSArray ret = new JSArray<>(len); + for(int i = 0; i < len; ++i) { + ret.set(i, stringToJS(input[i])); + } + return ret; + } + + @Unmanaged + public static String stringFromJS(JSString input) { + if(input == null) return null; + int len = input.getLength(); + char[] chars = new char[len]; + for(int i = 0; i < len; ++i) { + chars[i] = charCodeAt(input, i); + } + return new String(chars); + } + + @Import(module = "teavmJso", name = "charAt") + private static native char charCodeAt(JSString str, int idx); + + @Unmanaged + public static String[] stringArrayFromJS(JSArray input) { + if(input == null) return null; + int len = input.getLength(); + String[] ret = new String[len]; + for(int i = 0; i < len; ++i) { + ret[i] = stringFromJS(input.get(i)); + } + return ret; + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/ClientMain.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/ClientMain.java new file mode 100644 index 0000000..8b99951 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/ClientMain.java @@ -0,0 +1,96 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import java.io.PrintStream; + +import org.teavm.interop.Import; +import org.teavm.jso.JSObject; +import org.teavm.jso.browser.Window; + +import net.lax1dude.eaglercraft.v1_8.EagRuntime; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.opts.JSEaglercraftXOptsRoot; +import net.minecraft.client.main.Main; + +/** + * 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. + * + */ +public class ClientMain { + + private static final PrintStream systemOut = System.out; + private static final PrintStream systemErr = System.err; + public static String configLocalesFolder = null; + + public static void _main() { + try { + systemOut.println("ClientMain: [INFO] eaglercraftx wasm gc is starting..."); + JSObject opts = getEaglerXOpts(); + + if(opts == null) { + systemErr.println("ClientMain: [ERROR] the \"window.eaglercraftXOpts\" variable is undefined"); + systemErr.println("ClientMain: [ERROR] eaglercraftx cannot start"); + Window.alert("ERROR: game cannot start, the \"window.eaglercraftXOpts\" variable is undefined"); + return; + } + + try { + JSEaglercraftXOptsRoot eaglercraftOpts = (JSEaglercraftXOptsRoot)opts; + + configLocalesFolder = eaglercraftOpts.getLocalesURI("lang"); + if(configLocalesFolder.endsWith("/")) { + configLocalesFolder = configLocalesFolder.substring(0, configLocalesFolder.length() - 1); + } + + ((WASMGCClientConfigAdapter)WASMGCClientConfigAdapter.instance).loadNative(eaglercraftOpts); + + systemOut.println("ClientMain: [INFO] configuration was successful"); + }catch(Throwable t) { + systemErr.println("ClientMain: [ERROR] the \"window.eaglercraftXOpts\" variable is invalid"); + EagRuntime.debugPrintStackTraceToSTDERR(t); + systemErr.println("ClientMain: [ERROR] eaglercraftx cannot start"); + Window.alert("ERROR: game cannot start, the \"window.eaglercraftXOpts\" variable is invalid: " + t.toString()); + return; + } + + systemOut.println("ClientMain: [INFO] initializing eaglercraftx runtime"); + + try { + EagRuntime.create(); + }catch(Throwable t) { + systemErr.println("ClientMain: [ERROR] eaglercraftx's runtime could not be initialized!"); + EagRuntime.debugPrintStackTraceToSTDERR(t); + PlatformRuntime.writeCrashReport("EaglercraftX's runtime could not be initialized!\n\n" + EagRuntime.getStackTrace(t)); + systemErr.println("ClientMain: [ERROR] eaglercraftx cannot start"); + return; + } + + systemOut.println("ClientMain: [INFO] launching eaglercraftx main thread"); + + try { + Main.appMain(); + }catch(Throwable t) { + systemErr.println("ClientMain: [ERROR] unhandled exception caused main thread to exit"); + EagRuntime.debugPrintStackTraceToSTDERR(t); + PlatformRuntime.writeCrashReport("Unhandled exception caused main thread to exit!\n\n" + EagRuntime.getStackTrace(t)); + } + }finally { + systemErr.println("ClientMain: [ERROR] eaglercraftx main thread has exited"); + } + } + + + @Import(module = "platformRuntime", name = "getEaglercraftXOpts") + private static native JSObject getEaglerXOpts(); + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/EPKLoader.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/EPKLoader.java new file mode 100644 index 0000000..1469dd9 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/EPKLoader.java @@ -0,0 +1,191 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import com.jcraft.jzlib.CRC32; +import com.jcraft.jzlib.GZIPInputStream; +import com.jcraft.jzlib.InflaterInputStream; + +import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.EaglerBufferInputStream; + +/** + * Copyright (c) 2022-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. + * + */ +public class EPKLoader { + + public static final void loadEPK(ByteBuffer epkFile, Map loadedFiles) throws IOException { + loadEPK(epkFile, "", loadedFiles); + } + + public static final void loadEPK(ByteBuffer epkFile, String path, Map loadedFiles) throws IOException { + int byteLength = epkFile.remaining(); + int l = byteLength - 16; + if(l < 1) { + throw new IOException("EPK file is incomplete"); + } + + EaglerBufferInputStream is = new EaglerBufferInputStream(epkFile); + + byte[] header = new byte[8]; + is.read(header); + String type = readASCII(header); + + if(!"EAGPKG$$".equals(type)) { + throw new IOException("Invalid EPK file type '" + type + "'"); + } + + byte[] endCode = new byte[] { (byte)':', (byte)':', (byte)':', (byte)'Y', + (byte)'E', (byte)'E', (byte)':', (byte)'>' }; + for(int i = 0; i < 8; ++i) { + if(epkFile.get(byteLength - 8 + i) != endCode[i]) { + throw new IOException("EPK file is missing EOF code (:::YEE:>)"); + } + } + + epkFile.limit(epkFile.limit() - 8); + + String vers = readASCII(is); + if(!vers.startsWith("ver2.")) { + throw new IOException("Unknown or invalid EPK version: " + vers); + } + + is.skip(is.read()); // skip filename + is.skip(loadShort(is)); // skip comment + is.skip(8); // skip millis date + + int numFiles = loadInt(is); + + char compressionType = (char)is.read(); + + InputStream zis; + switch(compressionType) { + case 'G': + zis = new GZIPInputStream(is); + break; + case 'Z': + zis = new InflaterInputStream(is); + break; + case '0': + zis = is; + break; + default: + throw new IOException("Invalid or unsupported EPK compression: " + compressionType); + } + + int blockFile = ('F' << 24) | ('I' << 16) | ('L' << 8) | 'E'; + int blockEnd = ('E' << 24) | ('N' << 16) | ('D' << 8) | '$'; + int blockHead = ('H' << 24) | ('E' << 16) | ('A' << 8) | 'D'; + + if(path.length() > 0 && !path.endsWith("/")) { + path = path + "/"; + } + + CRC32 crc32 = new CRC32(); + int blockType; + for(int i = 0; i < numFiles; ++i) { + + blockType = loadInt(zis); + + if(blockType == blockEnd) { + throw new IOException("Unexpected END when there are still " + (numFiles - i) + " files remaining"); + } + + String name = readASCII(zis); + int len = loadInt(zis); + + if(i == 0) { + if(blockType == blockHead) { + byte[] readType = new byte[len]; + zis.read(readType); + if(!"file-type".equals(name) || !"epk/resources".equals(readASCII(readType))) { + throw new IOException("EPK is not of file-type 'epk/resources'!"); + } + if(zis.read() != '>') { + throw new IOException("Object '" + name + "' is incomplete"); + } + continue; + }else { + throw new IOException("File '" + name + "' did not have a file-type block as the first entry in the file"); + } + } + + if(blockType == blockFile) { + if(len < 5) { + throw new IOException("File '" + name + "' is incomplete"); + } + + int expectedCRC = loadInt(zis); + + byte[] load = new byte[len - 5]; + zis.read(load); + + if(len > 5) { + crc32.reset(); + crc32.update(load, 0, load.length); + if(expectedCRC != (int)crc32.getValue()) { + throw new IOException("File '" + name + "' has an invalid checksum"); + } + } + + if(zis.read() != ':') { + throw new IOException("File '" + name + "' is incomplete"); + } + + loadedFiles.put(path + name, load); + }else { + zis.skip(len); + } + + if(zis.read() != '>') { + throw new IOException("Object '" + name + "' is incomplete"); + } + } + + if(loadInt(zis) != blockEnd) { + throw new IOException("EPK missing END$ object"); + } + + zis.close(); + } + + private static final int loadShort(InputStream is) throws IOException { + return (is.read() << 8) | is.read(); + } + + private static final int loadInt(InputStream is) throws IOException { + return (is.read() << 24) | (is.read() << 16) | (is.read() << 8) | is.read(); + } + + private static final String readASCII(byte[] bytesIn) throws IOException { + char[] charIn = new char[bytesIn.length]; + for(int i = 0; i < bytesIn.length; ++i) { + charIn[i] = (char)((int)bytesIn[i] & 0xFF); + } + return new String(charIn); + } + + private static final String readASCII(InputStream bytesIn) throws IOException { + int len = bytesIn.read(); + char[] charIn = new char[len]; + for(int i = 0; i < len; ++i) { + charIn[i] = (char)(bytesIn.read() & 0xFF); + } + return new String(charIn); + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/EarlyLoadScreen.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/EarlyLoadScreen.java new file mode 100644 index 0000000..3ce4c47 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/EarlyLoadScreen.java @@ -0,0 +1,262 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import static net.lax1dude.eaglercraft.v1_8.internal.PlatformOpenGL.*; +import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.*; + +import net.lax1dude.eaglercraft.v1_8.Base64; +import net.lax1dude.eaglercraft.v1_8.internal.IBufferArrayGL; +import net.lax1dude.eaglercraft.v1_8.internal.IBufferGL; +import net.lax1dude.eaglercraft.v1_8.internal.IProgramGL; +import net.lax1dude.eaglercraft.v1_8.internal.IShaderGL; +import net.lax1dude.eaglercraft.v1_8.internal.ITextureGL; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformAssets; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformInput; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.FloatBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.IntBuffer; +import net.lax1dude.eaglercraft.v1_8.opengl.EaglercraftGPU; +import net.lax1dude.eaglercraft.v1_8.opengl.ImageData; + +/** + * Copyright (c) 2022-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. + * + */ +public class EarlyLoadScreen { + + public static final String loadScreen = "iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAYAAABS3GwHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAHx0lEQVR42u3da27jIBRAYbfqFp1FuovM/GLEMIDBhsRJviNVapsYY8y5vPz4ut/v9wX4UL4VAQgAEAAgAEAAgAAAAQACAAQACAAQACAAQACAAAABAAIABAAIABAAIABAAIAAAAEAAgAEAAgAEAAgAEAAgAAAAQACAAQACAAQACAAQACAAAABAAIABAAIABAAIABAAIAAAAEAAgAEAAgAEAAgAAgAEAAgAEAAgAAAAQACAAQACAAQACAAQACAAMBr86MI3ovf39/i/9Z1XdZ1VUgEeN/Kf7vdqt8hgC7QW6OCE+CjK/+2bcv9fieCLtDjux9x/1t/u1xOveWSlisBXmQASoB/+fr6+vv7/X7vHteE8hxZrrpAkyo/2mU42soSgAAfN8YZ3aoSQOV/GNu2ZX9vGdjPEuBnVmXIVYqePly8famCne0TtuS1tt/a9kfSbWnqZw2u9yQesc91XZv7/iO2a+I+iG3b7uu63pdl2f1Z17WaTksaaXrbtk3JaynvR/O5l6/WtPaON3d8tf3v7e9d+RkVPeIVyDRKpREtfL+nGdxL7/f3d9m2bTdS5VZL4/Rz0fcRszm32604jZrLUyi/UXlb1/WlunKhTE63iCMif0tkao1IaXqlqFWKlr2RsTUPpXRLrUnYpqVlircfdby9LUCpbHpa1lyeW8tgL51SmZ9N+2dE5GqJlrkI0xJxaumV0ixt0xrd07TDdrl+aDoeGNnfbzne0RE1HqSOaF3SljptyXP7qF3QN3zi4Yw9LdF0r5+Zs7u175mLirU85KJiLbK3pt2bj1qZ1CJaz356WoD0u2ejaq11XNf1708uf73jqqeOAXotbIlgZ/t0tfSPRulZ050j0jubRjz2CGU/clyRRvvwv1LPIR4X5r6TtlJPmwY9W5la54vfea5+Zhm2dnniyj+j3GtdxCsMzL+vWAmuyujK2dLXnVGGYSZsduXPlV0625Vbk0nlnFlXhrYAezdjPFOa2sD4GRetlY5hdhnmpoHjKcXZlb927Llp4JCvWYHy8leDxpHgbCH0zBo9s3vyiLK8QiBIxwiPaHWnjwFGZbjl9r5RAtxut92Fp5GLTqPHP735qpXDrK5QbjFz27b/Wp802IXu2Yz6cGoadDmwCHV0enVJFpbCfkqLQ6Mvg9g7riPToEfyfrYMl4ZLOUadw1rZh33H/ytNjcbnunfavakeX02As3P1rZVoT4KeVdBXESDN05HV4pFXDaQrxqkE6TnISfC0dYAZA5PSSu3orkeYiSil/Sl3cm3b9t+NKbMHxHtTpenvcT7C33Gez+b1e3QFvvrUY2nhZ/Qi0KtMC+f6/KWpytnnsjWoXuKWyNaZkyud/HTh55mVvTYt++h8zDiXlTFnkwS1wfhlBZgxj917acNe9H9mZWuJvjPuez0azJ5RPj1T3kMe/zJyUNMzkMpdJts6MNybyckNXo/cwLI0XtZ8ZkaldBwt2x65RHvGMRwZoO9dWLh3CfqofC0zZhtKU5fpiWkVIE4n3b423Zemf0SA5cQdVenxt9x70FJ+8TEfkbxUuXqDytnp0L2p0kewzJjeOnMSWtKKt92rQCNageXEDTot05xH1iZy5Xf2lsra9iMrZDjW2dG9ha/7wLuNS5ctpDevt9y2WBu0ptvnxh2l75YutOrtu+/1m+N8tw66022PlGHrcfVuP+NCwNrg+2ETFPcPI45yLSu8s1Yg8UY3xb8K6WP2WualrzJjhDl8f2Ll721iPeiWAG8hwMw+LQhw6co/cpWaPO/DR4wBchU23APQMiMy43EhuAZDp0FfaQxwRCJjAQK8xTigp0uk4hPgowbH+vkEAD4GL8gAAQACAAQACAAQACAAQACAAAABAAIABAAIABAAIABAAIAAAAEAAgAEAK7NJR6M9S6PLQzPHZr1sulSuXmCxQu3APHz+sNP6wOspr09/CL76ym3Tzr2t2sBHhk13+UYwgsmnvFeXwI8qUtRinZxZNq27e/3tm3Lvg8gjWRpxc09Rj3eb2l/ufTiZ5CG78Sfn305eO7durX8tH4W8pB+Pz32vTQJcGAcED+0Nv5//Pbw9GTl+sKh8sVRMo2WoWkPJy0WpiRB6XVFpa5IvF28v3RfvX36mpylBwKXPktbkjiI1I69liYBTg6E4wqTkyOWolRB4nTSE5XuszaI3dvfngRppM1F+9auTG4fuW1raeXendYiWk+aBBjQf44jZW/TWoriV3gRddwi9L57IPfY9lA5Q3nF6YZyq33WIkLt/NTSJMCAcUD4/Wzhxt2o3Hjg0a3emSdPt7Q2t9vtn3KrfXY0L7U091rWo599xBggjSgh0pSa79aTl4ugaR8913qU9ld6vWlvd6bn+7mB+96MUHpcLULtHftemlqAAwKEwVd6MtNBbK4C7kWLuMkuDT5zA+za/nKzMC0VOu0CtXQhal2UeKCfG2PUPsvNZrUcey3NV8Dj0Z/cvctNQ77DmogWAM0S7M0gQQvwluS6HFZ0CQA8DJdDgwAAAQACAAQACAAQACAAQACAAAABAAIABAAIABAAIABAAIAAAAEAAgAEAAgAEAAgAEAAgAAAAQACAAQACAAQACAAQACAAAABAAIABAAIABAAIABAAIAAAAEAAgAEAAgAEAAgAEAAgAAAAYBlWf4A1W4Hx65cJAoAAAAASUVORK5CYII="; + + private static IBufferGL vbo = null; + private static IProgramGL program = null; + private static ITextureGL finalTexture = null; + + public static void extractingAssetsScreen() { + boolean gles3 = checkOpenGLESVersion() >= 300; + boolean vaos = checkVAOCapable(); + + // create textures: + + ITextureGL tex = _wglGenTextures(); + _wglActiveTexture(GL_TEXTURE0); + _wglBindTexture(GL_TEXTURE_2D, tex); + _wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + _wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + _wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + _wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + ImageData img = PlatformAssets.loadImageFile(Base64.decodeBase64(loadScreen)); + ByteBuffer upload = PlatformRuntime.allocateByteBuffer(192*192*4); + IntBuffer pixelUpload = upload.asIntBuffer(); + pixelUpload.put(img.pixels); + pixelUpload.flip(); + _wglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 192, 192, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixelUpload); + + // create vertex buffer: + + FloatBuffer vertexUpload = upload.asFloatBuffer(); + vertexUpload.clear(); + vertexUpload.put(0.0f); vertexUpload.put(0.0f); + vertexUpload.put(0.0f); vertexUpload.put(1.0f); + vertexUpload.put(1.0f); vertexUpload.put(0.0f); + vertexUpload.put(1.0f); vertexUpload.put(0.0f); + vertexUpload.put(0.0f); vertexUpload.put(1.0f); + vertexUpload.put(1.0f); vertexUpload.put(1.0f); + vertexUpload.flip(); + + vbo = _wglGenBuffers(); + _wglBindBuffer(GL_ARRAY_BUFFER, vbo); + _wglBufferData(GL_ARRAY_BUFFER, vertexUpload, GL_STATIC_DRAW); + + PlatformRuntime.freeByteBuffer(upload); + + // compile the splash shader: + + IShaderGL vert = _wglCreateShader(GL_VERTEX_SHADER); + _wglShaderSource(vert, gles3 + ? "#version 300 es\nprecision mediump float; layout(location = 0) in vec2 a_pos; out vec2 v_pos; void main() { gl_Position = vec4(((v_pos = a_pos) - 0.5) * vec2(2.0, -2.0), 0.0, 1.0); }" + : "#version 100\nprecision mediump float; attribute vec2 a_pos; varying vec2 v_pos; void main() { gl_Position = vec4(((v_pos = a_pos) - 0.5) * vec2(2.0, -2.0), 0.0, 1.0); }"); + _wglCompileShader(vert); + + IShaderGL frag = _wglCreateShader(GL_FRAGMENT_SHADER); + _wglShaderSource(frag, gles3 + ? "#version 300 es\nprecision mediump float; precision mediump sampler2D; in vec2 v_pos; layout(location = 0) out vec4 fragColor; uniform sampler2D tex; uniform vec2 aspect; void main() { fragColor = vec4(textureLod(tex, clamp(v_pos * aspect - ((aspect - 1.0) * 0.5), 0.02, 0.98), 0.0).rgb, 1.0); }" + : "#version 100\nprecision mediump float; precision mediump sampler2D; varying vec2 v_pos; uniform sampler2D tex; uniform vec2 aspect; void main() { gl_FragColor = vec4(texture2D(tex, clamp(v_pos * aspect - ((aspect - 1.0) * 0.5), 0.02, 0.98)).rgb, 1.0); }"); + _wglCompileShader(frag); + + program = _wglCreateProgram(); + + _wglAttachShader(program, vert); + _wglAttachShader(program, frag); + if(!gles3) { + _wglBindAttribLocation(program, 0, "a_pos"); + } + _wglLinkProgram(program); + _wglDetachShader(program, vert); + _wglDetachShader(program, frag); + _wglDeleteShader(vert); + _wglDeleteShader(frag); + + _wglUseProgram(program); + _wglUniform1i(_wglGetUniformLocation(program, "tex"), 0); + + int width = PlatformInput.getWindowWidth(); + int height = PlatformInput.getWindowHeight(); + float x, y; + if(width > height) { + x = (float)width / (float)height; + y = 1.0f; + }else { + x = 1.0f; + y = (float)height / (float)width; + } + + _wglActiveTexture(GL_TEXTURE0); + _wglBindTexture(GL_TEXTURE_2D, tex); + + _wglViewport(0, 0, width, height); + _wglClearColor(1.0f, 1.0f, 1.0f, 1.0f); + _wglClear(GL_COLOR_BUFFER_BIT); + + _wglUseProgram(program); + _wglUniform2f(_wglGetUniformLocation(program, "aspect"), x, y); + + IBufferArrayGL vao = null; + if(vaos) { + vao = _wglGenVertexArrays(); + _wglBindVertexArray(vao); + } + _wglEnableVertexAttribArray(0); + _wglVertexAttribPointer(0, 2, GL_FLOAT, false, 8, 0); + _wglDrawArrays(GL_TRIANGLES, 0, 6); + + _wglDisableVertexAttribArray(0); + + PlatformInput.update(); + + _wglUseProgram(null); + _wglBindBuffer(GL_ARRAY_BUFFER, null); + _wglBindTexture(GL_TEXTURE_2D, null); + _wglDeleteTextures(tex); + if(vaos) { + _wglDeleteVertexArrays(vao); + } + } + + public static void loadFinal(byte[] finalLoadScreen) { + ImageData img = PlatformAssets.loadImageFile(finalLoadScreen); + if(img == null) { + return; + } + finalTexture = _wglGenTextures(); + _wglActiveTexture(GL_TEXTURE0); + _wglBindTexture(GL_TEXTURE_2D, finalTexture); + _wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + _wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + _wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + _wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + IntBuffer upload = PlatformRuntime.allocateIntBuffer(img.width * img.height); + upload.put(img.pixels); + upload.flip(); + _wglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img.width, img.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, upload); + PlatformRuntime.freeIntBuffer(upload); + } + + public static void paintFinal(boolean softVAOs) { + if(finalTexture == null) return; + boolean vaos = checkVAOCapable(); + + _wglBindTexture(GL_TEXTURE_2D, finalTexture); + _wglUseProgram(program); + + int width = PlatformInput.getWindowWidth(); + int height = PlatformInput.getWindowHeight(); + float x, y; + if(width > height) { + x = (float)width / (float)height; + y = 1.0f; + }else { + x = 1.0f; + y = (float)height / (float)width; + } + + _wglActiveTexture(GL_TEXTURE0); + _wglBindTexture(GL_TEXTURE_2D, finalTexture); + + _wglViewport(0, 0, width, height); + _wglClearColor(1.0f, 1.0f, 1.0f, 1.0f); + _wglClear(GL_COLOR_BUFFER_BIT); + + _wglUniform2f(_wglGetUniformLocation(program, "aspect"), x, y); + + IBufferArrayGL vao = null; + if(vaos) { + if(softVAOs) { + vao = EaglercraftGPU.createGLBufferArray(); + EaglercraftGPU.bindGLBufferArray(vao); + }else { + vao = _wglGenVertexArrays(); + _wglBindVertexArray(vao); + } + } + if(vaos && softVAOs) { + EaglercraftGPU.bindVAOGLArrayBuffer(vbo); + EaglercraftGPU.enableVertexAttribArray(0); + EaglercraftGPU.vertexAttribPointer(0, 2, GL_FLOAT, false, 8, 0); + EaglercraftGPU.doDrawArrays(GL_TRIANGLES, 0, 6); + }else { + _wglBindBuffer(GL_ARRAY_BUFFER, vbo); + _wglEnableVertexAttribArray(0); + _wglVertexAttribPointer(0, 2, GL_FLOAT, false, 8, 0); + _wglDrawArrays(GL_TRIANGLES, 0, 6); + } + + if(!softVAOs) { + _wglDisableVertexAttribArray(0); + } + + PlatformInput.update(); + + _wglUseProgram(null); + if(!(vaos && softVAOs)) { + _wglBindBuffer(GL_ARRAY_BUFFER, null); + } + _wglBindTexture(GL_TEXTURE_2D, null); + if(softVAOs) { + EaglercraftGPU.clearCurrentBinding(EaglercraftGPU.CLEAR_BINDING_ACTIVE_TEXTURE | EaglercraftGPU.CLEAR_BINDING_TEXTURE0); + } + if(vaos) { + if(softVAOs) { + EaglercraftGPU.destroyGLBufferArray(vao); + }else { + _wglDeleteVertexArrays(vao); + } + } + } + + public static void destroy() { + if(vbo != null) { + _wglDeleteBuffers(vbo); + vbo = null; + } + if(program != null) { + _wglDeleteProgram(program); + program = null; + } + if(finalTexture != null) { + _wglDeleteTextures(finalTexture); + finalTexture = null; + } + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/IndexedDBFilesystem.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/IndexedDBFilesystem.java new file mode 100644 index 0000000..add2b1c --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/IndexedDBFilesystem.java @@ -0,0 +1,199 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import org.teavm.interop.Import; +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; +import org.teavm.jso.core.JSString; +import org.teavm.jso.indexeddb.IDBDatabase; +import org.teavm.jso.typedarrays.ArrayBuffer; +import org.teavm.jso.typedarrays.Uint8Array; + +import net.lax1dude.eaglercraft.v1_8.internal.IEaglerFilesystem; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime; +import net.lax1dude.eaglercraft.v1_8.internal.VFSFilenameIterator; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCBufferAllocator; +import net.lax1dude.eaglercraft.v1_8.internal.vfs2.EaglerFileSystemException; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformFilesystem.FilesystemDatabaseInitializationException; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformFilesystem.FilesystemDatabaseLockedException; + +/** + * 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. + * + */ +public class IndexedDBFilesystem implements IEaglerFilesystem { + + public static IEaglerFilesystem createFilesystem(String dbName) { + String filesystemDB = "_net_lax1dude_eaglercraft_v1_8_internal_PlatformFilesystem_1_8_8_" + dbName; + JSDatabaseOpen dbOpen = openDB(BetterJSStringConverter.stringToJS(filesystemDB)); + + if(dbOpen.getFailedLocked()) { + throw new FilesystemDatabaseLockedException(dbOpen.getFailedError()); + } + + if(dbOpen.getFailedInit()) { + throw new FilesystemDatabaseInitializationException(dbOpen.getFailedError()); + } + + IDBDatabase database = dbOpen.getDatabase(); + if(database == null) { + throw new NullPointerException("IDBDatabase is null!"); + } + + return new IndexedDBFilesystem(dbName, filesystemDB, database); + } + + private interface JSDatabaseOpen extends JSObject { + + @JSProperty + boolean getFailedInit(); + + @JSProperty + boolean getFailedLocked(); + + @JSProperty + String getFailedError(); + + @JSProperty + IDBDatabase getDatabase(); + + } + + @Import(module = "platformFilesystem", name = "openDB") + private static native JSDatabaseOpen openDB(JSString filesystemDB); + + private final String name; + private final String indexedDBName; + private IDBDatabase database; + + private IndexedDBFilesystem(String name, String indexedDBName, IDBDatabase database) { + this.name = name; + this.indexedDBName = indexedDBName; + this.database = database; + } + + @Override + public String getFilesystemName() { + return name; + } + + @Override + public String getInternalDBName() { + return "indexeddb:" + indexedDBName; + } + + @Override + public boolean isRamdisk() { + return false; + } + + @Override + public boolean eaglerDelete(String pathName) { + return eaglerDelete(database, BetterJSStringConverter.stringToJS(pathName)); + } + + @Import(module = "platformFilesystem", name = "eaglerDelete") + private static native boolean eaglerDelete(IDBDatabase database, JSString pathName); + + @Override + public ByteBuffer eaglerRead(String pathName) { + ArrayBuffer ar = eaglerRead(database, BetterJSStringConverter.stringToJS(pathName)); + if(ar == null) { + return null; + } + Uint8Array arr = new Uint8Array(ar); + ByteBuffer buf = PlatformRuntime.allocateByteBuffer(arr.getLength()); + WASMGCBufferAllocator.getUnsignedByteBufferView(buf).set(arr); + return buf; + } + + @Import(module = "platformFilesystem", name = "eaglerRead") + private static native ArrayBuffer eaglerRead(IDBDatabase database, JSString pathName); + + @Override + public void eaglerWrite(String pathName, ByteBuffer data) { + int len = data.remaining(); + Uint8Array arr = new Uint8Array(len); + arr.set(WASMGCBufferAllocator.getByteBufferView(data)); + if(!eaglerWrite(database, BetterJSStringConverter.stringToJS(pathName), arr.getBuffer())) { + throw new EaglerFileSystemException("Failed to write " + len + " byte file to indexeddb table: " + pathName); + } + } + + @Import(module = "platformFilesystem", name = "eaglerWrite") + private static native boolean eaglerWrite(IDBDatabase database, JSString pathName, ArrayBuffer arr); + + @Override + public boolean eaglerExists(String pathName) { + return eaglerExists(database, BetterJSStringConverter.stringToJS(pathName)); + } + + @Import(module = "platformFilesystem", name = "eaglerExists") + private static native boolean eaglerExists(IDBDatabase database, JSString pathName); + + @Override + public boolean eaglerMove(String pathNameOld, String pathNameNew) { + return eaglerMove(database, BetterJSStringConverter.stringToJS(pathNameOld), BetterJSStringConverter.stringToJS(pathNameNew)); + } + + @Import(module = "platformFilesystem", name = "eaglerMove") + private static native boolean eaglerMove(IDBDatabase database, JSString pathNameOld, JSString pathNameNew); + + @Override + public int eaglerCopy(String pathNameOld, String pathNameNew) { + return eaglerCopy(database, BetterJSStringConverter.stringToJS(pathNameOld), BetterJSStringConverter.stringToJS(pathNameNew)); + } + + @Import(module = "platformFilesystem", name = "eaglerCopy") + private static native int eaglerCopy(IDBDatabase database, JSString pathNameOld, JSString pathNameNew); + + @Override + public int eaglerSize(String pathName) { + return eaglerSize(database, BetterJSStringConverter.stringToJS(pathName)); + } + + @Import(module = "platformFilesystem", name = "eaglerSize") + private static native int eaglerSize(IDBDatabase database, JSString pathName); + + private interface JSDatabaseIteratorResult extends JSObject { + + @JSProperty + int getLength(); + + String getRow(int idx); + + } + + @Override + public void eaglerIterate(String pathName, VFSFilenameIterator itr, boolean recursive) { + JSDatabaseIteratorResult result = eaglerIterate(database, BetterJSStringConverter.stringToJS(pathName), recursive); + if(result != null) { + for(int i = 0, len = result.getLength(); i < len; ++i) { + itr.next(result.getRow(i)); + } + } + } + + @Import(module = "platformFilesystem", name = "eaglerIterate") + private static native JSDatabaseIteratorResult eaglerIterate(IDBDatabase database, JSString pathName, boolean recursive); + + @Override + public void closeHandle() { + if(database != null) { + database.close(); + database = null; + } + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/JOrbisAudioBufferDecoder.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/JOrbisAudioBufferDecoder.java new file mode 100644 index 0000000..83f8361 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/JOrbisAudioBufferDecoder.java @@ -0,0 +1,384 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +import org.teavm.jso.webaudio.AudioBuffer; + +import com.jcraft.jogg.Packet; +import com.jcraft.jogg.Page; +import com.jcraft.jogg.StreamState; +import com.jcraft.jogg.SyncState; +import com.jcraft.jorbis.Block; +import com.jcraft.jorbis.Comment; +import com.jcraft.jorbis.DspState; +import com.jcraft.jorbis.Info; + +import net.lax1dude.eaglercraft.v1_8.EaglerInputStream; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformAudio; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.FloatBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCBufferAllocator; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; + +/** + * 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. + * + */ +public class JOrbisAudioBufferDecoder { + + private EaglerInputStream inputStream; + private boolean endOfStream = false; + private byte[] buffer = null; + private int bufferSize; + private int count = 0; + private int index = 0; + private float[][] convertedBuffer = null; + private float[][][] pcmInfo; + private int[] pcmIndex; + private Packet joggPacket = new Packet(); + private Page joggPage = new Page(); + private StreamState joggStreamState = new StreamState(); + private SyncState joggSyncState = new SyncState(); + private DspState jorbisDspState = new DspState(); + private Block jorbisBlock; + private Comment jorbisComment; + private Info jorbisInfo; + private String errorString; + + private static final Logger logger = LogManager.getLogger("JOrbisAudioBufferDecoder"); + + private static final JOrbisAudioBufferDecoder instance = new JOrbisAudioBufferDecoder(); + + public static AudioBuffer decodeAudioJOrbis(byte[] data, String errorString) { + JOrbisAudioBufferDecoder dec = instance; + synchronized(dec) { + if(!dec.init(data, errorString)) { + logger.error("[{}]: Invalid header detected", errorString); + return null; + } + int ch = -1; + int len = 0; + List lst = new LinkedList<>(); + float[][] b; + while((b = dec.readBytes()) != null) { + if(ch == -1) { + ch = b.length; + } + len += b[0].length; + lst.add(b); + } + if(dec.jorbisInfo.channels != ch) { + logger.warn("[{}]: Number of channels in header does not match the stream", errorString); + } + if(ch == -1 || len == 0) { + logger.error("[{}]: Empty file", errorString); + return null; + } + FloatBuffer buf = PlatformRuntime.allocateFloatBuffer(ch * len); + try { + int len2 = 0; + for(float[][] fl : lst) { + for(int i = 0; i < ch; ++i) { + buf.position(i * len + len2); + buf.put(fl[i]); + } + len2 += fl[0].length; + } + buf.clear(); + return PlatformAudio.decodeAudioBufferPCMBrowser(WASMGCBufferAllocator.getFloatBufferView(buf), ch, + len, dec.jorbisInfo.rate); + }finally { + PlatformRuntime.freeFloatBuffer(buf); + } + } + } + + private JOrbisAudioBufferDecoder() { + this.jorbisBlock = new Block(this.jorbisDspState); + this.jorbisComment = new Comment(); + this.jorbisInfo = new Info(); + } + + private boolean init(byte[] data, String errorString) { + this.inputStream = new EaglerInputStream(data); + this.errorString = errorString; + + if (this.joggStreamState != null) { + this.joggStreamState.clear(); + } + + if (this.jorbisBlock != null) { + this.jorbisBlock.clear(); + } + + if (this.jorbisDspState != null) { + this.jorbisDspState.clear(); + } + + if (this.jorbisInfo != null) { + this.jorbisInfo.clear(); + } + + if (this.joggSyncState != null) { + this.joggSyncState.clear(); + } + + if (this.inputStream != null) { + try { + this.inputStream.close(); + } catch (IOException var7) { + } + } + + this.bufferSize = 8192; + this.buffer = null; + this.count = 0; + this.index = 0; + this.joggStreamState = new StreamState(); + this.jorbisBlock = new Block(this.jorbisDspState); + this.jorbisDspState = new DspState(); + this.jorbisInfo = new Info(); + this.joggSyncState = new SyncState(); + + this.endOfStream = false; + this.joggSyncState.init(); + this.joggSyncState.buffer(this.bufferSize); + this.buffer = this.joggSyncState.data; + + vigg: { + this.index = this.joggSyncState.buffer(this.bufferSize); + int bytes = this.inputStream.read(this.joggSyncState.data, this.index, this.bufferSize); + if (bytes < 0) { + bytes = 0; + } + + this.joggSyncState.wrote(bytes); + if (this.joggSyncState.pageout(this.joggPage) != 1) { + if (bytes < this.bufferSize) { + break vigg; + } else { + logger.error("[{}]: Ogg header not recognized in method 'readHeader'.", errorString); + return false; + } + } else { + this.joggStreamState.init(this.joggPage.serialno()); + this.jorbisInfo.init(); + this.jorbisComment.init(); + if (this.joggStreamState.pagein(this.joggPage) < 0) { + logger.error("[{}]: Problem with first Ogg header page in method 'readHeader'.", errorString); + return false; + } else if (this.joggStreamState.packetout(this.joggPacket) != 1) { + logger.error("[{}]: Problem with first Ogg header packet in method 'readHeader'.", errorString); + return false; + } else if (this.jorbisInfo.synthesis_headerin(this.jorbisComment, this.joggPacket) < 0) { + logger.error("[{}]: File does not contain Vorbis header in method 'readHeader'.", errorString); + return false; + } else { + int i = 0; + + while (i < 2) { + label73: while (true) { + int result; + do { + if (i >= 2) { + break label73; + } + + result = this.joggSyncState.pageout(this.joggPage); + if (result == 0) { + break label73; + } + } while (result != 1); + + this.joggStreamState.pagein(this.joggPage); + + while (i < 2) { + result = this.joggStreamState.packetout(this.joggPacket); + if (result == 0) { + break; + } + + if (result == -1) { + logger.error("[{}]: Secondary Ogg header corrupt in method 'readHeader'.", errorString); + return false; + } + + this.jorbisInfo.synthesis_headerin(this.jorbisComment, this.joggPacket); + ++i; + } + } + + this.index = this.joggSyncState.buffer(this.bufferSize); + bytes = this.inputStream.read(this.joggSyncState.data, this.index, this.bufferSize); + if (bytes < 0) { + bytes = 0; + } + + if (bytes == 0 && i < 2) { + logger.error( + "[{}]: End of file reached before finished reading Ogg header in method 'readHeader'", + errorString); + return false; + } + + this.joggSyncState.wrote(bytes); + } + + this.index = this.joggSyncState.buffer(this.bufferSize); + this.buffer = this.joggSyncState.data; + } + } + } + + this.jorbisDspState.synthesis_init(this.jorbisInfo); + this.jorbisBlock.init(this.jorbisDspState); + int channels = this.jorbisInfo.channels; + int rate = this.jorbisInfo.rate; + this.pcmInfo = new float[1][][]; + this.pcmIndex = new int[channels]; + if(convertedBuffer == null || convertedBuffer.length != this.jorbisInfo.channels || (convertedBuffer.length > 0 && convertedBuffer[0].length != this.bufferSize)) { + this.convertedBuffer = new float[this.jorbisInfo.channels][this.bufferSize]; + } + + return true; + } + + private float[][] readBytes() { + if (this.endOfStream) { + return null; + } else { + float[][] returnBuffer = null; + switch (this.joggSyncState.pageout(this.joggPage)) { + default: + this.joggStreamState.pagein(this.joggPage); + if (this.joggPage.granulepos() == 0L) { + this.endOfStream = true; + return null; + } else { + label99: { + while (true) { + switch (this.joggStreamState.packetout(this.joggPacket)) { + case -1: + break; + case 0: + if (this.joggPage.eos() != 0) { + this.endOfStream = true; + } + break label99; + default: + if (this.jorbisBlock.synthesis(this.joggPacket) == 0) { + this.jorbisDspState.synthesis_blockin(this.jorbisBlock); + } + + int samples; + while ((samples = this.jorbisDspState.synthesis_pcmout(this.pcmInfo, + this.pcmIndex)) > 0) { + float[][] pcmf = this.pcmInfo[0]; + int bout = samples < bufferSize ? samples : this.bufferSize; + + for (int i = 0; i < this.jorbisInfo.channels; ++i) { + float[] f1 = convertedBuffer[i]; + float[] f2 = pcmf[i]; + int mono = this.pcmIndex[i]; + for (int j = 0; j < bout; ++j) { + f1[j] = f2[mono + j]; + } + } + + this.jorbisDspState.synthesis_read(bout); + returnBuffer = appendFloatArrays(returnBuffer, this.convertedBuffer, bout); + } + } + } + } + } + case -1: + case 0: + if (!this.endOfStream) { + this.index = this.joggSyncState.buffer(this.bufferSize); + this.buffer = this.joggSyncState.data; + + try { + this.count = this.inputStream.read(this.buffer, this.index, this.bufferSize); + } catch (Exception var11) { + return null; + } + + if (this.count == -1) { + return returnBuffer; + } + + this.joggSyncState.wrote(this.count); + if (this.count == 0) { + this.endOfStream = true; + } + } + + return returnBuffer; + } + } + } + + private static float[][] appendFloatArrays(float[][] arrayOne, float[][] arrayTwo, int arrayTwoBytes) { + int bytes = arrayTwoBytes; + int l; + if (arrayTwo != null && (l = arrayTwo[0].length) != 0) { + if (l < arrayTwoBytes) { + bytes = l; + } + } else { + bytes = 0; + } + + if ((arrayOne != null || arrayTwo != null) && bytes > 0) { + float[][] newArray; + + if (arrayOne == null) { + int ch = arrayTwo.length; + int len1 = arrayTwo[0].length; + newArray = new float[ch][bytes]; + for(int i = 0; i < ch; ++i) { + System.arraycopy(arrayTwo[i], 0, newArray[i], 0, bytes); + } + arrayTwo = null; + } else { + int ch = arrayOne.length; + int len1 = arrayOne[0].length; + if (arrayTwo != null && bytes > 0) { + newArray = new float[ch][len1 + bytes]; + for(int i = 0; i < ch; ++i) { + System.arraycopy(arrayOne[i], 0, newArray[i], 0, len1); + System.arraycopy(arrayTwo[i], 0, newArray[i], len1, bytes); + } + arrayOne = null; + arrayTwo = null; + } else { + newArray = new float[ch][len1]; + for(int i = 0; i < ch; ++i) { + System.arraycopy(arrayOne[i], 0, newArray[i], 0, len1); + } + arrayOne = null; + } + } + + return newArray; + } else { + return null; + } + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/LegacyKeycodeTranslator.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/LegacyKeycodeTranslator.java new file mode 100644 index 0000000..493c6bd --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/LegacyKeycodeTranslator.java @@ -0,0 +1,328 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.Sets; + +/** + * 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. + * + */ +public class LegacyKeycodeTranslator { + + public static class LegacyKeycode { + + public final int keyCode; + public final int location; + + private LegacyKeycode(int keyCode, int location) { + this.keyCode = keyCode; + this.location = location; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof LegacyKeycode)) + return false; + LegacyKeycode other = (LegacyKeycode) obj; + if (keyCode != other.keyCode) + return false; + if (location != other.location) + return false; + return true; + } + + } + + private static final Set numpadVolatile = Sets.newHashSet( + "Comma", "Minus", "Period", "Slash", "Equal", "Enter", "Digit0", "Digit1", "Digit2", "Digit3", + "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9", "IntlYen"); + + private final Map codeLookupBase = new HashMap<>(); + private final Map codeLookupLayout = new HashMap<>(); + + public LegacyKeycodeTranslator() { + codeLookupBase.put("Digit0", new LegacyKeycode(0x30, 0)); + codeLookupBase.put("Digit1", new LegacyKeycode(0x31, 0)); + codeLookupBase.put("Digit2", new LegacyKeycode(0x32, 0)); + codeLookupBase.put("Digit3", new LegacyKeycode(0x33, 0)); + codeLookupBase.put("Digit4", new LegacyKeycode(0x34, 0)); + codeLookupBase.put("Digit5", new LegacyKeycode(0x35, 0)); + codeLookupBase.put("Digit6", new LegacyKeycode(0x36, 0)); + codeLookupBase.put("Digit7", new LegacyKeycode(0x37, 0)); + codeLookupBase.put("Digit8", new LegacyKeycode(0x38, 0)); + codeLookupBase.put("Digit9", new LegacyKeycode(0x39, 0)); + codeLookupBase.put("KeyA", new LegacyKeycode(0x41, 0)); + codeLookupBase.put("KeyB", new LegacyKeycode(0x42, 0)); + codeLookupBase.put("KeyC", new LegacyKeycode(0x43, 0)); + codeLookupBase.put("KeyD", new LegacyKeycode(0x44, 0)); + codeLookupBase.put("KeyE", new LegacyKeycode(0x45, 0)); + codeLookupBase.put("KeyF", new LegacyKeycode(0x46, 0)); + codeLookupBase.put("KeyG", new LegacyKeycode(0x47, 0)); + codeLookupBase.put("KeyH", new LegacyKeycode(0x48, 0)); + codeLookupBase.put("KeyI", new LegacyKeycode(0x49, 0)); + codeLookupBase.put("KeyJ", new LegacyKeycode(0x4A, 0)); + codeLookupBase.put("KeyK", new LegacyKeycode(0x4B, 0)); + codeLookupBase.put("KeyL", new LegacyKeycode(0x4C, 0)); + codeLookupBase.put("KeyM", new LegacyKeycode(0x4D, 0)); + codeLookupBase.put("KeyN", new LegacyKeycode(0x4E, 0)); + codeLookupBase.put("KeyO", new LegacyKeycode(0x4F, 0)); + codeLookupBase.put("KeyP", new LegacyKeycode(0x50, 0)); + codeLookupBase.put("KeyQ", new LegacyKeycode(0x51, 0)); + codeLookupBase.put("KeyR", new LegacyKeycode(0x52, 0)); + codeLookupBase.put("KeyS", new LegacyKeycode(0x53, 0)); + codeLookupBase.put("KeyT", new LegacyKeycode(0x54, 0)); + codeLookupBase.put("KeyU", new LegacyKeycode(0x55, 0)); + codeLookupBase.put("KeyV", new LegacyKeycode(0x56, 0)); + codeLookupBase.put("KeyW", new LegacyKeycode(0x57, 0)); + codeLookupBase.put("KeyX", new LegacyKeycode(0x58, 0)); + codeLookupBase.put("KeyY", new LegacyKeycode(0x59, 0)); + codeLookupBase.put("KeyZ", new LegacyKeycode(0x5A, 0)); + codeLookupBase.put("Comma", new LegacyKeycode(0xBC, 0)); + codeLookupBase.put("Period", new LegacyKeycode(0xBE, 0)); + codeLookupBase.put("Semicolon", new LegacyKeycode(0xBA, 0)); + codeLookupBase.put("Quote", new LegacyKeycode(0xDE, 0)); + codeLookupBase.put("BracketLeft", new LegacyKeycode(0xDB, 0)); + codeLookupBase.put("BracketRight", new LegacyKeycode(0xDD, 0)); + codeLookupBase.put("Backquote", new LegacyKeycode(0xC0, 0)); + codeLookupBase.put("Backslash", new LegacyKeycode(0xDC, 0)); + codeLookupBase.put("IntlBackslash", new LegacyKeycode(0xDC, 0)); + codeLookupBase.put("Minus", new LegacyKeycode(0xBD, 0)); + codeLookupBase.put("Equal", new LegacyKeycode(0xBB, 0)); + codeLookupBase.put("Slash", new LegacyKeycode(0xBF, 0)); + codeLookupBase.put("IntlRo", new LegacyKeycode(0xC1, 0)); + codeLookupBase.put("IntlYen", new LegacyKeycode(0xFF, 0)); + codeLookupBase.put("AltLeft", new LegacyKeycode(0x12, 1)); + codeLookupBase.put("AltRight", new LegacyKeycode(0x12, 2)); + codeLookupBase.put("CapsLock", new LegacyKeycode(0x14, 0)); + codeLookupBase.put("ControlLeft", new LegacyKeycode(0x11, 1)); + codeLookupBase.put("ControlRight", new LegacyKeycode(0x11, 2)); + codeLookupBase.put("MetaLeft", new LegacyKeycode(0x5B, 1)); + codeLookupBase.put("MetaRight", new LegacyKeycode(0x5C, 2)); + codeLookupBase.put("ShiftLeft", new LegacyKeycode(0x10, 1)); + codeLookupBase.put("ShiftRight", new LegacyKeycode(0x10, 2)); + codeLookupBase.put("ContextMenu", new LegacyKeycode(0x5D, 0)); + codeLookupBase.put("Enter", new LegacyKeycode(0x0D, 0)); + codeLookupBase.put("Space", new LegacyKeycode(0x20, 0)); + codeLookupBase.put("Backspace", new LegacyKeycode(0x08, 0)); + codeLookupBase.put("Tab", new LegacyKeycode(0x09, 0)); + codeLookupBase.put("Delete", new LegacyKeycode(0x2E, 0)); + codeLookupBase.put("End", new LegacyKeycode(0x23, 0)); + codeLookupBase.put("Help", new LegacyKeycode(0x2D, 0)); + codeLookupBase.put("Home", new LegacyKeycode(0x24, 0)); + codeLookupBase.put("Insert", new LegacyKeycode(0x2D, 0)); + codeLookupBase.put("PageDown", new LegacyKeycode(0x22, 0)); + codeLookupBase.put("PageUp", new LegacyKeycode(0x21, 0)); + codeLookupBase.put("ArrowDown", new LegacyKeycode(0x28, 0)); + codeLookupBase.put("ArrowLeft", new LegacyKeycode(0x25, 0)); + codeLookupBase.put("ArrowRight", new LegacyKeycode(0x27, 0)); + codeLookupBase.put("ArrowUp", new LegacyKeycode(0x26, 0)); + codeLookupBase.put("Escape", new LegacyKeycode(0x1B, 0)); + codeLookupBase.put("PrintScreen", new LegacyKeycode(0x2C, 0)); + codeLookupBase.put("ScrollLock", new LegacyKeycode(0x91, 0)); + codeLookupBase.put("Pause", new LegacyKeycode(0x13, 0)); + codeLookupBase.put("F1", new LegacyKeycode(0x70, 0)); + codeLookupBase.put("F2", new LegacyKeycode(0x71, 0)); + codeLookupBase.put("F3", new LegacyKeycode(0x72, 0)); + codeLookupBase.put("F4", new LegacyKeycode(0x73, 0)); + codeLookupBase.put("F5", new LegacyKeycode(0x74, 0)); + codeLookupBase.put("F6", new LegacyKeycode(0x75, 0)); + codeLookupBase.put("F7", new LegacyKeycode(0x76, 0)); + codeLookupBase.put("F8", new LegacyKeycode(0x77, 0)); + codeLookupBase.put("F9", new LegacyKeycode(0x78, 0)); + codeLookupBase.put("F10", new LegacyKeycode(0x79, 0)); + codeLookupBase.put("F11", new LegacyKeycode(0x7A, 0)); + codeLookupBase.put("F12", new LegacyKeycode(0x7B, 0)); + codeLookupBase.put("F13", new LegacyKeycode(0x7C, 0)); + codeLookupBase.put("F14", new LegacyKeycode(0x7D, 0)); + codeLookupBase.put("F15", new LegacyKeycode(0x7E, 0)); + codeLookupBase.put("F16", new LegacyKeycode(0x7F, 0)); + codeLookupBase.put("F17", new LegacyKeycode(0x80, 0)); + codeLookupBase.put("F18", new LegacyKeycode(0x81, 0)); + codeLookupBase.put("F19", new LegacyKeycode(0x82, 0)); + codeLookupBase.put("F20", new LegacyKeycode(0x83, 0)); + codeLookupBase.put("F21", new LegacyKeycode(0x84, 0)); + codeLookupBase.put("F22", new LegacyKeycode(0x85, 0)); + codeLookupBase.put("F23", new LegacyKeycode(0x86, 0)); + codeLookupBase.put("F24", new LegacyKeycode(0x87, 0)); + codeLookupBase.put("NumLock", new LegacyKeycode(0x90, 3)); + codeLookupBase.put("Numpad0", new LegacyKeycode(0x60, 3)); + codeLookupBase.put("Numpad1", new LegacyKeycode(0x61, 3)); + codeLookupBase.put("Numpad2", new LegacyKeycode(0x62, 3)); + codeLookupBase.put("Numpad3", new LegacyKeycode(0x63, 3)); + codeLookupBase.put("Numpad4", new LegacyKeycode(0x64, 3)); + codeLookupBase.put("Numpad5", new LegacyKeycode(0x65, 3)); + codeLookupBase.put("Numpad6", new LegacyKeycode(0x66, 3)); + codeLookupBase.put("Numpad7", new LegacyKeycode(0x67, 3)); + codeLookupBase.put("Numpad8", new LegacyKeycode(0x68, 3)); + codeLookupBase.put("Numpad9", new LegacyKeycode(0x69, 3)); + codeLookupBase.put("NumpadAdd", new LegacyKeycode(0x6B, 3)); + codeLookupBase.put("NumpadComma", new LegacyKeycode(0xC2, 3)); + codeLookupBase.put("NumpadDecimal", new LegacyKeycode(0x6E, 3)); + codeLookupBase.put("NumpadDivide", new LegacyKeycode(0x6F, 3)); + codeLookupBase.put("NumpadEnter", new LegacyKeycode(0x0D, 3)); + codeLookupBase.put("NumpadEqual", new LegacyKeycode(0x0C, 3)); + codeLookupBase.put("NumpadMultiply", new LegacyKeycode(0x6A, 3)); + codeLookupBase.put("NumpadSubtract", new LegacyKeycode(0x6D, 3)); + } + + public LegacyKeycodeTranslator addBrowserLayoutMapping(String keyChar, String codeStr) { + LegacyKeycode mapTo = codeLookupBase.get(codeStr); + if(mapTo != null) { + String keyCode = getCodeFromLayoutChar(keyChar); + if(keyCode != null && !keyCode.equals(codeStr) && !(codeStr.startsWith("Numpad") && numpadVolatile.contains(keyCode)) && !mapTo.equals(codeLookupBase.get(keyCode))) { + codeLookupLayout.put(keyCode, mapTo); + } + } + return this; + } + + public int getRemappedKeyCount() { + return codeLookupLayout.size(); + } + + public Map buildLayoutTable() { + if(codeLookupLayout.isEmpty()) { + return codeLookupBase; + } + Map ret = new HashMap<>(); + ret.putAll(codeLookupBase); + ret.putAll(codeLookupLayout); + return ret; + } + + public static String getCodeFromLayoutChar(String keyChar) { + if(keyChar.length() != 1) { + return null; + } + char c = keyChar.charAt(0); + String ret = getCodeFromLayoutChar0(c); + if(ret == null) { + ret = getCodeFromLayoutChar0(Character.toLowerCase(c)); + } + return ret; + } + + private static String getCodeFromLayoutChar0(char keyChar) { + switch(keyChar) { + case 'e': + return "KeyE"; + case 'd': + return "KeyD"; + case 'u': + return "KeyU"; + case '-': + return "Minus"; + case 'h': + return "KeyH"; + case 'z': + return "KeyZ"; + case '=': + return "Equal"; + case 'p': + return "KeyP"; + case ';': + return "Semicolon"; + case ']': + return "BracketRight"; + case '/': + return "Slash"; + case '[': + return "BracketLeft"; + case 'l': + return "KeyL"; + case '8': + return "Digit8"; + case 'w': + return "KeyW"; + case 's': + return "KeyS"; + case '5': + return "Digit5"; + case '9': + return "Digit9"; + case 'o': + return "KeyO"; + case '.': + return "Period"; + case '6': + return "Digit6"; + case 'v': + return "KeyV"; + case '3': + return "Digit3"; + case '`': + return "Backquote"; + case 'g': + return "KeyG"; + case 'j': + return "KeyJ"; + case 'q': + return "KeyQ"; + case '1': + return "Digit1"; + case 't': + return "KeyT"; + case 'y': + return "KeyY"; + case '\'': + return "Quote"; + case '\\': + return "Backslash"; + case 'k': + return "KeyK"; + case 'f': + return "KeyF"; + case 'i': + return "KeyI"; + case 'r': + return "KeyR"; + case 'x': + return "KeyX"; + case 'a': + return "KeyA"; + case '2': + return "Digit2"; + case '7': + return "Digit7"; + case 'm': + return "KeyM"; + case '4': + return "Digit4"; + case '0': + return "Digit0"; + case 'n': + return "KeyN"; + case 'b': + return "KeyB"; + case 'c': + return "KeyC"; + case ',': + return "Comma"; + case '*': + return "NumpadMultiply"; + case 0xA5: + return "IntlYen"; + default: + return null; + } + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/MainClass.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/MainClass.java new file mode 100644 index 0000000..94d23f5 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/MainClass.java @@ -0,0 +1,44 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import net.lax1dude.eaglercraft.v1_8.sp.server.internal.wasm_gc_teavm.WorkerMain; + +/** + * Copyright (c) 2022-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. + * + */ +public class MainClass { + + public static void main(String[] args) { + WASMGCCrashReportStrings.setCrashReportStrings(); + if(args.length == 1) { + if("_worker_process_".equalsIgnoreCase(args[0])) { + workerMain(); + return; + } + }else if(args.length == 0) { + clientMain(); + return; + } + System.out.println("???"); + } + + private static void clientMain() { + ClientMain._main(); + } + + private static void workerMain() { + WorkerMain._main(); + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/SortedTouchEvent.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/SortedTouchEvent.java new file mode 100644 index 0000000..c702a0b --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/SortedTouchEvent.java @@ -0,0 +1,146 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; +import org.teavm.jso.core.JSArrayReader; + +import net.lax1dude.eaglercraft.v1_8.internal.EnumTouchEvent; + +/** + * 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. + * + */ +public class SortedTouchEvent { + + public interface ITouchUIDMapper { + int call(int uidIn); + } + + public interface JSTouchPoint extends JSObject { + + @JSProperty + int getPointX(); + + @JSProperty + int getPointY(); + + @JSProperty + float getRadius(); + + @JSProperty + float getForce(); + + @JSProperty + int getPointUID(); + + } + + public static class TouchPoint { + + public final int pointX; + public final int pointY; + public final float radius; + public final float force; + public final int uid; + + public TouchPoint(int pointX, int pointY, float radius, float force, int uid) { + this.pointX = pointX; + this.pointY = pointY; + this.radius = radius; + this.force = force; + this.uid = uid; + } + + } + + public static final Comparator touchSortingComparator = (t1, t2) -> { + return t1.uid - t2.uid; + }; + + private static List convertTouchList(JSArrayReader jsArray, ITouchUIDMapper uidMapper, + int windowHeight, float windowDPI) { + int l = jsArray.getLength(); + List ret = new ArrayList<>(l); + for(int i = 0; i < l; ++i) { + JSTouchPoint p = jsArray.get(i); + ret.add(new TouchPoint((int)(p.getPointX() * windowDPI), windowHeight - (int)(p.getPointY() * windowDPI) - 1, + p.getRadius() * windowDPI, p.getForce(), uidMapper.call(p.getPointUID()))); + } + Collections.sort(ret, touchSortingComparator); + return ret; + } + + public static SortedTouchEvent createTouchEvent(EnumTouchEvent type, JSArrayReader changedTouches, + JSArrayReader targetTouches, ITouchUIDMapper uidMapper, int windowHeight, float windowDPI) { + List changedTouchesList = convertTouchList(changedTouches, uidMapper, windowHeight, windowDPI); + List targetTouchesList = convertTouchList(targetTouches, uidMapper, windowHeight, windowDPI); + List eventTouchesList; + switch(type) { + case TOUCHSTART: + eventTouchesList = changedTouchesList; + break; + case TOUCHMOVE: + eventTouchesList = targetTouchesList; + break; + case TOUCHEND: + default: + eventTouchesList = changedTouchesList; + break; + } + return new SortedTouchEvent(type, changedTouchesList, targetTouchesList, eventTouchesList); + } + + public final EnumTouchEvent type; + private final List changedTouchesList; + private final List targetTouchesList; + private final List eventTouchesList; + + public SortedTouchEvent(EnumTouchEvent type, List changedTouchesList, + List targetTouchesList, List eventTouchesList) { + this.type = type; + this.changedTouchesList = changedTouchesList; + this.targetTouchesList = targetTouchesList; + this.eventTouchesList = eventTouchesList; + } + + public int getChangedTouchesSize() { + return changedTouchesList.size(); + } + + public List getChangedTouches() { + return changedTouchesList; + } + + public int getTargetTouchesSize() { + return targetTouchesList.size(); + } + + public List getTargetTouches() { + return targetTouchesList; + } + + public int getEventTouchesSize() { + return eventTouchesList.size(); + } + + public List getEventTouches() { + return eventTouchesList; + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/TeaVMUtils.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/TeaVMUtils.java new file mode 100644 index 0000000..665c634 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/TeaVMUtils.java @@ -0,0 +1,39 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import org.teavm.jso.JSBody; +import org.teavm.jso.JSObject; +import org.teavm.jso.typedarrays.ArrayBuffer; +import org.teavm.jso.typedarrays.ArrayBufferView; +import org.teavm.jso.typedarrays.Float32Array; +import org.teavm.jso.typedarrays.Int16Array; +import org.teavm.jso.typedarrays.Int32Array; +import org.teavm.jso.typedarrays.Int8Array; +import org.teavm.jso.typedarrays.Uint8Array; + +/** + * 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. + * + */ +public class TeaVMUtils { + + @JSBody(params = { "obj" }, script = "return !!obj;") + public static native boolean isTruthy(JSObject object); + + @JSBody(params = { "obj" }, script = "return !obj;") + public static native boolean isNotTruthy(JSObject object); + + @JSBody(params = { "obj" }, script = "return obj === undefined;") + public static native boolean isUndefined(JSObject object); + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/TextDecoder.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/TextDecoder.java new file mode 100644 index 0000000..a88f315 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/TextDecoder.java @@ -0,0 +1,31 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import org.teavm.jso.JSClass; +import org.teavm.jso.JSObject; +import org.teavm.jso.core.JSString; +import org.teavm.jso.typedarrays.Uint8Array; + +/** + * 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. + * + */ +@JSClass +public class TextDecoder implements JSObject { + + public TextDecoder(String encoding) { + } + + public native JSString decode(Uint8Array buffer); + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WASMGCClientConfigAdapter.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WASMGCClientConfigAdapter.java new file mode 100644 index 0000000..fb42a11 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WASMGCClientConfigAdapter.java @@ -0,0 +1,480 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import java.util.ArrayList; +import java.util.List; + +import net.lax1dude.eaglercraft.v1_8.EagRuntime; +import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion; +import net.lax1dude.eaglercraft.v1_8.ThreadLocalRandom; +import net.lax1dude.eaglercraft.v1_8.sp.relay.RelayManager; +import org.json.JSONArray; +import org.json.JSONObject; +import org.teavm.jso.JSObject; +import org.teavm.jso.core.JSArrayReader; + +import net.lax1dude.eaglercraft.v1_8.internal.IClientConfigAdapter; +import net.lax1dude.eaglercraft.v1_8.internal.IClientConfigAdapterHooks; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.opts.JSEaglercraftXOptsHooks; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.opts.JSEaglercraftXOptsRelay; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.opts.JSEaglercraftXOptsRoot; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.opts.JSEaglercraftXOptsServer; +import net.lax1dude.eaglercraft.v1_8.sp.relay.RelayEntry; + +/** + * Copyright (c) 2022-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. + * + */ +public class WASMGCClientConfigAdapter implements IClientConfigAdapter { + + public static final IClientConfigAdapter instance = new WASMGCClientConfigAdapter(); + + private String defaultLocale = "en_US"; + private List defaultServers = new ArrayList<>(); + private List relays = new ArrayList<>(); + private String serverToJoin = null; + private String worldsDB = "worlds"; + private String resourcePacksDB = "resourcePacks"; + private boolean checkGLErrors = false; + private boolean checkShaderGLErrors = false; + private boolean demoMode = EaglercraftVersion.forceDemoMode; + private boolean isEnableDownloadOfflineButton = true; + private String downloadOfflineButtonLink = null; + private boolean useSpecialCursors = false; + private boolean logInvalidCerts = false; + private boolean checkRelaysForUpdates = false; + private boolean enableSignatureBadge = false; + private boolean allowVoiceClient = true; + private boolean allowFNAWSkins = true; + private String localStorageNamespace = "_eaglercraftX"; + private final WASMGCClientConfigAdapterHooks hooks = new WASMGCClientConfigAdapterHooks(); + private boolean enableMinceraft = true; + private boolean enableServerCookies = true; + private boolean allowServerRedirects = true; + private boolean crashOnUncaughtExceptions = false; + private boolean openDebugConsoleOnLaunch = false; + private boolean fixDebugConsoleUnloadListener = false; + private boolean forceWebViewSupport = false; + private boolean enableWebViewCSP = false; + private boolean autoFixLegacyStyleAttr = false; + private boolean forceProfanityFilter = false; + private boolean forceWebGL1 = false; + private boolean forceWebGL2 = false; + private boolean allowExperimentalWebGL1 = false; + private boolean useWebGLExt = true; + private boolean useJOrbisAudioDecoder = false; + private boolean useXHRFetch = false; + private boolean useVisualViewport = true; + private boolean deobfStackTraces = true; + private boolean disableBlobURLs = false; + private boolean eaglerNoDelay = false; + private boolean ramdiskMode = false; + private boolean singleThreadMode = false; + private boolean enforceVSync = true; + + public void loadNative(JSObject jsObject) { + JSEaglercraftXOptsRoot eaglercraftXOpts = (JSEaglercraftXOptsRoot)jsObject; + + defaultLocale = eaglercraftXOpts.getLang("en_US"); + serverToJoin = eaglercraftXOpts.getJoinServer(null); + worldsDB = eaglercraftXOpts.getWorldsDB("worlds"); + resourcePacksDB = eaglercraftXOpts.getResourcePacksDB("resourcePacks"); + checkGLErrors = eaglercraftXOpts.getCheckGLErrors(false); + checkShaderGLErrors = eaglercraftXOpts.getCheckShaderGLErrors(false); + demoMode = EaglercraftVersion.forceDemoMode || eaglercraftXOpts.getDemoMode(false); + isEnableDownloadOfflineButton = eaglercraftXOpts.getEnableDownloadOfflineButton(true); + downloadOfflineButtonLink = eaglercraftXOpts.getDownloadOfflineButtonLink(null); + useSpecialCursors = eaglercraftXOpts.getHtml5CursorSupport(false); + logInvalidCerts = EaglercraftVersion.enableUpdateService && !demoMode && eaglercraftXOpts.getLogInvalidCerts(false); + enableSignatureBadge = eaglercraftXOpts.getEnableSignatureBadge(false); + allowVoiceClient = eaglercraftXOpts.getAllowVoiceClient(true); + allowFNAWSkins = !demoMode && eaglercraftXOpts.getAllowFNAWSkins(true); + localStorageNamespace = eaglercraftXOpts.getLocalStorageNamespace(EaglercraftVersion.localStorageNamespace); + enableMinceraft = eaglercraftXOpts.getEnableMinceraft(true); + enableServerCookies = !demoMode && eaglercraftXOpts.getEnableServerCookies(true); + allowServerRedirects = eaglercraftXOpts.getAllowServerRedirects(true); + crashOnUncaughtExceptions = eaglercraftXOpts.getCrashOnUncaughtExceptions(false); + openDebugConsoleOnLaunch = eaglercraftXOpts.getOpenDebugConsoleOnLaunch(false); + fixDebugConsoleUnloadListener = eaglercraftXOpts.getFixDebugConsoleUnloadListener(false); + forceWebViewSupport = eaglercraftXOpts.getForceWebViewSupport(false); + enableWebViewCSP = eaglercraftXOpts.getEnableWebViewCSP(true); + autoFixLegacyStyleAttr = eaglercraftXOpts.getAutoFixLegacyStyleAttr(true); + forceProfanityFilter = eaglercraftXOpts.getForceProfanityFilter(false); + forceWebGL1 = eaglercraftXOpts.getForceWebGL1(false); + forceWebGL2 = eaglercraftXOpts.getForceWebGL2(false); + allowExperimentalWebGL1 = eaglercraftXOpts.getAllowExperimentalWebGL1(true); + useWebGLExt = eaglercraftXOpts.getUseWebGLExt(true); + useJOrbisAudioDecoder = eaglercraftXOpts.getUseJOrbisAudioDecoder(false); + useXHRFetch = eaglercraftXOpts.getUseXHRFetch(false); + useVisualViewport = eaglercraftXOpts.getUseVisualViewport(true); + deobfStackTraces = eaglercraftXOpts.getDeobfStackTraces(true); + disableBlobURLs = eaglercraftXOpts.getDisableBlobURLs(false); + eaglerNoDelay = eaglercraftXOpts.getEaglerNoDelay(false); + ramdiskMode = eaglercraftXOpts.getRamdiskMode(false); + singleThreadMode = eaglercraftXOpts.getSingleThreadMode(false); + enforceVSync = eaglercraftXOpts.getEnforceVSync(true); + JSEaglercraftXOptsHooks hooksObj = eaglercraftXOpts.getHooks(); + if(hooksObj != null) { + hooks.loadHooks(hooksObj); + } + + defaultServers.clear(); + JSArrayReader serversArray = eaglercraftXOpts.getServers(); + if(serversArray != null) { + for(int i = 0, l = serversArray.getLength(); i < l; ++i) { + JSEaglercraftXOptsServer serverEntry = serversArray.get(i); + boolean hideAddr = serverEntry.getHideAddr(false); + String serverAddr = serverEntry.getAddr(); + if(serverAddr != null) { + String serverName = serverEntry.getName("Default Server #" + i); + defaultServers.add(new DefaultServer(serverName, serverAddr, hideAddr)); + } + } + } + + relays.clear(); + JSArrayReader relaysArray = eaglercraftXOpts.getRelays(); + if(relaysArray != null) { + boolean gotAPrimary = false; + for(int i = 0, l = relaysArray.getLength(); i < l; ++i) { + JSEaglercraftXOptsRelay relay = relaysArray.get(i); + String addr = relay.getAddr(); + if(addr != null) { + boolean p = relay.getPrimary(); + if(p) { + if(gotAPrimary) { + p = false; + }else { + gotAPrimary = true; + } + } + relays.add(new RelayEntry(relay.getAddr(), relay.getComment("Default Relay #" + i), p)); + } + } + } + + boolean officialUpdates = !demoMode && EaglercraftVersion.updateBundlePackageName.equals("net.lax1dude.eaglercraft.v1_8.client"); + if (relays.size() <= 0) { + int choice = ThreadLocalRandom.current().nextInt(3); + relays.add(new RelayEntry("wss://relay.deev.is/", "lax1dude relay #1", choice == 0)); + relays.add(new RelayEntry("wss://relay.lax1dude.net/", "lax1dude relay #2", choice == 1)); + relays.add(new RelayEntry("wss://relay.shhnowisnottheti.me/", "ayunami relay #1", choice == 2)); + checkRelaysForUpdates = !demoMode && eaglercraftXOpts.getCheckRelaysForUpdates(officialUpdates); + }else { + if(officialUpdates) { + for(int i = 0, l = relays.size(); i < l; ++i) { + String addr = relays.get(i).address; + if(!addr.contains("deev.is") && !addr.contains("lax1dude.net") && !addr.contains("shhnowisnottheti.me")) { + officialUpdates = false; + break; + } + } + } + checkRelaysForUpdates = !demoMode && eaglercraftXOpts.getCheckRelaysForUpdates(officialUpdates); + } + + RelayManager.relayManager.load(EagRuntime.getStorage("r")); + + if (RelayManager.relayManager.count() <= 0) { + RelayManager.relayManager.loadDefaults(); + RelayManager.relayManager.save(); + } + } + + @Override + public String getDefaultLocale() { + return defaultLocale; + } + + @Override + public List getDefaultServerList() { + return defaultServers; + } + + @Override + public String getServerToJoin() { + return serverToJoin; + } + + @Override + public String getWorldsDB() { + return worldsDB; + } + + @Override + public String getResourcePacksDB() { + return resourcePacksDB; + } + + @Override + public JSONObject getIntegratedServerOpts() { + return null; + } + + @Override + public List getRelays() { + return relays; + } + + @Override + public boolean isCheckGLErrors() { + return checkGLErrors; + } + + @Override + public boolean isCheckShaderGLErrors() { + return checkShaderGLErrors; + } + + @Override + public boolean isDemo() { + return demoMode; + } + + @Override + public boolean allowUpdateSvc() { + return false; + } + + @Override + public boolean allowUpdateDL() { + return false; + } + + @Override + public boolean isEnableDownloadOfflineButton() { + return isEnableDownloadOfflineButton; + } + + @Override + public String getDownloadOfflineButtonLink() { + return downloadOfflineButtonLink; + } + + @Override + public boolean useSpecialCursors() { + return useSpecialCursors; + } + + @Override + public boolean isLogInvalidCerts() { + return logInvalidCerts; + } + + @Override + public boolean isCheckRelaysForUpdates() { + return false; + } + + @Override + public boolean isEnableSignatureBadge() { + return enableSignatureBadge; + } + + @Override + public boolean isAllowVoiceClient() { + return allowVoiceClient; + } + + @Override + public boolean isAllowFNAWSkins() { + return allowFNAWSkins; + } + + @Override + public String getLocalStorageNamespace() { + return localStorageNamespace; + } + + @Override + public boolean isEnableMinceraft() { + return enableMinceraft; + } + + @Override + public boolean isEnableServerCookies() { + return enableServerCookies; + } + + @Override + public boolean isAllowServerRedirects() { + return allowServerRedirects; + } + + @Override + public boolean isOpenDebugConsoleOnLaunch() { + return openDebugConsoleOnLaunch; + } + + public boolean isFixDebugConsoleUnloadListenerTeaVM() { + return fixDebugConsoleUnloadListener; + } + + @Override + public boolean isForceWebViewSupport() { + return forceWebViewSupport; + } + + @Override + public boolean isEnableWebViewCSP() { + return enableWebViewCSP; + } + + public boolean isAutoFixLegacyStyleAttrTeaVM() { + return autoFixLegacyStyleAttr; + } + + public boolean isForceWebGL1TeaVM() { + return forceWebGL1; + } + + public boolean isForceWebGL2TeaVM() { + return forceWebGL2; + } + + public boolean isAllowExperimentalWebGL1TeaVM() { + return allowExperimentalWebGL1; + } + + public boolean isUseWebGLExtTeaVM() { + return useWebGLExt; + } + + public boolean isUseJOrbisAudioDecoderTeaVM() { + return useJOrbisAudioDecoder; + } + + public boolean isUseXHRFetchTeaVM() { + return useXHRFetch; + } + + public boolean isDeobfStackTracesTeaVM() { + return deobfStackTraces; + } + + public boolean isUseVisualViewportTeaVM() { + return useVisualViewport; + } + + public boolean isDisableBlobURLsTeaVM() { + return disableBlobURLs; + } + + public boolean isSingleThreadModeTeaVM() { + return singleThreadMode; + } + + @Override + public boolean isAllowBootMenu() { + return false; + } + + @Override + public boolean isForceProfanityFilter() { + return forceProfanityFilter; + } + + @Override + public boolean isEaglerNoDelay() { + return eaglerNoDelay; + } + + @Override + public boolean isRamdiskMode() { + return ramdiskMode; + } + + @Override + public boolean isEnforceVSync() { + return enforceVSync; + } + + @Override + public IClientConfigAdapterHooks getHooks() { + return hooks; + } + + public JSONObject toJSONObject() { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("lang", defaultLocale); + jsonObject.put("joinServer", serverToJoin); + jsonObject.put("worldsDB", worldsDB); + jsonObject.put("resourcePacksDB", resourcePacksDB); + jsonObject.put("checkGLErrors", checkGLErrors); + jsonObject.put("checkShaderGLErrors", checkShaderGLErrors); + jsonObject.put("demoMode", demoMode); + jsonObject.put("enableDownloadOfflineButton", isEnableDownloadOfflineButton); + jsonObject.put("downloadOfflineButtonLink", downloadOfflineButtonLink); + jsonObject.put("html5CursorSupport", useSpecialCursors); + jsonObject.put("logInvalidCerts", logInvalidCerts); + jsonObject.put("checkRelaysForUpdates", checkRelaysForUpdates); + jsonObject.put("enableSignatureBadge", enableSignatureBadge); + jsonObject.put("allowVoiceClient", allowVoiceClient); + jsonObject.put("allowFNAWSkins", allowFNAWSkins); + jsonObject.put("localStorageNamespace", localStorageNamespace); + jsonObject.put("enableMinceraft", enableMinceraft); + jsonObject.put("enableServerCookies", enableServerCookies); + jsonObject.put("allowServerRedirects", allowServerRedirects); + jsonObject.put("crashOnUncaughtExceptions", crashOnUncaughtExceptions); + jsonObject.put("openDebugConsoleOnLaunch", openDebugConsoleOnLaunch); + jsonObject.put("fixDebugConsoleUnloadListener", fixDebugConsoleUnloadListener); + jsonObject.put("forceWebViewSupport", forceWebViewSupport); + jsonObject.put("enableWebViewCSP", enableWebViewCSP); + jsonObject.put("autoFixLegacyStyleAttr", autoFixLegacyStyleAttr); + jsonObject.put("forceProfanityFilter", forceProfanityFilter); + jsonObject.put("forceWebGL1", forceWebGL1); + jsonObject.put("forceWebGL2", forceWebGL2); + jsonObject.put("allowExperimentalWebGL1", allowExperimentalWebGL1); + jsonObject.put("useWebGLExt", useWebGLExt); + jsonObject.put("useJOrbisAudioDecoder", useJOrbisAudioDecoder); + jsonObject.put("useXHRFetch", useXHRFetch); + jsonObject.put("useVisualViewport", useVisualViewport); + jsonObject.put("deobfStackTraces", deobfStackTraces); + jsonObject.put("disableBlobURLs", disableBlobURLs); + jsonObject.put("eaglerNoDelay", eaglerNoDelay); + jsonObject.put("ramdiskMode", ramdiskMode); + jsonObject.put("singleThreadMode", singleThreadMode); + jsonObject.put("enforceVSync", enforceVSync); + JSONArray serversArr = new JSONArray(); + for(int i = 0, l = defaultServers.size(); i < l; ++i) { + DefaultServer srv = defaultServers.get(i); + JSONObject obj = new JSONObject(); + obj.put("addr", srv.addr); + obj.put("hideAddr", srv.hideAddress); + obj.put("name", srv.name); + serversArr.put(obj); + } + jsonObject.put("servers", serversArr); + JSONArray relaysArr = new JSONArray(); + for(int i = 0, l = relays.size(); i < l; ++i) { + RelayEntry rl = relays.get(i); + JSONObject obj = new JSONObject(); + obj.put("addr", rl.address); + obj.put("comment", rl.comment); + obj.put("primary", rl.primary); + relaysArr.put(obj); + } + jsonObject.put("relays", relaysArr); + return jsonObject; + } + + @Override + public String toString() { + return toJSONObject().toString(); + } + + public String toStringFormatted() { + return toJSONObject().toString(4); + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WASMGCClientConfigAdapterHooks.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WASMGCClientConfigAdapterHooks.java new file mode 100644 index 0000000..38fc518 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WASMGCClientConfigAdapterHooks.java @@ -0,0 +1,122 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import java.util.function.Consumer; + +import org.teavm.jso.JSFunctor; +import org.teavm.jso.JSObject; + +import net.lax1dude.eaglercraft.v1_8.internal.IClientConfigAdapterHooks; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.opts.JSEaglercraftXOptsHooks; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; + +/** + * 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. + * + */ +public class WASMGCClientConfigAdapterHooks implements IClientConfigAdapterHooks { + + private static final Logger logger = LogManager.getLogger("TeaVMClientConfigAdapterHooks"); + + private LocalStorageSaveHook saveHook = null; + private LocalStorageLoadHook loadHook = null; + private CrashReportHook crashHook = null; + private ScreenChangeHook screenChangedHook = null; + + @JSFunctor + private static interface LocalStorageSaveHook extends JSObject { + void call(String key, String base64); + } + + @Override + public void callLocalStorageSavedHook(String key, String base64) { + if(saveHook != null) { + try { + saveHook.call(key, base64); + }catch(Throwable t) { + logger.error("Caught exception while invoking eaglercraftXOpts \"localStorageSaved\" hook!"); + logger.error(t); + } + } + } + + @JSFunctor + private static interface LocalStorageLoadHook extends JSObject { + String call(String key); + } + + @Override + public String callLocalStorageLoadHook(String key) { + if(loadHook != null) { + try { + return loadHook.call(key); + }catch(Throwable t) { + logger.error("Caught exception while invoking eaglercraftXOpts \"localStorageLoaded\" hook!"); + logger.error(t); + return null; + } + }else { + return null; + } + } + + @JSFunctor + private static interface ScreenChangeHook extends JSObject { + String call(String screenName, int scaledWidth, int scaledHeight, int realWidth, int realHeight, + int scaleFactor); + } + + @Override + public void callScreenChangedHook(String screenName, int scaledWidth, int scaledHeight, int realWidth, + int realHeight, int scaleFactor) { + if(screenChangedHook != null) { + try { + screenChangedHook.call(screenName, scaledWidth, scaledHeight, realWidth, realHeight, scaleFactor); + }catch(Throwable t) { + logger.error("Caught exception while invoking eaglercraftXOpts \"screenChanged\" hook!"); + logger.error(t); + } + } + } + + @JSFunctor + private static interface CrashReportHook extends JSObject { + void call(String crashReport, CustomMessageCB customMessageCB); + } + + @JSFunctor + private static interface CustomMessageCB extends JSObject { + void call(String msg); + } + + @Override + public void callCrashReportHook(String crashReport, Consumer customMessageCB) { + if(crashHook != null) { + try { + crashHook.call(crashReport, (msg) -> customMessageCB.accept(msg)); + }catch(Throwable t) { + logger.error("Caught exception while invoking eaglercraftXOpts \"screenChanged\" hook!"); + logger.error(t); + } + } + } + + public void loadHooks(JSEaglercraftXOptsHooks hooks) { + saveHook = (LocalStorageSaveHook)hooks.getLocalStorageSavedHook(); + loadHook = (LocalStorageLoadHook)hooks.getLocalStorageLoadedHook(); + crashHook = (CrashReportHook)hooks.getCrashReportHook(); + screenChangedHook = (ScreenChangeHook)hooks.getScreenChangedHook(); + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WASMGCCrashReportStrings.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WASMGCCrashReportStrings.java new file mode 100644 index 0000000..a5ad493 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WASMGCCrashReportStrings.java @@ -0,0 +1,38 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import org.teavm.interop.Import; +import org.teavm.jso.core.JSString; + +import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion; + +/** + * 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. + * + */ +public class WASMGCCrashReportStrings { + + private static final int CRASH_REPORT_EAGLER_VERSION = 0; + private static final int CRASH_REPORT_EAGLER_VENDOR = 1; + private static final int CRASH_REPORT_MINECRAFT_VERSION = 2; + + @Import(module = "platformRuntime", name = "setCrashReportString") + private static native void setCrashReportString(int id, JSString str); + + public static void setCrashReportStrings() { + setCrashReportString(CRASH_REPORT_EAGLER_VERSION, JSString.valueOf(EaglercraftVersion.projectForkVersion)); + setCrashReportString(CRASH_REPORT_EAGLER_VENDOR, JSString.valueOf(EaglercraftVersion.projectForkVendor)); + setCrashReportString(CRASH_REPORT_MINECRAFT_VERSION, JSString.valueOf("1.8.8")); + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WASMGCWebSocketClient.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WASMGCWebSocketClient.java new file mode 100644 index 0000000..f8cc069 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WASMGCWebSocketClient.java @@ -0,0 +1,242 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; +import org.teavm.jso.typedarrays.Uint8Array; + +import net.lax1dude.eaglercraft.v1_8.EagUtils; +import net.lax1dude.eaglercraft.v1_8.internal.EnumEaglerConnectionState; +import net.lax1dude.eaglercraft.v1_8.internal.IWebSocketClient; +import net.lax1dude.eaglercraft.v1_8.internal.IWebSocketFrame; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCBufferAllocator; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCDirectArrayConverter; + +/** + * 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. + * + */ +public class WASMGCWebSocketClient implements IWebSocketClient { + + public interface JSWebSocketClientHandle extends JSObject { + + @JSProperty + int getState(); + + void closeSocket(); + + void sendStringFrame(String str); + + void sendBinaryFrame(Uint8Array arr); + + int availableFrames(); + + WASMGCWebSocketFrame.JSWebSocketFrame getNextFrame(); + + WASMGCWebSocketFrame.JSWebSocketFrame[] getAllFrames(); + + void clearFrames(); + + int availableStringFrames(); + + WASMGCWebSocketFrame.JSWebSocketFrame getNextStringFrame(); + + WASMGCWebSocketFrame.JSWebSocketFrame[] getAllStringFrames(); + + void clearStringFrames(); + + int availableBinaryFrames(); + + WASMGCWebSocketFrame.JSWebSocketFrame getNextBinaryFrame(); + + WASMGCWebSocketFrame.JSWebSocketFrame[] getAllBinaryFrames(); + + void clearBinaryFrames(); + + } + + private final JSWebSocketClientHandle handle; + private final String uri; + + public WASMGCWebSocketClient(JSWebSocketClientHandle handle, String uri) { + this.handle = handle; + this.uri = uri; + } + + @Override + public EnumEaglerConnectionState getState() { + int state = handle.getState(); + switch(state) { + case 0: + default: + return EnumEaglerConnectionState.CLOSED; + case 1: + return EnumEaglerConnectionState.CONNECTING; + case 2: + return EnumEaglerConnectionState.CONNECTED; + case 3: + return EnumEaglerConnectionState.FAILED; + } + } + + @Override + public boolean connectBlocking(int timeoutMS) { + long startTime = PlatformRuntime.steadyTimeMillis(); + int state; + for(;;) { + state = handle.getState(); + if(state != 1) { // CONNECTING + break; + } + EagUtils.sleep(50); + if(PlatformRuntime.steadyTimeMillis() - startTime > timeoutMS) { + state = 3; + break; + } + } + return state == 2; + } + + @Override + public boolean isOpen() { + return handle.getState() == 2; + } + + @Override + public boolean isClosed() { + int state = handle.getState(); + return state != 1 && state != 2; + } + + @Override + public void close() { + handle.closeSocket(); + } + + @Override + public int availableFrames() { + return handle.availableFrames(); + } + + @Override + public IWebSocketFrame getNextFrame() { + WASMGCWebSocketFrame.JSWebSocketFrame nextFrame = handle.getNextFrame(); + return nextFrame != null ? new WASMGCWebSocketFrame(nextFrame) : null; + } + + @Override + public List getNextFrames() { + WASMGCWebSocketFrame.JSWebSocketFrame[] arrJS = handle.getAllFrames(); + if(arrJS == null) { + return null; + } + int len = arrJS.length; + IWebSocketFrame[] arr = new IWebSocketFrame[len]; + for(int i = 0; i < len; ++i) { + arr[i] = new WASMGCWebSocketFrame(arrJS[i]); + } + return Arrays.asList(arr); + } + + @Override + public void clearFrames() { + handle.clearFrames(); + } + + @Override + public int availableStringFrames() { + return handle.availableStringFrames(); + } + + @Override + public IWebSocketFrame getNextStringFrame() { + WASMGCWebSocketFrame.JSWebSocketFrame nextFrame = handle.getNextStringFrame(); + return nextFrame != null ? new WASMGCWebSocketFrame(nextFrame) : null; + } + + @Override + public List getNextStringFrames() { + WASMGCWebSocketFrame.JSWebSocketFrame[] arrJS = handle.getAllStringFrames(); + if(arrJS == null) { + return null; + } + int len = arrJS.length; + IWebSocketFrame[] arr = new IWebSocketFrame[len]; + for(int i = 0; i < len; ++i) { + arr[i] = new WASMGCWebSocketFrame(arrJS[i]); + } + return Arrays.asList(arr); + } + + @Override + public void clearStringFrames() { + handle.clearStringFrames(); + } + + @Override + public int availableBinaryFrames() { + return handle.availableBinaryFrames(); + } + + @Override + public IWebSocketFrame getNextBinaryFrame() { + WASMGCWebSocketFrame.JSWebSocketFrame nextFrame = handle.getNextBinaryFrame(); + return nextFrame != null ? new WASMGCWebSocketFrame(nextFrame) : null; + } + + @Override + public List getNextBinaryFrames() { + WASMGCWebSocketFrame.JSWebSocketFrame[] arrJS = handle.getAllBinaryFrames(); + if(arrJS == null) { + return null; + } + int len = arrJS.length; + IWebSocketFrame[] arr = new IWebSocketFrame[len]; + for(int i = 0; i < len; ++i) { + arr[i] = new WASMGCWebSocketFrame(arrJS[i]); + } + return Arrays.asList(arr); + } + + @Override + public void clearBinaryFrames() { + handle.clearStringFrames(); + } + + @Override + public void send(String str) { + handle.sendStringFrame(str); + } + + @Override + public void send(byte[] bytes) { + ByteBuffer buf = WASMGCDirectArrayConverter.byteArrayToBuffer(bytes); + try { + handle.sendBinaryFrame(WASMGCBufferAllocator.getUnsignedByteBufferView(buf)); + }finally { + PlatformRuntime.freeByteBuffer(buf); + } + } + + @Override + public String getCurrentURI() { + return uri; + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WASMGCWebSocketFrame.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WASMGCWebSocketFrame.java new file mode 100644 index 0000000..d5c7e16 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WASMGCWebSocketFrame.java @@ -0,0 +1,116 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import java.io.InputStream; + +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; +import org.teavm.jso.core.JSString; +import org.teavm.jso.typedarrays.ArrayBuffer; +import org.teavm.jso.typedarrays.Uint8Array; + +import net.lax1dude.eaglercraft.v1_8.EaglerInputStream; +import net.lax1dude.eaglercraft.v1_8.internal.IWebSocketFrame; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCDirectArrayConverter; + +/** + * 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. + * + */ +public class WASMGCWebSocketFrame implements IWebSocketFrame { + + public interface JSWebSocketFrame extends JSObject { + + @JSProperty + int getType(); + + @JSProperty("data") + ArrayBuffer getDataBinary(); + + @JSProperty("data") + JSString getDataJSString(); + + @JSProperty + double getTimestamp(); + + } + + private final JSWebSocketFrame handle; + private final boolean isStr; + private int cachedLength = -1; + private String cachedString = null; + private byte[] cachedByteArray = null; + + public WASMGCWebSocketFrame(JSWebSocketFrame handle) { + this.handle = handle; + this.isStr = handle.getType() == 0; + } + + @Override + public boolean isString() { + return isStr; + } + + @Override + public String getString() { + if(cachedString == null) { + cachedString = getString0(); + } + return cachedString; + } + + private String getString0() { + if(!isStr) return null; + return BetterJSStringConverter.stringFromJS(handle.getDataJSString()); + } + + @Override + public byte[] getByteArray() { + if(cachedByteArray == null) { + cachedByteArray = getByteArray0(); + } + return cachedByteArray; + } + + private byte[] getByteArray0() { + if(isStr) return null; + return WASMGCDirectArrayConverter.externU8ArrayToByteArray(new Uint8Array(handle.getDataBinary())); + } + + @Override + public InputStream getInputStream() { + return new EaglerInputStream(getByteArray()); + } + + @Override + public int getLength() { + if(cachedLength == -1) { + cachedLength = getLength0(); + } + return cachedLength; + } + + private int getLength0() { + if(isStr) { + return handle.getDataJSString().getLength(); + }else { + return handle.getDataBinary().getByteLength(); + } + } + + @Override + public long getTimestamp() { + return (long)handle.getTimestamp(); + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WebGLBackBuffer.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WebGLBackBuffer.java new file mode 100644 index 0000000..50cd9ff --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WebGLBackBuffer.java @@ -0,0 +1,297 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import static net.lax1dude.eaglercraft.v1_8.internal.PlatformOpenGL.*; +import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.*; + +import net.lax1dude.eaglercraft.v1_8.internal.IBufferArrayGL; +import net.lax1dude.eaglercraft.v1_8.internal.IBufferGL; +import net.lax1dude.eaglercraft.v1_8.internal.IFramebufferGL; +import net.lax1dude.eaglercraft.v1_8.internal.IProgramGL; +import net.lax1dude.eaglercraft.v1_8.internal.IRenderbufferGL; +import net.lax1dude.eaglercraft.v1_8.internal.IShaderGL; +import net.lax1dude.eaglercraft.v1_8.internal.ITextureGL; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; +import net.lax1dude.eaglercraft.v1_8.opengl.EaglercraftGPU; +import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager; + +/** + * Copyright (c) 2022-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. + * + */ +public class WebGLBackBuffer { + + private static int glesVers = -1; + + private static IFramebufferGL eagFramebuffer; + private static int width; + private static int height; + + // GLES 3.0+ + private static IRenderbufferGL gles3ColorRenderbuffer; + private static IRenderbufferGL gles3DepthRenderbuffer; + + // GLES 2.0 + private static ITextureGL gles2ColorTexture; + private static IRenderbufferGL gles2DepthRenderbuffer; + private static IProgramGL gles2BlitProgram; + private static IBufferArrayGL gles2BlitVAO; + private static IBufferGL gles2BlitVBO; + + private static boolean isVAOCapable = false; + private static boolean isEmulatedVAOPhase = false; + + private static final int _GL_FRAMEBUFFER = 0x8D40; + private static final int _GL_RENDERBUFFER = 0x8D41; + private static final int _GL_COLOR_ATTACHMENT0 = 0x8CE0; + private static final int _GL_DEPTH_ATTACHMENT = 0x8D00; + private static final int _GL_DEPTH_COMPONENT16 = 0x81A5; + private static final int _GL_DEPTH_COMPONENT32F = 0x8CAC; + private static final int _GL_READ_FRAMEBUFFER = 0x8CA8; + private static final int _GL_DRAW_FRAMEBUFFER = 0x8CA9; + + public static void initBackBuffer(int sw, int sh) { + glesVers = checkOpenGLESVersion(); + eagFramebuffer = _wglCreateFramebuffer(); + isVAOCapable = checkVAOCapable(); + isEmulatedVAOPhase = false; + width = sw; + height = sh; + if(glesVers >= 300) { + gles3ColorRenderbuffer = _wglCreateRenderbuffer(); + gles3DepthRenderbuffer = _wglCreateRenderbuffer(); + _wglBindFramebuffer(_GL_FRAMEBUFFER, eagFramebuffer); + _wglBindRenderbuffer(_GL_RENDERBUFFER, gles3ColorRenderbuffer); + _wglRenderbufferStorage(_GL_RENDERBUFFER, GL_RGBA8, sw, sh); + _wglFramebufferRenderbuffer(_GL_FRAMEBUFFER, _GL_COLOR_ATTACHMENT0, _GL_RENDERBUFFER, gles3ColorRenderbuffer); + _wglBindRenderbuffer(_GL_RENDERBUFFER, gles3DepthRenderbuffer); + _wglRenderbufferStorage(_GL_RENDERBUFFER, _GL_DEPTH_COMPONENT32F, sw, sh); + _wglFramebufferRenderbuffer(_GL_FRAMEBUFFER, _GL_DEPTH_ATTACHMENT, _GL_RENDERBUFFER, gles3DepthRenderbuffer); + _wglDrawBuffers(_GL_COLOR_ATTACHMENT0); + }else { + gles2ColorTexture = _wglGenTextures(); + gles2DepthRenderbuffer = _wglCreateRenderbuffer(); + _wglBindFramebuffer(_GL_FRAMEBUFFER, eagFramebuffer); + _wglBindTexture(GL_TEXTURE_2D, gles2ColorTexture); + _wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + _wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + _wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + _wglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + _wglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, sw, sh, 0, GL_RGBA, GL_UNSIGNED_BYTE, (ByteBuffer)null); + _wglFramebufferTexture2D(_GL_FRAMEBUFFER, _GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gles2ColorTexture, 0); + _wglBindRenderbuffer(_GL_RENDERBUFFER, gles2DepthRenderbuffer); + _wglRenderbufferStorage(_GL_RENDERBUFFER, _GL_DEPTH_COMPONENT16, sw, sh); + _wglFramebufferRenderbuffer(_GL_FRAMEBUFFER, _GL_DEPTH_ATTACHMENT, _GL_RENDERBUFFER, gles2DepthRenderbuffer); + + ByteBuffer upload = PlatformRuntime.allocateByteBuffer(48); + upload.putFloat(0.0f); upload.putFloat(0.0f); + upload.putFloat(1.0f); upload.putFloat(0.0f); + upload.putFloat(0.0f); upload.putFloat(1.0f); + upload.putFloat(1.0f); upload.putFloat(0.0f); + upload.putFloat(1.0f); upload.putFloat(1.0f); + upload.putFloat(0.0f); upload.putFloat(1.0f); + upload.flip(); + + gles2BlitVBO = _wglGenBuffers(); + EaglercraftGPU.bindVAOGLArrayBufferNow(gles2BlitVBO); + _wglBufferData(GL_ARRAY_BUFFER, upload, GL_STATIC_DRAW); + + PlatformRuntime.freeByteBuffer(upload); + + if(isVAOCapable) { + gles2BlitVAO = _wglGenVertexArrays(); + _wglBindVertexArray(gles2BlitVAO); + _wglEnableVertexAttribArray(0); + _wglVertexAttribPointer(0, 2, GL_FLOAT, false, 8, 0); + } + + IShaderGL vertShader = _wglCreateShader(GL_VERTEX_SHADER); + _wglShaderSource(vertShader, "#version 100\nprecision mediump float; attribute vec2 a_pos2f; varying vec2 v_tex2f; void main() { v_tex2f = a_pos2f; gl_Position = vec4(a_pos2f * 2.0 - 1.0, 0.0, 1.0); }"); + _wglCompileShader(vertShader); + + IShaderGL fragShader = _wglCreateShader(GL_FRAGMENT_SHADER); + _wglShaderSource(fragShader, checkTextureLODCapable() + ? "#version 100\n#extension GL_EXT_shader_texture_lod : enable\nprecision mediump float; precision mediump sampler2D; varying vec2 v_tex2f; uniform sampler2D u_samplerTex; void main() { gl_FragColor = vec4(texture2DLodEXT(u_samplerTex, v_tex2f, 0.0).rgb, 1.0); }" + : "#version 100\nprecision mediump float; precision mediump sampler2D; varying vec2 v_tex2f; uniform sampler2D u_samplerTex; void main() { gl_FragColor = vec4(texture2D(u_samplerTex, v_tex2f).rgb, 1.0); }"); + _wglCompileShader(fragShader); + + gles2BlitProgram = _wglCreateProgram(); + + _wglAttachShader(gles2BlitProgram, vertShader); + _wglAttachShader(gles2BlitProgram, fragShader); + + _wglBindAttribLocation(gles2BlitProgram, 0, "a_pos2f"); + + _wglLinkProgram(gles2BlitProgram); + + _wglDetachShader(gles2BlitProgram, vertShader); + _wglDetachShader(gles2BlitProgram, fragShader); + + _wglDeleteShader(vertShader); + _wglDeleteShader(fragShader); + + _wglUseProgram(gles2BlitProgram); + + _wglUniform1i(_wglGetUniformLocation(gles2BlitProgram, "u_samplerTex"), 0); + } + } + + public static IFramebufferGL getBackBuffer() { + return eagFramebuffer; + } + + public static void enterVAOEmulationPhase() { + if(glesVers < 300) { + if(!isEmulatedVAOPhase) { + if(isVAOCapable) { + _wglDeleteVertexArrays(gles2BlitVAO); + } + gles2BlitVAO = EaglercraftGPU.createGLBufferArray(); + EaglercraftGPU.bindGLBufferArray(gles2BlitVAO); + EaglercraftGPU.bindVAOGLArrayBuffer(gles2BlitVBO); + EaglercraftGPU.enableVertexAttribArray(0); + EaglercraftGPU.vertexAttribPointer(0, 2, GL_FLOAT, false, 8, 0); + isEmulatedVAOPhase = true; + } + } + } + + private static void drawBlitQuad() { + if(isEmulatedVAOPhase) { + EaglercraftGPU.bindGLBufferArray(gles2BlitVAO); + EaglercraftGPU.doDrawArrays(GL_TRIANGLES, 0, 6); + }else { + if(isVAOCapable) { + _wglBindVertexArray(gles2BlitVAO); + _wglDrawArrays(GL_TRIANGLES, 0, 6); + }else { + EaglercraftGPU.bindGLArrayBuffer(gles2BlitVBO); + _wglEnableVertexAttribArray(0); + _wglVertexAttribPointer(0, 2, GL_FLOAT, false, 8, 0); + _wglDrawArrays(GL_TRIANGLES, 0, 6); + } + } + } + + public static void flipBuffer(int windowWidth, int windowHeight) { + if(glesVers >= 300) { + _wglBindFramebufferLow(_GL_READ_FRAMEBUFFER, eagFramebuffer); + _wglBindFramebufferLow(_GL_DRAW_FRAMEBUFFER, null); + _wglBlitFramebuffer(0, 0, width, height, 0, 0, windowWidth, windowHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + _wglBindFramebufferLow(_GL_FRAMEBUFFER, eagFramebuffer); + + if(windowWidth != width || windowHeight != height) { + width = windowWidth; + height = windowHeight; + + _wglBindRenderbuffer(_GL_RENDERBUFFER, gles3ColorRenderbuffer); + _wglRenderbufferStorage(_GL_RENDERBUFFER, GL_RGBA8, windowWidth, windowHeight); + + _wglBindRenderbuffer(_GL_RENDERBUFFER, gles3DepthRenderbuffer); + _wglRenderbufferStorage(_GL_RENDERBUFFER, _GL_DEPTH_COMPONENT32F, windowWidth, windowHeight); + } + }else { + _wglBindFramebufferLow(_GL_FRAMEBUFFER, null); + _wglActiveTexture(GL_TEXTURE0); + _wglBindTexture(GL_TEXTURE_2D, gles2ColorTexture); + + int[] viewportStash = null; + if(isEmulatedVAOPhase) { + viewportStash = new int[4]; + EaglercraftGPU.glGetInteger(GL_VIEWPORT, viewportStash); + GlStateManager.viewport(0, 0, windowWidth, windowHeight); + GlStateManager.eagPushStateForGLES2BlitHack(); + GlStateManager.disableDepth(); + GlStateManager.disableBlend(); + }else { + _wglViewport(0, 0, windowWidth, windowHeight); + _wglDisable(GL_DEPTH_TEST); + _wglDisable(GL_BLEND); + } + + EaglercraftGPU.clearCurrentBinding(EaglercraftGPU.CLEAR_BINDING_SHADER_PROGRAM | EaglercraftGPU.CLEAR_BINDING_ARRAY_BUFFER); + + EaglercraftGPU.bindGLShaderProgram(gles2BlitProgram); + + drawBlitQuad(); + + if(windowWidth != width || windowHeight != height) { + width = windowWidth; + height = windowHeight; + + _wglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, windowWidth, windowHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, (ByteBuffer)null); + + _wglBindRenderbuffer(_GL_RENDERBUFFER, gles2DepthRenderbuffer); + _wglRenderbufferStorage(_GL_RENDERBUFFER, _GL_DEPTH_COMPONENT16, windowWidth, windowHeight); + } + + if(isEmulatedVAOPhase) { + EaglercraftGPU.clearCurrentBinding(EaglercraftGPU.CLEAR_BINDING_TEXTURE0 | EaglercraftGPU.CLEAR_BINDING_ACTIVE_TEXTURE | EaglercraftGPU.CLEAR_BINDING_SHADER_PROGRAM); + if(viewportStash[2] > 0) { + GlStateManager.viewport(viewportStash[0], viewportStash[1], viewportStash[2], viewportStash[3]); + } + GlStateManager.eagPopStateForGLES2BlitHack(); + }else { + EaglercraftGPU.clearCurrentBinding(EaglercraftGPU.CLEAR_BINDING_TEXTURE0 | EaglercraftGPU.CLEAR_BINDING_ACTIVE_TEXTURE | EaglercraftGPU.CLEAR_BINDING_SHADER_PROGRAM | EaglercraftGPU.CLEAR_BINDING_BUFFER_ARRAY); + } + + _wglBindFramebuffer(_GL_FRAMEBUFFER, eagFramebuffer); + } + } + + public static void destroy() { + if(eagFramebuffer != null) { + _wglDeleteFramebuffer(eagFramebuffer); + eagFramebuffer = null; + } + if(gles3ColorRenderbuffer != null) { + _wglDeleteRenderbuffer(gles3ColorRenderbuffer); + gles3ColorRenderbuffer = null; + } + if(gles3DepthRenderbuffer != null) { + _wglDeleteRenderbuffer(gles3DepthRenderbuffer); + gles3DepthRenderbuffer = null; + } + if(gles2ColorTexture != null) { + _wglDeleteTextures(gles2ColorTexture); + gles2ColorTexture = null; + } + if(gles2DepthRenderbuffer != null) { + _wglDeleteRenderbuffer(gles2DepthRenderbuffer); + gles2DepthRenderbuffer = null; + } + if(gles2BlitProgram != null) { + _wglDeleteProgram(gles2BlitProgram); + gles2BlitProgram = null; + } + if(gles2BlitVAO != null) { + if(isEmulatedVAOPhase) { + EaglercraftGPU.destroyGLBufferArray(gles2BlitVAO); + }else if(isVAOCapable) { + _wglDeleteVertexArrays(gles2BlitVAO); + } + gles2BlitVAO = null; + } + if(gles2BlitVBO != null) { + _wglDeleteBuffers(gles2BlitVBO); + gles2BlitVBO = null; + } + width = 0; + height = 0; + isVAOCapable = false; + isEmulatedVAOPhase = false; + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WebGLQuery.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WebGLQuery.java new file mode 100644 index 0000000..8aa4ce6 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WebGLQuery.java @@ -0,0 +1,21 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import org.teavm.jso.JSObject; + +/** + * Copyright (c) 2022 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. + * + */ +public interface WebGLQuery extends JSObject { +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WebGLVertexArray.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WebGLVertexArray.java new file mode 100644 index 0000000..f3262b9 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/WebGLVertexArray.java @@ -0,0 +1,21 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm; + +import org.teavm.jso.JSObject; + +/** + * Copyright (c) 2022 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. + * + */ +public interface WebGLVertexArray extends JSObject { +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/opts/JSEaglercraftXOptsHooks.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/opts/JSEaglercraftXOptsHooks.java new file mode 100644 index 0000000..365d520 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/opts/JSEaglercraftXOptsHooks.java @@ -0,0 +1,35 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.opts; + +import org.teavm.jso.JSBody; +import org.teavm.jso.JSObject; + +/** + * 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. + * + */ +public interface JSEaglercraftXOptsHooks extends JSObject { + + @JSBody(script = "return (typeof this.localStorageSaved === \"function\") ? this.localStorageSaved : null;") + JSObject getLocalStorageSavedHook(); + + @JSBody(script = "return (typeof this.localStorageLoaded === \"function\") ? this.localStorageLoaded : null;") + JSObject getLocalStorageLoadedHook(); + + @JSBody(script = "return (typeof this.crashReportShow === \"function\") ? this.crashReportShow : null;") + JSObject getCrashReportHook(); + + @JSBody(script = "return (typeof this.screenChanged === \"function\") ? this.screenChanged : null;") + JSObject getScreenChangedHook(); + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/opts/JSEaglercraftXOptsRelay.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/opts/JSEaglercraftXOptsRelay.java new file mode 100644 index 0000000..8d5c621 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/opts/JSEaglercraftXOptsRelay.java @@ -0,0 +1,32 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.opts; + +import org.teavm.jso.JSBody; +import org.teavm.jso.JSObject; + +/** + * 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. + * + */ +public interface JSEaglercraftXOptsRelay extends JSObject { + + @JSBody(script = "return (typeof this.addr === \"string\") ? this.addr : null;") + String getAddr(); + + @JSBody(params = { "def" }, script = "return (typeof this.comment === \"string\") ? this.comment : def;") + String getComment(String defaultValue); + + @JSBody(script = "return (typeof this.primary === \"boolean\") ? this.primary : false;") + boolean getPrimary(); + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/opts/JSEaglercraftXOptsRoot.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/opts/JSEaglercraftXOptsRoot.java new file mode 100644 index 0000000..99d755f --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/opts/JSEaglercraftXOptsRoot.java @@ -0,0 +1,174 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.opts; + +import org.teavm.jso.JSBody; +import org.teavm.jso.JSObject; +import org.teavm.jso.core.JSArrayReader; + +/** + * 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. + * + */ +public interface JSEaglercraftXOptsRoot extends JSObject { + + @JSBody(params = { "def" }, script = "return (typeof this.lang === \"string\") ? this.lang : def;") + String getLang(String defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.joinServer === \"string\") ? this.joinServer : def;") + String getJoinServer(String defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.localesURI === \"string\") ? this.localesURI : def;") + String getLocalesURI(String defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.worldsDB === \"string\") ? this.worldsDB : def;") + String getWorldsDB(String defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.resourcePacksDB === \"string\") ? this.resourcePacksDB : def;") + String getResourcePacksDB(String defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.demoMode === \"boolean\") ? this.demoMode : def;") + boolean getDemoMode(boolean defaultValue); + + @JSBody(script = "return (typeof this.servers === \"object\") ? this.servers : null;") + JSArrayReader getServers(); + + @JSBody(script = "return (typeof this.relays === \"object\") ? this.relays : null;") + JSArrayReader getRelays(); + + @JSBody(params = { "def" }, script = "return (typeof this.checkGLErrors === \"boolean\") ? this.checkGLErrors : def;") + boolean getCheckGLErrors(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.checkShaderGLErrors === \"boolean\") ? this.checkShaderGLErrors : def;") + boolean getCheckShaderGLErrors(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.enableDownloadOfflineButton === \"boolean\") ? this.enableDownloadOfflineButton : def;") + boolean getEnableDownloadOfflineButton(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.downloadOfflineButtonLink === \"string\") ? this.downloadOfflineButtonLink : def;") + String getDownloadOfflineButtonLink(String defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.html5CursorSupport === \"boolean\") ? this.html5CursorSupport : def;") + boolean getHtml5CursorSupport(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.allowUpdateSvc === \"boolean\") ? this.allowUpdateSvc : def;") + boolean getAllowUpdateSvc(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.allowUpdateDL === \"boolean\") ? this.allowUpdateDL : def;") + boolean getAllowUpdateDL(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.logInvalidCerts === \"boolean\") ? this.logInvalidCerts : def;") + boolean getLogInvalidCerts(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.enableSignatureBadge === \"boolean\") ? this.enableSignatureBadge : def;") + boolean getEnableSignatureBadge(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.checkRelaysForUpdates === \"boolean\") ? this.checkRelaysForUpdates : def;") + boolean getCheckRelaysForUpdates(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.allowVoiceClient === \"boolean\") ? this.allowVoiceClient : def;") + boolean getAllowVoiceClient(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.allowFNAWSkins === \"boolean\") ? this.allowFNAWSkins : def;") + boolean getAllowFNAWSkins(boolean defaultValue); + + @JSBody(script = "return (typeof this.hooks === \"object\") ? this.hooks : null;") + JSEaglercraftXOptsHooks getHooks(); + + @JSBody(params = { "def" }, script = "return (typeof this.localStorageNamespace === \"string\") ? this.localStorageNamespace : def;") + String getLocalStorageNamespace(String defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.enableMinceraft === \"boolean\") ? this.enableMinceraft : def;") + boolean getEnableMinceraft(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.enableServerCookies === \"boolean\") ? this.enableServerCookies : def;") + boolean getEnableServerCookies(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.allowServerRedirects === \"boolean\") ? this.allowServerRedirects : def;") + boolean getAllowServerRedirects(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.crashOnUncaughtExceptions === \"boolean\") ? this.crashOnUncaughtExceptions : def;") + boolean getCrashOnUncaughtExceptions(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.openDebugConsoleOnLaunch === \"boolean\") ? this.openDebugConsoleOnLaunch : def;") + boolean getOpenDebugConsoleOnLaunch(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.fixDebugConsoleUnloadListener === \"boolean\") ? this.fixDebugConsoleUnloadListener : def;") + boolean getFixDebugConsoleUnloadListener(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.forceWebViewSupport === \"boolean\") ? this.forceWebViewSupport : def;") + boolean getForceWebViewSupport(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.enableWebViewCSP === \"boolean\") ? this.enableWebViewCSP : def;") + boolean getEnableWebViewCSP(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.autoFixLegacyStyleAttr === \"boolean\") ? this.autoFixLegacyStyleAttr : def;") + boolean getAutoFixLegacyStyleAttr(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.showBootMenuOnLaunch === \"boolean\") ? this.showBootMenuOnLaunch : def;") + boolean getShowBootMenuOnLaunch(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.bootMenuBlocksUnsignedClients === \"boolean\") ? this.bootMenuBlocksUnsignedClients : def;") + boolean getBootMenuBlocksUnsignedClients(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.allowBootMenu === \"boolean\") ? this.allowBootMenu : def;") + boolean getAllowBootMenu(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.forceProfanityFilter === \"boolean\") ? this.forceProfanityFilter : def;") + boolean getForceProfanityFilter(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.forceWebGL1 === \"boolean\") ? this.forceWebGL1 : def;") + boolean getForceWebGL1(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.forceWebGL2 === \"boolean\") ? this.forceWebGL2 : def;") + boolean getForceWebGL2(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.allowExperimentalWebGL1 === \"boolean\") ? this.allowExperimentalWebGL1 : def;") + boolean getAllowExperimentalWebGL1(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.useWebGLExt === \"boolean\") ? this.useWebGLExt : def;") + boolean getUseWebGLExt(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.useDelayOnSwap === \"boolean\") ? this.useDelayOnSwap : def;") + boolean getUseDelayOnSwap(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.useJOrbisAudioDecoder === \"boolean\") ? this.useJOrbisAudioDecoder : def;") + boolean getUseJOrbisAudioDecoder(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.useXHRFetch === \"boolean\") ? this.useXHRFetch : def;") + boolean getUseXHRFetch(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.useVisualViewport === \"boolean\") ? this.useVisualViewport : def;") + boolean getUseVisualViewport(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.deobfStackTraces === \"boolean\") ? this.deobfStackTraces : def;") + boolean getDeobfStackTraces(boolean deobfStackTraces); + + @JSBody(params = { "def" }, script = "return (typeof this.disableBlobURLs === \"boolean\") ? this.disableBlobURLs : def;") + boolean getDisableBlobURLs(boolean deobfStackTraces); + + @JSBody(params = { "def" }, script = "return (typeof this.eaglerNoDelay === \"boolean\") ? this.eaglerNoDelay : def;") + boolean getEaglerNoDelay(boolean deobfStackTraces); + + @JSBody(params = { "def" }, script = "return (typeof this.ramdiskMode === \"boolean\") ? this.ramdiskMode : def;") + boolean getRamdiskMode(boolean deobfStackTraces); + + @JSBody(params = { "def" }, script = "return (typeof this.singleThreadMode === \"boolean\") ? this.singleThreadMode : def;") + boolean getSingleThreadMode(boolean deobfStackTraces); + + @JSBody(params = { "def" }, script = "return (typeof this.enforceVSync === \"boolean\") ? this.enforceVSync : def;") + boolean getEnforceVSync(boolean enforceVSync); + + @JSBody(params = { "def" }, script = "return (typeof this.enableEPKVersionCheck === \"boolean\") ? this.enableEPKVersionCheck : def;") + boolean getEnableEPKVersionCheck(boolean deobfStackTraces); + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/opts/JSEaglercraftXOptsServer.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/opts/JSEaglercraftXOptsServer.java new file mode 100644 index 0000000..d3edd12 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/internal/wasm_gc_teavm/opts/JSEaglercraftXOptsServer.java @@ -0,0 +1,32 @@ +package net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.opts; + +import org.teavm.jso.JSBody; +import org.teavm.jso.JSObject; + +/** + * 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. + * + */ +public interface JSEaglercraftXOptsServer extends JSObject { + + @JSBody(script = "return (typeof this.addr === \"string\") ? this.addr : null;") + String getAddr(); + + @JSBody(params = { "def" }, script = "return (typeof this.hideAddr === \"boolean\") ? this.hideAddr : def;") + boolean getHideAddr(boolean defaultValue); + + @JSBody(params = { "def" }, script = "return (typeof this.name === \"string\") ? this.name : def;") + String getName(String defaultValue); + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/sp/internal/ClientPlatformSingleplayer.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/sp/internal/ClientPlatformSingleplayer.java new file mode 100644 index 0000000..524eae1 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/sp/internal/ClientPlatformSingleplayer.java @@ -0,0 +1,143 @@ +package net.lax1dude.eaglercraft.v1_8.sp.internal; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import org.teavm.interop.Import; +import org.teavm.jso.core.JSString; +import org.teavm.jso.typedarrays.Uint8Array; + +import net.lax1dude.eaglercraft.v1_8.internal.IPCPacketData; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCBufferAllocator; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCDirectArrayConverter; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.BetterJSStringConverter; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.WASMGCClientConfigAdapter; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; +import net.lax1dude.eaglercraft.v1_8.sp.server.internal.wasm_gc_teavm.JS_IPCPacketData; +import net.lax1dude.eaglercraft.v1_8.sp.server.internal.wasm_gc_teavm.SingleThreadWorker; + +/** + * 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. + * + */ +public class ClientPlatformSingleplayer { + + private static final Logger logger = LogManager.getLogger("ClientPlatformSingleplayer"); + + private static boolean isSingleThreadMode = false; + + private static final LinkedList singleThreadMessageQueue = new LinkedList<>(); + + public static void startIntegratedServer(boolean singleThreadMode) { + singleThreadMode |= ((WASMGCClientConfigAdapter)PlatformRuntime.getClientConfigAdapter()).isSingleThreadModeTeaVM(); + if(singleThreadMode) { + if(!isSingleThreadMode) { + SingleThreadWorker.singleThreadStartup(singleThreadMessageQueue::add); + isSingleThreadMode = true; + } + }else { + if(!startIntegratedServer0()) { + logger.error("Failed to start integrated server!"); + logger.error("Falling back to single thread mode..."); + startIntegratedServer(true); + }else { + logger.info("Integrated server started"); + } + } + } + + @Import(module = "clientPlatformSingleplayer", name = "startIntegratedServer") + private static native boolean startIntegratedServer0(); + + public static void sendPacket(IPCPacketData packet) { + if(isSingleThreadMode) { + SingleThreadWorker.sendPacketToWorker(packet); + }else { + ByteBuffer buf = WASMGCDirectArrayConverter.byteArrayToBuffer(packet.contents); + try { + sendPacket0(BetterJSStringConverter.stringToJS(packet.channel), WASMGCBufferAllocator.getUnsignedByteBufferView(buf)); + }finally { + PlatformRuntime.freeByteBuffer(buf); + } + } + } + + @Import(module = "clientPlatformSingleplayer", name = "sendPacket") + private static native void sendPacket0(JSString channel, Uint8Array arr); + + public static List recieveAllPacket() { + if(isSingleThreadMode) { + if(singleThreadMessageQueue.size() == 0) { + return null; + }else { + List ret = new ArrayList<>(singleThreadMessageQueue); + singleThreadMessageQueue.clear(); + return ret; + } + }else { + int cnt = getAvailablePackets(); + if(cnt == 0) { + return null; + } + IPCPacketData[] ret = new IPCPacketData[cnt]; + for(int i = 0; i < cnt; ++i) { + ret[i] = getNextPacket().internalize(); + } + return Arrays.asList(ret); + } + } + + @Import(module = "clientPlatformSingleplayer", name = "getAvailablePackets") + private static native int getAvailablePackets(); + + @Import(module = "clientPlatformSingleplayer", name = "getNextPacket") + private static native JS_IPCPacketData getNextPacket(); + + public static boolean canKillWorker() { + return !isSingleThreadMode; + } + + @Import(module = "clientPlatformSingleplayer", name = "killWorker") + public static native void killWorker(); + + public static boolean isRunningSingleThreadMode() { + return isSingleThreadMode; + } + + public static boolean isSingleThreadModeSupported() { + return true; + } + + public static void updateSingleThreadMode() { + if(isSingleThreadMode) { + SingleThreadWorker.singleThreadUpdate(); + } + } + + public static void showCrashReportOverlay(String report, int x, int y, int w, int h) { + showCrashReportOverlay0(BetterJSStringConverter.stringToJS(report), x, y, w, h); + } + + @Import(module = "clientPlatformSingleplayer", name = "showCrashReportOverlay") + private static native void showCrashReportOverlay0(JSString report, int x, int y, int w, int h); + + @Import(module = "clientPlatformSingleplayer", name = "hideCrashReportOverlay") + public static native void hideCrashReportOverlay(); + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/ServerPlatformSingleplayer.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/ServerPlatformSingleplayer.java new file mode 100644 index 0000000..a89e59f --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/ServerPlatformSingleplayer.java @@ -0,0 +1,146 @@ +package net.lax1dude.eaglercraft.v1_8.sp.server.internal; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Consumer; + +import org.teavm.interop.Import; +import org.teavm.jso.JSFunctor; +import org.teavm.jso.JSObject; +import org.teavm.jso.core.JSString; +import org.teavm.jso.typedarrays.Uint8Array; + +import net.lax1dude.eaglercraft.v1_8.Filesystem; +import net.lax1dude.eaglercraft.v1_8.internal.IClientConfigAdapter; +import net.lax1dude.eaglercraft.v1_8.internal.IEaglerFilesystem; +import net.lax1dude.eaglercraft.v1_8.internal.IPCPacketData; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.ByteBuffer; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCBufferAllocator; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCDirectArrayConverter; +import net.lax1dude.eaglercraft.v1_8.internal.vfs2.VFile2; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.BetterJSStringConverter; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.WASMGCClientConfigAdapter; +import net.lax1dude.eaglercraft.v1_8.sp.server.EaglerIntegratedServerWorker; +import net.lax1dude.eaglercraft.v1_8.sp.server.IWASMCrashCallback; +import net.lax1dude.eaglercraft.v1_8.sp.server.internal.wasm_gc_teavm.JS_IPCPacketData; + +/** + * 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. + * + */ +public class ServerPlatformSingleplayer { + + private static final List messageQueue = new LinkedList<>(); + + private static boolean singleThreadMode = false; + private static Consumer singleThreadCB = null; + + private static IEaglerFilesystem filesystem = null; + + public static void initializeContext() { + singleThreadMode = false; + singleThreadCB = null; + filesystem = Filesystem.getHandleFor(getClientConfigAdapter().getWorldsDB()); + VFile2.setPrimaryFilesystem(filesystem); + } + + public static IEaglerFilesystem getWorldsDatabase() { + return filesystem; + } + + public static void initializeContextSingleThread(Consumer packetSendCallback) { + singleThreadMode = true; + singleThreadCB = packetSendCallback; + filesystem = Filesystem.getHandleFor(getClientConfigAdapter().getWorldsDB()); + } + + public static void sendPacket(IPCPacketData packet) { + if(singleThreadMode) { + singleThreadCB.accept(packet); + }else { + ByteBuffer buf = WASMGCDirectArrayConverter.byteArrayToBuffer(packet.contents); + try { + sendPacket0(BetterJSStringConverter.stringToJS(packet.channel), WASMGCBufferAllocator.getUnsignedByteBufferView(buf)); + }finally { + PlatformRuntime.freeByteBuffer(buf); + } + } + } + + @Import(module = "serverPlatformSingleplayer", name = "sendPacket") + private static native void sendPacket0(JSString channel, Uint8Array arr); + + public static List recieveAllPacket() { + if(singleThreadMode) { + if(messageQueue.size() == 0) { + return null; + }else { + List ret = new ArrayList<>(messageQueue); + messageQueue.clear(); + return ret; + } + }else { + int cnt = getAvailablePackets(); + if(cnt == 0) { + return null; + } + IPCPacketData[] ret = new IPCPacketData[cnt]; + for(int i = 0; i < cnt; ++i) { + ret[i] = getNextPacket().internalize(); + } + return Arrays.asList(ret); + } + } + + @Import(module = "serverPlatformSingleplayer", name = "getAvailablePackets") + private static native int getAvailablePackets(); + + @Import(module = "serverPlatformSingleplayer", name = "getNextPacket") + private static native JS_IPCPacketData getNextPacket(); + + @Import(module = "platformRuntime", name = "immediateContinue") + public static native void immediateContinue(); + + public static IClientConfigAdapter getClientConfigAdapter() { + return WASMGCClientConfigAdapter.instance; + } + + public static boolean isSingleThreadMode() { + return singleThreadMode; + } + + public static void recievePacketSingleThreadTeaVM(IPCPacketData pkt) { + messageQueue.add(pkt); + } + + public static void setCrashCallbackWASM(IWASMCrashCallback callback) { + setCrashCallbackWASM0().call(callback != null ? callback::callback : null); + } + + @JSFunctor + private static interface JSWASMCrashCallback extends JSObject { + void callback(String crashReport, boolean terminated); + } + + private static interface JSWASMCrashCallbackInterface extends JSObject { + void call(JSWASMCrashCallback callback); + } + + @Import(module = "serverPlatformSingleplayer", name = "setCrashCallback") + private static native JSWASMCrashCallbackInterface setCrashCallbackWASM0(); + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/wasm_gc_teavm/JS_IPCPacketData.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/wasm_gc_teavm/JS_IPCPacketData.java new file mode 100644 index 0000000..fde06d9 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/wasm_gc_teavm/JS_IPCPacketData.java @@ -0,0 +1,37 @@ +package net.lax1dude.eaglercraft.v1_8.sp.server.internal.wasm_gc_teavm; + +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; +import org.teavm.jso.typedarrays.Uint8Array; + +import net.lax1dude.eaglercraft.v1_8.internal.IPCPacketData; +import net.lax1dude.eaglercraft.v1_8.internal.buffer.WASMGCDirectArrayConverter; + +/** + * 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. + * + */ +public interface JS_IPCPacketData extends JSObject { + + @JSProperty + String getCh(); + + @JSProperty + Uint8Array getData(); + + default IPCPacketData internalize() { + return new IPCPacketData(getCh(), WASMGCDirectArrayConverter.externU8ArrayToByteArray(getData())); + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/wasm_gc_teavm/SingleThreadWorker.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/wasm_gc_teavm/SingleThreadWorker.java new file mode 100644 index 0000000..d806571 --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/wasm_gc_teavm/SingleThreadWorker.java @@ -0,0 +1,44 @@ +package net.lax1dude.eaglercraft.v1_8.sp.server.internal.wasm_gc_teavm; + +import java.util.function.Consumer; + +import net.lax1dude.eaglercraft.v1_8.internal.IPCPacketData; +import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; +import net.lax1dude.eaglercraft.v1_8.log4j.Logger; +import net.lax1dude.eaglercraft.v1_8.sp.server.EaglerIntegratedServerWorker; +import net.lax1dude.eaglercraft.v1_8.sp.server.internal.ServerPlatformSingleplayer; + +/** + * 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. + * + */ +public class SingleThreadWorker { + + private static final Logger logger = LogManager.getLogger("SingleThreadWorker"); + + public static void singleThreadStartup(Consumer packetSendCallback) { + logger.info("Starting single-thread mode worker..."); + ServerPlatformSingleplayer.initializeContextSingleThread(packetSendCallback); + EaglerIntegratedServerWorker.singleThreadMain(); + } + + public static void sendPacketToWorker(IPCPacketData pkt) { + ServerPlatformSingleplayer.recievePacketSingleThreadTeaVM(pkt); + } + + public static void singleThreadUpdate() { + EaglerIntegratedServerWorker.singleThreadUpdate(); + } + +} diff --git a/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/wasm_gc_teavm/WorkerMain.java b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/wasm_gc_teavm/WorkerMain.java new file mode 100644 index 0000000..78849bf --- /dev/null +++ b/sources/wasm-gc-teavm/java/net/lax1dude/eaglercraft/v1_8/sp/server/internal/wasm_gc_teavm/WorkerMain.java @@ -0,0 +1,61 @@ +package net.lax1dude.eaglercraft.v1_8.sp.server.internal.wasm_gc_teavm; + +import java.io.PrintStream; + +import org.teavm.interop.Import; +import org.teavm.jso.JSObject; + +import net.lax1dude.eaglercraft.v1_8.EagRuntime; +import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime; +import net.lax1dude.eaglercraft.v1_8.internal.wasm_gc_teavm.WASMGCClientConfigAdapter; +import net.lax1dude.eaglercraft.v1_8.sp.ipc.IPCPacket15Crashed; +import net.lax1dude.eaglercraft.v1_8.sp.ipc.IPCPacketFFProcessKeepAlive; +import net.lax1dude.eaglercraft.v1_8.sp.server.EaglerIntegratedServerWorker; +import net.lax1dude.eaglercraft.v1_8.sp.server.internal.ServerPlatformSingleplayer; + +/** + * 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. + * + */ +public class WorkerMain { + + public static void _main() { + PrintStream systemOut = System.out; + PrintStream systemErr = System.err; + try { + systemOut.println("WorkerMain: [INFO] eaglercraftx worker thread is starting..."); + JSObject startArgs = getEaglerXOpts(); + systemOut.println("WorkerMain: [INFO] reading configuration"); + ((WASMGCClientConfigAdapter)WASMGCClientConfigAdapter.instance).loadNative(startArgs); + systemOut.println("WorkerMain: [INFO] initializing server runtime"); + ServerPlatformSingleplayer.initializeContext(); + systemOut.println("WorkerMain: [INFO] starting worker thread"); + PlatformRuntime.setThreadName("IntegratedServer"); + EaglerIntegratedServerWorker.serverMain(); + }catch(Throwable t) { + System.setOut(systemOut); + System.setErr(systemErr); + systemErr.println("WorkerMain: [ERROR] uncaught exception thrown!"); + EagRuntime.debugPrintStackTraceToSTDERR(t); + EaglerIntegratedServerWorker.sendIPCPacket(new IPCPacket15Crashed("UNCAUGHT EXCEPTION CAUGHT IN WORKER PROCESS!\n\n" + EagRuntime.getStackTrace(t))); + EaglerIntegratedServerWorker.sendIPCPacket(new IPCPacketFFProcessKeepAlive(IPCPacketFFProcessKeepAlive.EXITED)); + }finally { + systemErr.println("WorkerMain: [ERROR] eaglercraftx worker thread has exited"); + } + } + + @Import(module = "platformRuntime", name = "getEaglercraftXOpts") + private static native JSObject getEaglerXOpts(); + +} diff --git a/sources/wasm-gc-teavm/js/WASMGCBufferAllocator.js b/sources/wasm-gc-teavm/js/WASMGCBufferAllocator.js new file mode 100644 index 0000000..fcaf40f --- /dev/null +++ b/sources/wasm-gc-teavm/js/WASMGCBufferAllocator.js @@ -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); +} diff --git a/sources/wasm-gc-teavm/js/clientPlatformSingleplayer.js b/sources/wasm-gc-teavm/js/clientPlatformSingleplayer.js new file mode 100644 index 0000000..16c7f39 --- /dev/null +++ b/sources/wasm-gc-teavm/js/clientPlatformSingleplayer.js @@ -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} + */ + 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"); +} diff --git a/sources/wasm-gc-teavm/js/eagruntime_entrypoint.js b/sources/wasm-gc-teavm/js/eagruntime_entrypoint.js new file mode 100644 index 0000000..f6c3c1f --- /dev/null +++ b/sources/wasm-gc-teavm/js/eagruntime_entrypoint.js @@ -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):Array|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("???"); +} diff --git a/sources/wasm-gc-teavm/js/eagruntime_main.js b/sources/wasm-gc-teavm/js/eagruntime_main.js new file mode 100644 index 0000000..5743037 --- /dev/null +++ b/sources/wasm-gc-teavm/js/eagruntime_main.js @@ -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):Array|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} */ +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} + */ +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 = "

Mobile Browser Detected

" + + "

Warning: EaglercraftX WASM-GC requires a lot of memory and may not be stable on most mobile devices!

" + + "

" + /*+ (allowBootMenu ? "

" : "")*/ + + "

(Tablets and phones with large screens work best)

"; + 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} */ + 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} + */ +function deobfuscateStack(stack) { + if(!stack) return null; + /** @type {!Array} */ + 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} */ + 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 = "

+ This device is incompatible with Eaglercraft :(

" + + "
" + + "

Issue:

" + + "

" + + "

" + + "

Current Date: " + (new Date()).toLocaleString() + "

" + + "


Things you can try:

" + + "
    " + + "
  1. Just try using Eaglercraft on a different device, it isn't a bug it's common sense
  2. " + + "
  3. If this screen just appeared randomly, try restarting your browser or device
  4. " + + "
  5. If you are not using Chrome/Edge, try installing the latest Google Chrome
  6. " + + "
  7. If your browser is out of date, please update it to the latest version
  8. " + + "
" + + "
"; + + 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 = ""; + } + return "window.navigator." + k + " = " + val + "\n"; +} + +/** + * @param {string} k + * @return {string} + */ +function getStringNav(k) { + try { + return window.navigator[k]; + } catch(e) { + return ""; + } +} + +/** + * @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 = ""; + } + return "window.navigator.plugins = " + val + "\n"; +} + +/** + * @param {string} k + * @return {string} + */ +function addDebugScreen(k) { + var val; + try { + val = window.screen[k]; + } catch(e) { + val = ""; + } + return "window.screen." + k + " = " + val + "\n"; +} + +/** + * @param {string} k + * @return {string} + */ +function addDebugLocation(k) { + var val; + try { + val = window.location[k]; + } catch(e) { + val = ""; + } + return "window.location." + k + " = " + val + "\n"; +} + +/** + * @param {string} k + * @return {string} + */ +function addDebug(k) { + var val; + try { + val = window[k]; + } catch(e) { + val = ""; + } + return "window." + k + " = " + val + "\n"; +} diff --git a/sources/wasm-gc-teavm/js/eagruntime_util.js b/sources/wasm-gc-teavm/js/eagruntime_util.js new file mode 100644 index 0000000..350f802 --- /dev/null +++ b/sources/wasm-gc-teavm/js/eagruntime_util.js @@ -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, "{}: ", 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; + } + } + +} diff --git a/sources/wasm-gc-teavm/js/externs.js b/sources/wasm-gc-teavm/js/externs.js new file mode 100644 index 0000000..12526ad --- /dev/null +++ b/sources/wasm-gc-teavm/js/externs.js @@ -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)} fn + */ +self.__eaglercraftXLoaderContext.runMain = function(fn) {}; + +/** + * @param {Object} o + */ +self.__eaglerXOnMessage = function(o) {}; + +window.__isEaglerX188UnloadListenerSet = ""; + +/** @type {function()|null} */ +window.__curEaglerX188UnloadListenerCB = function() {}; + +/** + * @return {Promise} + */ +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 = ""; diff --git a/sources/wasm-gc-teavm/js/fix-webm-duration.js b/sources/wasm-gc-teavm/js/fix-webm-duration.js new file mode 100644 index 0000000..d2ff2f1 --- /dev/null +++ b/sources/wasm-gc-teavm/js/fix-webm-duration.js @@ -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); + } +} diff --git a/sources/wasm-gc-teavm/js/platformApplication.js b/sources/wasm-gc-teavm/js/platformApplication.js new file mode 100644 index 0000000..3136005 --- /dev/null +++ b/sources/wasm-gc-teavm/js/platformApplication.js @@ -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} + */ + 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("" + + "Debug Console" + + "

"); + 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"); +} diff --git a/sources/wasm-gc-teavm/js/platformAssets.js b/sources/wasm-gc-teavm/js/platformAssets.js new file mode 100644 index 0000000..a19f1de --- /dev/null +++ b/sources/wasm-gc-teavm/js/platformAssets.js @@ -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"); +} diff --git a/sources/wasm-gc-teavm/js/platformAudio.js b/sources/wasm-gc-teavm/js/platformAudio.js new file mode 100644 index 0000000..04de4fe --- /dev/null +++ b/sources/wasm-gc-teavm/js/platformAudio.js @@ -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"); +} diff --git a/sources/wasm-gc-teavm/js/platformFilesystem.js b/sources/wasm-gc-teavm/js/platformFilesystem.js new file mode 100644 index 0000000..31716f2 --- /dev/null +++ b/sources/wasm-gc-teavm/js/platformFilesystem.js @@ -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} + */ +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} + */ +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); diff --git a/sources/wasm-gc-teavm/js/platformInput.js b/sources/wasm-gc-teavm/js/platformInput.js new file mode 100644 index 0000000..2abb124 --- /dev/null +++ b/sources/wasm-gc-teavm/js/platformInput.js @@ -0,0 +1,1138 @@ +/* + * 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 platfInputName = "platformInput"; + +async function initPlatformInput(inputImports) { + + const EVENT_INPUT_MOUSE = 0; + const EVENT_INPUT_KEYBOARD = 1; + const EVENT_INPUT_TOUCH = 2; + const EVENT_INPUT_TOUCH_KEYBOARD = 3; + const EVENT_INPUT_TOUCH_PASTE = 4; + const EVENT_INPUT_FOCUS = 5; + const EVENT_INPUT_BLUR = 6; + const EVENT_INPUT_MOUSE_ENTER = 7; + const EVENT_INPUT_MOUSE_EXIT = 8; + const EVENT_INPUT_WINDOW_RESIZE = 9; + const EVENT_INPUT_GAMEPAD = 10; + + const EVENT_MOUSE_DOWN = 0; + const EVENT_MOUSE_UP = 1; + const EVENT_MOUSE_MOVE = 2; + const EVENT_MOUSE_WHEEL = 3; + + const EVENT_KEY_DOWN = 0; + const EVENT_KEY_UP = 1; + const EVENT_KEY_REPEAT = 2; + + const EVENT_KEY_MOD_CONTROL = 1; + const EVENT_KEY_MOD_SHIFT = 2; + const EVENT_KEY_MOD_ALT = 4; + + const EVENT_TOUCH_START = 0; + const EVENT_TOUCH_MOVE = 1; + const EVENT_TOUCH_END = 2; + + const EVENT_TOUCH_KEYBOARD_ABSOLUTE = 0; + const EVENT_TOUCH_KEYBOARD_CODEPOINTS = 1; + + const EVENT_RESIZE_WINDOW = 1; + const EVENT_RESIZE_VISUAL_VIEWPORT = 2; + const EVENT_RESIZE_DPI = 4; + + const EVENT_GAMEPAD_CONNECTED = 0; + const EVENT_GAMEPAD_DISCONNECTED = 1; + + var touchKeyboardOpenZone = null; + var touchOffsetX = 0; + var touchOffsetY = 0; + var touchKeyboardForm = null; + var touchKeyboardField = null; + var shownTouchKeyboardEventWarning = false; + var shownLegacyTouchKeyboardWarning = false; + var showniOSReturnTouchKeyboardWarning = false; + var lastTouchKeyboardEvtA = 0.0; + var lastTouchKeyboardEvtB = 0.0; + var lastTouchKeyboardEvtC = 0.0; + + var lastWindowWidth = -1; + var lastWindowHeight = -1; + var lastWindowDPI = -1.0; + var useVisualViewport = false; + var lastVisualViewportX = -1; + var lastVisualViewportY = -1; + var lastVisualViewportW = -1; + var lastVisualViewportH = -1; + + var pointerLockSupported = false; + var pointerLockFlag = false; + var mouseUngrabTimer = 0; + var mouseGrabTimer = 0; + var mouseUngrabTimeout = -1; + + var fullscreenSupported = false; + var fullscreenQuery = null; + var keyboardLockSupported = false; + var lockKeys = false; + + var vsyncSupport = false; + var vsyncTimeout = -1; + + var gamepadSupported = false; + + const currentEventListeners = { + contextmenu: null, + mousedown: null, + mouseup: null, + mousemove: null, + mouseenter: null, + mouseleave: null, + touchstart: null, + touchend: null, + touchmove: null, + touchcancel: null, + gamepadconnected: null, + gamepaddisconnected: null, + keydown: null, + keyup: null, + touchKeyboardOpenZone_touchstart: null, + touchKeyboardOpenZone_touchend: null, + touchKeyboardOpenZone_touchmove: null, + wheel: null, + focus: null, + blur: null, + pointerlock: null, + fullscreenChange: null + }; + + touchKeyboardOpenZone = document.createElement("div"); + touchKeyboardOpenZone.classList.add("_eaglercraftX_keyboard_open_zone"); + touchKeyboardOpenZone.style.display = "none"; + touchKeyboardOpenZone.style.position = "absolute"; + touchKeyboardOpenZone.style.backgroundColor = "transparent"; + touchKeyboardOpenZone.style.top = "0px"; + touchKeyboardOpenZone.style.left = "0px"; + touchKeyboardOpenZone.style.width = "0px"; + touchKeyboardOpenZone.style.height = "0px"; + touchKeyboardOpenZone.style.zIndex = "100"; + touchKeyboardOpenZone.style.touchAction = "pan-x pan-y"; + touchKeyboardOpenZone.style.setProperty("-webkit-touch-callout", "none"); + touchKeyboardOpenZone.style.setProperty("-webkit-tap-highlight-color", "rgba(255, 255, 255, 0)"); + parentElement.appendChild(touchKeyboardOpenZone); + + function updateTouchOffset() { + touchOffsetX = 0; + touchOffsetY = 0; + var el = canvasElement; + while(el && !isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) { + touchOffsetX += el.offsetLeft - el.scrollLeft; + touchOffsetY += el.offsetTop - el.scrollTop; + el = el.offsetParent; + } + } + + function reportWindowSize() { + var newWindowDPI = window.devicePixelRatio; + if(newWindowDPI < 0.01) newWindowDPI = 1.0; + updateTouchOffset(); + const w = parentElement.clientWidth; + const h = parentElement.clientHeight; + var newWindowWidth = (w * newWindowDPI) | 0; + var newWindowHeight = (h * newWindowDPI) | 0; + var newVisualViewportX = 0; + var newVisualViewportY = 0; + var newVisualViewportW = newWindowWidth; + var newVisualViewportH = newWindowHeight; + if(useVisualViewport) { + const vv = window.visualViewport; + const scale = vv.scale; + newVisualViewportX = (vv.pageLeft * newWindowDPI * scale); + newVisualViewportY = (vv.pageTop * newWindowDPI * scale); + newVisualViewportW = (vv.width * newWindowDPI * scale); + newVisualViewportH = (vv.height * newWindowDPI * scale); + if(newVisualViewportW < 1) newVisualViewportW = 1; + if(newVisualViewportH < 1) newVisualViewportH = 1; + if(newVisualViewportX < 0) { + newVisualViewportW += newVisualViewportX; + newVisualViewportX = 0; + }else if(newVisualViewportX >= newWindowWidth) { + newVisualViewportX = newWindowWidth - 1; + } + if(newVisualViewportY < 0) { + newVisualViewportH += newVisualViewportY; + newVisualViewportY = 0; + }else if(newVisualViewportY >= newWindowHeight) { + newVisualViewportY = newWindowHeight - 1; + } + if((newVisualViewportX + newVisualViewportW) > newWindowWidth) { + newVisualViewportW = newWindowWidth - newVisualViewportX; + } + if((newVisualViewportY + newVisualViewportH) > newWindowHeight) { + newVisualViewportH = newWindowHeight - newVisualViewportY; + } + } + const eventData = { + "eventTypeMask": 0 + }; + if(lastWindowDPI !== newWindowDPI) { + lastWindowDPI = newWindowDPI; + eventData["eventTypeMask"] |= EVENT_RESIZE_DPI; + eventData["windowDPI"] = newWindowDPI; + } + if(lastWindowWidth !== newWindowWidth || lastWindowHeight !== newWindowHeight) { + lastWindowWidth = newWindowWidth; + lastWindowHeight = newWindowHeight; + eventData["eventTypeMask"] |= EVENT_RESIZE_WINDOW; + eventData["windowWidth"] = newWindowWidth; + eventData["windowHeight"] = newWindowHeight; + } + if(lastVisualViewportX !== newVisualViewportX || lastVisualViewportY !== newVisualViewportY + || lastVisualViewportW !== newVisualViewportW || lastVisualViewportH !== newVisualViewportH) { + lastVisualViewportX = newVisualViewportX; + lastVisualViewportY = newVisualViewportY; + lastVisualViewportW = newVisualViewportW; + lastVisualViewportH = newVisualViewportH; + eventData["eventTypeMask"] |= EVENT_RESIZE_VISUAL_VIEWPORT; + eventData["visualViewportX"] = newVisualViewportX; + eventData["visualViewportY"] = newVisualViewportY; + eventData["visualViewportW"] = newVisualViewportW; + eventData["visualViewportH"] = newVisualViewportH; + } + if(eventData["eventTypeMask"] !== 0) { + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_WINDOW_RESIZE, eventData); + } + } + + reportWindowSize(); + + parentElement.addEventListener("contextmenu", currentEventListeners.contextmenu = function(/** Event */ evt) { + evt.preventDefault(); + evt.stopPropagation(); + }); + + canvasElement.addEventListener("mousedown", /** @type {function(Event)} */ (currentEventListeners.mousedown = function(/** MouseEvent */ evt) { + evt.preventDefault(); + evt.stopPropagation(); + if(pointerLockSupported && pointerLockFlag && document.pointerLockElement !== canvasElement) { + mouseSetGrabbed(1); + return; + } + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_MOUSE, { + "eventType": EVENT_MOUSE_DOWN, + "posX": evt.offsetX, + "posY": evt.offsetY, + "button": evt.button + }); + })); + + canvasElement.addEventListener("mouseup", /** @type {function(Event)} */ (currentEventListeners.mouseup = function(/** MouseEvent */ evt) { + evt.preventDefault(); + evt.stopPropagation(); + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_MOUSE, { + "eventType": EVENT_MOUSE_UP, + "posX": evt.offsetX, + "posY": evt.offsetY, + "button": evt.button + }); + })); + + canvasElement.addEventListener("mousemove", /** @type {function(Event)} */ (currentEventListeners.mousemove = function(/** MouseEvent */ evt) { + evt.preventDefault(); + evt.stopPropagation(); + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_MOUSE, { + "eventType": EVENT_MOUSE_MOVE, + "posX": evt.offsetX, + "posY": evt.offsetY, + "movementX": evt.movementX, + "movementY": evt.movementY + }); + })); + + canvasElement.addEventListener("mouseenter", currentEventListeners.mouseenter = function(/** Event */ evt) { + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_MOUSE_ENTER, null); + }); + + canvasElement.addEventListener("mouseleave", currentEventListeners.mouseleave = function(/** Event */ evt) { + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_MOUSE_EXIT, null); + }); + + /** + * @param {TouchList} lstIn + * @return {Array} + */ + function createTouchList(lstIn) { + const len = lstIn.length; + const ret = new Array(len); + for(var i = 0; i < len; ++i) { + const itm = lstIn.item(i); + ret[i] = { + "pointX": itm.pageX - touchOffsetX, + "pointY": itm.pageY - touchOffsetY, + "radius": (itm.radiusX + itm.radiusY) * 0.5, + "force": itm.force, + "pointUID": itm.identifier + }; + } + return ret; + } + + canvasElement.addEventListener("touchstart", /** @type {function(Event)} */ (currentEventListeners.touchstart = function(/** TouchEvent */ evt) { + evt.preventDefault(); + evt.stopPropagation(); + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_TOUCH, { + "eventType": EVENT_TOUCH_START, + "changedTouches": createTouchList(evt.changedTouches), + "targetTouches": createTouchList(evt.targetTouches) + }); + touchCloseDeviceKeyboard(); + })); + + canvasElement.addEventListener("touchend", /** @type {function(Event)} */ (currentEventListeners.touchend = function(/** TouchEvent */ evt) { + evt.preventDefault(); + evt.stopPropagation(); + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_TOUCH, { + "eventType": EVENT_TOUCH_END, + "changedTouches": createTouchList(evt.changedTouches), + "targetTouches": createTouchList(evt.targetTouches) + }); + })); + + canvasElement.addEventListener("touchmove", /** @type {function(Event)} */ (currentEventListeners.touchmove = function(/** TouchEvent */ evt) { + evt.preventDefault(); + evt.stopPropagation(); + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_TOUCH, { + "eventType": EVENT_TOUCH_MOVE, + "changedTouches": createTouchList(evt.changedTouches), + "targetTouches": createTouchList(evt.targetTouches) + }); + })); + + canvasElement.addEventListener("touchcancel", /** @type {function(Event)} */ (currentEventListeners.touchcancel = function(/** TouchEvent */ evt) { + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_TOUCH, { + "eventType": EVENT_TOUCH_END, + "changedTouches": createTouchList(evt.changedTouches), + "targetTouches": createTouchList(evt.targetTouches) + }); + })); + + canvasElement.addEventListener("wheel", /** @type {function(Event)} */ (currentEventListeners.wheel = function(/** WheelEvent */ evt) { + evt.preventDefault(); + evt.stopPropagation(); + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_MOUSE, { + "eventType": EVENT_MOUSE_WHEEL, + "posX": evt.offsetX, + "posY": evt.offsetY, + "wheel": evt.deltaY + }); + })); + + window.addEventListener("blur", currentEventListeners.blur = function(/** Event */ evt) { + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_BLUR, null); + }); + + window.addEventListener("focus", currentEventListeners.focus = function(/** Event */ evt) { + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_FOCUS, null); + }); + + /** + * @param {number} evtType + * @param {KeyboardEvent} evt + * @return {Object} + */ + function createKeyEvent(evtType, evt) { + return { + "eventType": evtType, + "keyCode": (evt.code || null), + "keyName": (evt.key || null), + "legacyCode": (typeof evt.which === "number") ? evt.which : ((typeof evt.keyCode === "number") ? evt.keyCode : 0), + "location": evt.location, + "mods": ((evt.ctrlKey ? EVENT_KEY_MOD_CONTROL : 0) + | (evt.shiftKey ? EVENT_KEY_MOD_SHIFT : 0) + | (evt.metaKey ? EVENT_KEY_MOD_ALT : 0)) + }; + } + + window.addEventListener("keydown", /** @type {function(Event)} */ (currentEventListeners.keydown = function(/** KeyboardEvent */ evt) { + evt.preventDefault(); + evt.stopPropagation(); + if(evt.key === "F11" && !evt.repeat) { + toggleFullscreenImpl(); + return; + } + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_KEYBOARD, createKeyEvent(evt.repeat ? EVENT_KEY_REPEAT : EVENT_KEY_DOWN, evt)); + if(evt.timeStamp && evt.key !== "Unidentified") lastTouchKeyboardEvtA = evt.timeStamp; + })); + + window.addEventListener("keyup", /** @type {function(Event)} */ (currentEventListeners.keyup = function(/** KeyboardEvent */ evt) { + evt.preventDefault(); + evt.stopPropagation(); + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_KEYBOARD, createKeyEvent(EVENT_KEY_UP, evt)); + })); + + touchKeyboardOpenZone.addEventListener("touchstart", /** @type {function(Event)} */ (currentEventListeners.touchKeyboardOpenZone_touchstart = function(/** TouchEvent */ evt) { + evt.preventDefault(); + evt.stopPropagation(); + })); + + touchKeyboardOpenZone.addEventListener("touchend", /** @type {function(Event)} */ (currentEventListeners.touchKeyboardOpenZone_touchend = function(/** TouchEvent */ evt) { + evt.preventDefault(); + evt.stopPropagation(); + touchOpenDeviceKeyboard(); + })); + + touchKeyboardOpenZone.addEventListener("touchmove", /** @type {function(Event)} */ (currentEventListeners.touchKeyboardOpenZone_touchmove = function(/** TouchEvent */ evt) { + evt.preventDefault(); + evt.stopPropagation(); + })); + + pointerLockSupported = (typeof document.exitPointerLock === "function"); + + if(pointerLockSupported) { + document.addEventListener("pointerlockchange", /** @type {function(Event)} */ (currentEventListeners.pointerlock = function(evt) { + window.setTimeout(function() { + const grab = (document.pointerLockElement === canvasElement); + if(!grab) { + if(pointerLockFlag) { + mouseUngrabTimer = performance.now() | 0; + } + } + pointerLockFlag = grab; + }, 60); + })); + }else { + eagError("Pointer lock is not supported on this browser"); + } + + fullscreenSupported = (typeof document.exitFullscreen === "function"); + + if(fullscreenSupported) { + fullscreenQuery = window.matchMedia("(display-mode: fullscreen)"); + keyboardLockSupported = !!(navigator.keyboard && navigator.keyboard.lock); + if(keyboardLockSupported) { + fullscreenQuery.addEventListener("change", /** @type {function(Event)} */ (currentEventListeners.fullscreenChange = function(evt) { + if(!fullscreenQuery.matches) { + navigator.keyboard.unlock(); + lockKeys = false; + } + })); + } + }else { + eagError("Fullscreen is not supported on this browser"); + } + + if(typeof window.visualViewport !== "undefined") { + if(rootElement.tagName.toLowerCase() === "body") { + useVisualViewport = true; + }else { + const bodyTag = document.body; + if (Math.abs(bodyTag.clientWidth - parentElement.clientWidth) <= 10 + && Math.abs(bodyTag.clientHeight - parentElement.clientHeight) <= 10) { + useVisualViewport = true; + } + } + }else { + useVisualViewport = false; + } + + if(useVisualViewport) { + eagInfo("Note: Detected game is embedded in body, some screens may be resized to window.visualViewport instead for a better mobile experience"); + } + + function asyncRequestAnimationFrame() { + return new Promise(function(resolve) { + if(vsyncTimeout !== -1) { + throw new Error("Main thread is already waiting for VSync!"); + } + const hasCompleted = [ false ]; + window.requestAnimationFrame(function() { + if(!hasCompleted[0]) { + hasCompleted[0] = true; + if(vsyncTimeout !== -1) { + window.clearTimeout(vsyncTimeout); + vsyncTimeout = -1; + } + resolve(); + } + }); + vsyncTimeout = window.setTimeout(function() { + if(!hasCompleted[0]) { + hasCompleted[0] = true; + vsyncTimeout = -1; + resolve(); + } + }, 20); + }); + } + + try { + await asyncRequestAnimationFrame(); + vsyncSupport = true; + }catch(ex) { + vsyncSupport = false; + } + + if(!vsyncSupport) { + eagError("VSync is not supported on this browser"); + } + + gamepadSupported = (typeof navigator.getGamepads === "function"); + if(gamepadSupported) { + window.addEventListener("gamepadconnected", /** @type {function(Event)} */ (currentEventListeners.gamepadconnected = function(/** GamepadEvent */ evt) { + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_GAMEPAD, { + "eventType": EVENT_GAMEPAD_CONNECTED + }); + })); + window.addEventListener("gamepaddisconnected", /** @type {function(Event)} */ (currentEventListeners.gamepaddisconnected = function(/** GamepadEvent */ evt) { + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_GAMEPAD, { + "eventType": EVENT_GAMEPAD_DISCONNECTED, + "gamepad": evt.gamepad + }); + })); + }else { + eagError("Gamepad detected as unsupported!"); + } + + /** + * @return {boolean} + */ + inputImports["keyboardLayoutSupported"] = function() { + return !!(navigator.keyboard && navigator.keyboard.getLayoutMap); + }; + + /** + * @return {Promise} + */ + async function iterateKeyboardLayoutImpl() { + const ret = []; + try { + const layout = await navigator.keyboard.getLayoutMap(); + if(layout && layout["forEach"]) { + layout["forEach"](function(/** string */ k, /** string */ v) { + ret.push({ + "key": k, + "value": v + }); + }); + } + }catch(ex) { + } + return ret; + } + + inputImports["iterateKeyboardLayout"] = new WebAssembly.Suspending(iterateKeyboardLayoutImpl); + + /** + * @param {number} width + * @param {number} height + */ + inputImports["updateCanvasSize"] = function(width, height) { + if(canvasElement.width !== width) { + canvasElement.width = width; + } + if(canvasElement.height !== height) { + canvasElement.height = height; + } + }; + + function sleepPromise(ms) { + return new Promise(function(resolve) { + setTimeout(resolve, ms); + }); + } + + var manualSyncTimer = 0; + + /** + * @param {number} fpsLimit + * @return {Promise} + */ + function syncDelay(fpsLimit) { + if(fpsLimit > 0 && fpsLimit < 1000) { + if(manualSyncTimer === 0) { + manualSyncTimer = performance.now(); + }else { + const millis = performance.now(); + const frameMillis = (1000 / fpsLimit); + var frameTime = millis - manualSyncTimer; + if(frameTime > 2000 || frameTime < 0) { + frameTime = frameMillis; + manualSyncTimer = millis; + }else { + manualSyncTimer += frameMillis; + } + if(frameTime >= 0 && frameTime < frameMillis) { + return sleepPromise(frameMillis - frameTime); + } + } + }else { + manualSyncTimer = 0; + } + + return swapDelayImpl(); + } + + /** + * @param {number} fpsLimit + * @param {number} vsync + * @return {Promise} + */ + function updatePlatformAndSleepImpl(fpsLimit, vsync) { + reportWindowSize(); + if((typeof document.visibilityState !== "string") || (document.visibilityState === "visible")) { + if(vsyncSupport && vsync) { + manualSyncTimer = 0; + return asyncRequestAnimationFrame(); + }else { + if(fpsLimit <= 0) { + manualSyncTimer = 0; + return swapDelayImpl(); + }else { + return syncDelay(fpsLimit); + } + } + }else { + manualSyncTimer = 0; + return sleepPromise(50); + } + } + + inputImports["updatePlatformAndSleep"] = new WebAssembly.Suspending(updatePlatformAndSleepImpl); + + /** + * @return {boolean} + */ + inputImports["isVSyncSupported"] = function() { + return vsyncSupport; + } + + /** + * @param {number} grab + */ + function mouseSetGrabbed(grab) { + if(!pointerLockSupported) return; + pointerLockFlag = !!grab; + const t = performance.now() | 0; + mouseGrabTimer = t; + if(grab) { + try { + canvasElement.requestPointerLock(); + }catch(ex) { + } + if(mouseUngrabTimeout !== -1) window.clearTimeout(mouseUngrabTimeout); + mouseUngrabTimeout = -1; + if(t - mouseUngrabTimer < 3000) { + mouseUngrabTimeout = window.setTimeout(function() { + try { + canvasElement.requestPointerLock(); + }catch(ex) { + } + }, 3100 - (t - mouseUngrabTimer)); + } + }else { + if(mouseUngrabTimeout !== -1) window.clearTimeout(mouseUngrabTimeout); + mouseUngrabTimeout = -1; + try { + document.exitPointerLock(); + }catch(ex) { + } + } + }; + + inputImports["mouseSetGrabbed"] = mouseSetGrabbed; + + /** + * @return {boolean} + */ + inputImports["isMouseGrabSupported"] = function() { + return pointerLockSupported; + }; + + /** + * @return {boolean} + */ + inputImports["isMouseGrabbed"] = function() { + return pointerLockFlag; + }; + + /** + * @return {boolean} + */ + inputImports["isPointerLocked"] = function() { + return pointerLockSupported && document.pointerLockElement === canvasElement; + }; + + /** + * @return {boolean} + */ + inputImports["supportsFullscreen"] = function() { + return fullscreenSupported; + }; + + function toggleFullscreenImpl() { + if(!fullscreenSupported) return; + if(fullscreenQuery.matches) { + if(keyboardLockSupported) { + try { + navigator.keyboard.unlock(); + }catch(ex) { + } + lockKeys = false; + } + try { + document.exitFullscreen(); + }catch(ex) { + } + }else { + if(keyboardLockSupported) { + try { + navigator.keyboard.lock(); + }catch(ex) { + } + lockKeys = true; + } + try { + canvasElement.requestFullscreen(); + }catch(ex) { + } + } + } + + inputImports["toggleFullscreen"] = toggleFullscreenImpl; + + /** + * @return {boolean} + */ + inputImports["isFullscreen"] = function() { + return fullscreenSupported && fullscreenQuery.matches; + }; + + function touchOpenDeviceKeyboard() { + if(!touchIsDeviceKeyboardOpenMAYBE()) { + if(touchKeyboardField) { + touchKeyboardField.blur(); + touchKeyboardField.value = ""; + setTimeout(function() { + if(touchKeyboardField) { + if(touchKeyboardForm) { + touchKeyboardForm.removeChild(touchKeyboardField); + }else { + touchKeyboardField.remove(); + } + touchKeyboardField = null; + } + if(touchKeyboardForm) { + parentElement.removeChild(touchKeyboardForm); + touchKeyboardForm = null; + } + }, 10); + return; + } + if(touchKeyboardForm) { + parentElement.removeChild(touchKeyboardForm); + touchKeyboardForm = null; + } + touchKeyboardForm = document.createElement("form"); + touchKeyboardForm.setAttribute("autocomplete", "off"); + touchKeyboardForm.classList.add("_eaglercraftX_text_input_wrapper"); + touchKeyboardForm.style.position = "absolute"; + touchKeyboardForm.style.top = "0px"; + touchKeyboardForm.style.left = "0px"; + touchKeyboardForm.style.right = "0px"; + touchKeyboardForm.style.bottom = "0px"; + touchKeyboardForm.style.zIndex = "-100"; + touchKeyboardForm.style.margin = "0px"; + touchKeyboardForm.style.padding = "0px"; + touchKeyboardForm.style.border = "none"; + touchKeyboardForm.addEventListener("submit", function(/** Event */ evt) { + evt.preventDefault(); + evt.stopPropagation(); + const d = evt.timeStamp; + if(d) { + if(lastTouchKeyboardEvtA !== 0.0 && (d - lastTouchKeyboardEvtA) < 10.0) { + return; + } + if(lastTouchKeyboardEvtB !== 0.0 && (d - lastTouchKeyboardEvtB) < 10.0) { + return; + } + if(lastTouchKeyboardEvtC !== 0.0 && (d - lastTouchKeyboardEvtC) < 10.0) { + return; + } + } + if(!showniOSReturnTouchKeyboardWarning) { + eagInfo("Note: Generating return keystroke from submit event on form, this browser probably doesn't generate keydown/beforeinput/input when enter/return is pressed on the on-screen keyboard"); + showniOSReturnTouchKeyboardWarning = true; + } + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_TOUCH_KEYBOARD, { + "eventType": EVENT_TOUCH_KEYBOARD_ABSOLUTE, + "absoluteCode": 0x1C, // KEY_RETURN + "absoluteChar": 10 // '\n' + }); + }); + touchKeyboardField = document.createElement("input"); + touchKeyboardField.type = "password"; + touchKeyboardField.value = " "; + touchKeyboardField.classList.add("_eaglercraftX_text_input_element"); + touchKeyboardField.setAttribute("autocomplete", "off"); + touchKeyboardField.style.position = "absolute"; + touchKeyboardField.style.top = "0px"; + touchKeyboardField.style.left = "0px"; + touchKeyboardField.style.right = "0px"; + touchKeyboardField.style.bottom = "0px"; + touchKeyboardField.style.zIndex = "-100"; + touchKeyboardField.style.margin = "0px"; + touchKeyboardField.style.padding = "0px"; + touchKeyboardField.style.border = "none"; + touchKeyboardField.style.setProperty("-webkit-touch-callout", "default"); + touchKeyboardField.addEventListener("beforeinput", /** @type {function(Event)} */ (function(/** InputEvent */ evt) { + if(touchKeyboardField !== evt.target) return; + if(!shownTouchKeyboardEventWarning) { + eagInfo("Note: Caught beforeinput event from on-screen keyboard, browser probably does not generate global keydown/keyup events on text fields, or does not respond to cancelling keydown"); + shownTouchKeyboardEventWarning = true; + } + const d = evt.timeStamp; + if(d) { + if(lastTouchKeyboardEvtA !== 0.0 && (d - lastTouchKeyboardEvtA) < 10.0) { + return; + } + lastTouchKeyboardEvtB = d; + } + evt.preventDefault(); + evt.stopPropagation(); + switch(evt.inputType) { + case "insertParagraph": + case "insertLineBreak": + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_TOUCH_KEYBOARD, { + "eventType": EVENT_TOUCH_KEYBOARD_ABSOLUTE, + "absoluteCode": 0x1C, // KEY_RETURN + "absoluteChar": 10 // '\n' + }); + break; + case "deleteWordBackward": + case "deleteSoftLineBackward": + case "deleteHardLineBackward": + case "deleteEntireSoftLine": + case "deleteContentBackward": + case "deleteContent": + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_TOUCH_KEYBOARD, { + "eventType": EVENT_TOUCH_KEYBOARD_ABSOLUTE, + "absoluteCode": 0x0E, // KEY_BACK + "absoluteChar": 0 + }); + break; + case "deleteWordForward": + case "deleteSoftLineForward": + case "deleteHardLineForward": + case "deleteContentForward": + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_TOUCH_KEYBOARD, { + "eventType": EVENT_TOUCH_KEYBOARD_ABSOLUTE, + "absoluteCode": 0xD3, // KEY_DELETE + "absoluteChar": 0 + }); + break; + case "insertText": + case "insertCompositionText": + case "insertReplacementText": { + const txt = evt.data; + if(txt && txt.length > 0) { + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_TOUCH_KEYBOARD, { + "eventType": EVENT_TOUCH_KEYBOARD_CODEPOINTS, + "codepoints": txt + }); + } + break; + } + case "insertFromPaste": + case "insertFromPasteAsQuotation": + case "insertFromDrop": + case "insertFromYank": + case "insertLink": { + const txt = evt.data; + if(txt && txt.length > 0) { + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_TOUCH_PASTE, txt); + } + break; + } + case "historyUndo": + case "historyRedo": + case "deleteByDrag": + case "deleteByCut": + break; + default: + eagInfo("Ignoring InputEvent.inputType \"{}\" from on-screen keyboard", evt.inputType); + break; + } + })); + touchKeyboardField.addEventListener("input", function(/** Event */ evt) { + if(touchKeyboardField !== evt.target) return; + if(!shownLegacyTouchKeyboardWarning) { + eagInfo("Note: Caught legacy input events from on-screen keyboard, browser could be outdated and doesn't support beforeinput event, or does not respond to cancelling beforeinput"); + shownLegacyTouchKeyboardWarning = true; + } + const d = evt.timeStamp; + if(d) { + if(lastTouchKeyboardEvtA !== 0.0 && (d - lastTouchKeyboardEvtA) < 10.0) { + return; + } + if(lastTouchKeyboardEvtB !== 0.0 && (d - lastTouchKeyboardEvtB) < 10.0) { + return; + } + lastTouchKeyboardEvtC = d; + } + var val = touchKeyboardField.value; + var l = val.length; + if(l === 0) { + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_TOUCH_KEYBOARD, { + "eventType": EVENT_TOUCH_KEYBOARD_ABSOLUTE, + "absoluteCode": 0x0E, // KEY_BACK + "absoluteChar": 0 + }); + }else if(l === 1) { + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_TOUCH_KEYBOARD, { + "eventType": EVENT_TOUCH_KEYBOARD_CODEPOINTS, + "codepoints": val + }); + }else { + val = val.trim(); + l = val.length; + if(l === 0) { + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_TOUCH_KEYBOARD, { + "eventType": EVENT_TOUCH_KEYBOARD_ABSOLUTE, + "absoluteCode": 0x39, // KEY_SPACE + "absoluteChar": 32 + }); + }else { + pushEvent(EVENT_TYPE_INPUT, EVENT_INPUT_TOUCH_KEYBOARD, { + "eventType": EVENT_TOUCH_KEYBOARD_CODEPOINTS, + "codepoints": val.charAt(l - 1) + }); + } + } + touchKeyboardField.value = " "; + touchKeyboardField.setSelectionRange(1, 1); + }); + touchKeyboardField.addEventListener("focus", function(/** Event */ evt) { + if(touchKeyboardField !== evt.target) return; + touchKeyboardField.value = " "; + touchKeyboardField.setSelectionRange(1, 1); + }); + touchKeyboardField.addEventListener("select", function(/** Event */ evt) { + if(touchKeyboardField !== evt.target) return; + evt.preventDefault(); + evt.stopPropagation(); + touchKeyboardField.value = " "; + touchKeyboardField.setSelectionRange(1, 1); + }); + touchKeyboardForm.appendChild(touchKeyboardField); + parentElement.appendChild(touchKeyboardForm); + touchKeyboardField.value = " "; + touchKeyboardField.focus(); + touchKeyboardField.select(); + touchKeyboardField.setSelectionRange(1, 1); + }else { + touchCloseDeviceKeyboard(); + } + } + + /** + * @param {number} x + * @param {number} y + * @param {number} w + * @param {number} h + */ + inputImports["touchSetOpenKeyboardZone"] = function(x, y, w, h) { + if(w !== 0 && h !== 0) { + touchKeyboardOpenZone.style.display = "block"; + touchKeyboardOpenZone.style.top = "" + ((lastWindowHeight - y - h) / lastWindowDPI) + "px"; + touchKeyboardOpenZone.style.left = "" + (x / lastWindowDPI) + "px"; + touchKeyboardOpenZone.style.width = "" + (w / lastWindowDPI) + "px"; + touchKeyboardOpenZone.style.height = "" + (h / lastWindowDPI) + "px"; + }else { + touchKeyboardOpenZone.style.display = "none"; + touchKeyboardOpenZone.style.top = "0px"; + touchKeyboardOpenZone.style.left = "0px"; + touchKeyboardOpenZone.style.width = "0px"; + touchKeyboardOpenZone.style.height = "0px"; + } + }; + + function touchCloseDeviceKeyboard() { + if(touchKeyboardField) { + touchKeyboardField.blur(); + touchKeyboardField.value = ""; + const el = touchKeyboardField; + const el2 = touchKeyboardForm; + window.setTimeout(function() { + if(el2 !== null) { + el2.removeChild(el); + el2.remove(); + }else { + el.remove(); + } + }, 10); + touchKeyboardField = null; + touchKeyboardForm = null; + }else if(touchKeyboardForm) { + if(parentElement) { + parentElement.removeChild(touchKeyboardForm); + }else { + touchKeyboardForm.remove(); + } + touchKeyboardForm = null; + } + } + + inputImports["touchCloseDeviceKeyboard"] = touchCloseDeviceKeyboard; + + function touchIsDeviceKeyboardOpenMAYBE() { + return !!touchKeyboardField && document.activeElement === touchKeyboardField; + } + + /** + * @return {boolean} + */ + inputImports["touchIsDeviceKeyboardOpenMAYBE"] = touchIsDeviceKeyboardOpenMAYBE; + + /** + * @return {boolean} + */ + inputImports["gamepadSupported"] = function() { + return gamepadSupported; + }; + + /** + * @return {boolean} + */ + inputImports["isVisualViewport"] = function() { + return useVisualViewport; + }; + + removeEventHandlers = function() { + try { + if(currentEventListeners.contextmenu) { + parentElement.removeEventListener("contextmenu", /** @type {function(Event)} */ (currentEventListeners.contextmenu)); + currentEventListeners.contextmenu = null; + } + if(currentEventListeners.mousedown) { + canvasElement.removeEventListener("mousedown", /** @type {function(Event)} */ (currentEventListeners.mousedown)); + currentEventListeners.mousedown = null; + } + if(currentEventListeners.mouseup) { + canvasElement.removeEventListener("mouseup", /** @type {function(Event)} */ (currentEventListeners.mouseup)); + currentEventListeners.mouseup = null; + } + if(currentEventListeners.mousemove) { + canvasElement.removeEventListener("mousemove", /** @type {function(Event)} */ (currentEventListeners.mousemove)); + currentEventListeners.mousemove = null; + } + if(currentEventListeners.mouseenter) { + canvasElement.removeEventListener("mouseenter", /** @type {function(Event)} */ (currentEventListeners.mouseenter)); + currentEventListeners.mouseenter = null; + } + if(currentEventListeners.mouseleave) { + canvasElement.removeEventListener("mouseleave", /** @type {function(Event)} */ (currentEventListeners.mouseleave)); + currentEventListeners.mouseleave = null; + } + if(currentEventListeners.touchstart) { + canvasElement.removeEventListener("touchstart", /** @type {function(Event)} */ (currentEventListeners.touchstart)); + currentEventListeners.touchstart = null; + } + if(currentEventListeners.touchend) { + canvasElement.removeEventListener("touchend", /** @type {function(Event)} */ (currentEventListeners.touchend)); + currentEventListeners.touchend = null; + } + if(currentEventListeners.touchmove) { + canvasElement.removeEventListener("touchmove", /** @type {function(Event)} */ (currentEventListeners.touchmove)); + currentEventListeners.touchmove = null; + } + if(currentEventListeners.touchcancel) { + canvasElement.removeEventListener("touchcancel", /** @type {function(Event)} */ (currentEventListeners.touchcancel)); + currentEventListeners.touchcancel = null; + } + if(currentEventListeners.gamepadconnected) { + window.removeEventListener("gamepadconnected", /** @type {function(Event)} */ (currentEventListeners.gamepadconnected)); + currentEventListeners.gamepadconnected = null; + } + if(currentEventListeners.gamepaddisconnected) { + window.removeEventListener("gamepaddisconnected", /** @type {function(Event)} */ (currentEventListeners.gamepaddisconnected)); + currentEventListeners.gamepaddisconnected = null; + } + if(currentEventListeners.keydown) { + window.removeEventListener("keydown", /** @type {function(Event)} */ (currentEventListeners.keydown)); + currentEventListeners.keydown = null; + } + if(currentEventListeners.keyup) { + window.removeEventListener("keyup", /** @type {function(Event)} */ (currentEventListeners.keyup)); + currentEventListeners.keyup = null; + } + if(currentEventListeners.touchKeyboardOpenZone_touchstart) { + touchKeyboardOpenZone.removeEventListener("touchstart", /** @type {function(Event)} */ (currentEventListeners.touchKeyboardOpenZone_touchstart)); + currentEventListeners.touchKeyboardOpenZone_touchstart = null; + } + if(currentEventListeners.touchKeyboardOpenZone_touchend) { + touchKeyboardOpenZone.removeEventListener("touchend", /** @type {function(Event)} */ (currentEventListeners.touchKeyboardOpenZone_touchend)); + currentEventListeners.touchKeyboardOpenZone_touchend = null; + } + if(currentEventListeners.touchKeyboardOpenZone_touchmove) { + touchKeyboardOpenZone.removeEventListener("touchmove", /** @type {function(Event)} */ (currentEventListeners.touchKeyboardOpenZone_touchmove)); + currentEventListeners.touchKeyboardOpenZone_touchmove = null; + } + if(currentEventListeners.wheel) { + canvasElement.removeEventListener("wheel", /** @type {function(Event)} */ (currentEventListeners.wheel)); + currentEventListeners.wheel = null; + } + if(currentEventListeners.focus) { + window.removeEventListener("focus", /** @type {function(Event)} */ (currentEventListeners.focus)); + currentEventListeners.focus = null; + } + if(currentEventListeners.blur) { + window.removeEventListener("blur", /** @type {function(Event)} */ (currentEventListeners.blur)); + currentEventListeners.blur = null; + } + if(currentEventListeners.pointerlock) { + document.removeEventListener("pointerlockchange", /** @type {function(Event)} */ (currentEventListeners.pointerlock)); + currentEventListeners.pointerlock = null; + } + if(currentEventListeners.fullscreenChange) { + fullscreenQuery.removeEventListener("change", /** @type {function(Event)} */ (currentEventListeners.fullscreenChange)); + currentEventListeners.fullscreenChange = null; + } + }catch(ex) { + eagError("Failed to remove event listeners! {}", ex); + } + removeEventHandlers = null; + }; +} + +function initNoPlatformInput(inputImports) { + setUnsupportedFunc(inputImports, platfInputName, "keyboardLayoutSupported"); + setUnsupportedFunc(inputImports, platfInputName, "iterateKeyboardLayout"); + setUnsupportedFunc(inputImports, platfInputName, "updateCanvasSize"); + setUnsupportedFunc(inputImports, platfInputName, "updatePlatformAndSleep"); + setUnsupportedFunc(inputImports, platfInputName, "isVSyncSupported"); + setUnsupportedFunc(inputImports, platfInputName, "mouseSetGrabbed"); + setUnsupportedFunc(inputImports, platfInputName, "isMouseGrabSupported"); + setUnsupportedFunc(inputImports, platfInputName, "isMouseGrabbed"); + setUnsupportedFunc(inputImports, platfInputName, "isPointerLocked"); + setUnsupportedFunc(inputImports, platfInputName, "supportsFullscreen"); + setUnsupportedFunc(inputImports, platfInputName, "toggleFullscreen"); + setUnsupportedFunc(inputImports, platfInputName, "isFullscreen"); + setUnsupportedFunc(inputImports, platfInputName, "touchSetOpenKeyboardZone"); + setUnsupportedFunc(inputImports, platfInputName, "touchCloseDeviceKeyboard"); + setUnsupportedFunc(inputImports, platfInputName, "touchIsDeviceKeyboardOpenMAYBE"); + setUnsupportedFunc(inputImports, platfInputName, "gamepadSupported"); + setUnsupportedFunc(inputImports, platfInputName, "isVisualViewport"); +} diff --git a/sources/wasm-gc-teavm/js/platformNetworking.js b/sources/wasm-gc-teavm/js/platformNetworking.js new file mode 100644 index 0000000..f85fd1a --- /dev/null +++ b/sources/wasm-gc-teavm/js/platformNetworking.js @@ -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; + }; + +})(); diff --git a/sources/wasm-gc-teavm/js/platformOpenGL.js b/sources/wasm-gc-teavm/js/platformOpenGL.js new file mode 100644 index 0000000..6b0cec8 --- /dev/null +++ b/sources/wasm-gc-teavm/js/platformOpenGL.js @@ -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"); +} diff --git a/sources/wasm-gc-teavm/js/platformRuntime.js b/sources/wasm-gc-teavm/js/platformRuntime.js new file mode 100644 index 0000000..2bbe912 --- /dev/null +++ b/sources/wasm-gc-teavm/js/platformRuntime.js @@ -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; +}; diff --git a/sources/wasm-gc-teavm/js/platformScreenRecord.js b/sources/wasm-gc-teavm/js/platformScreenRecord.js new file mode 100644 index 0000000..8d2a731 --- /dev/null +++ b/sources/wasm-gc-teavm/js/platformScreenRecord.js @@ -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} + */ +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} + */ +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"); +} diff --git a/sources/wasm-gc-teavm/js/platformVoiceClient.js b/sources/wasm-gc-teavm/js/platformVoiceClient.js new file mode 100644 index 0000000..f618080 --- /dev/null +++ b/sources/wasm-gc-teavm/js/platformVoiceClient.js @@ -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"); +} diff --git a/sources/wasm-gc-teavm/js/platformWebRTC.js b/sources/wasm-gc-teavm/js/platformWebRTC.js new file mode 100644 index 0000000..af15a37 --- /dev/null +++ b/sources/wasm-gc-teavm/js/platformWebRTC.js @@ -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} 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} */ (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} */ + iceServers: [], + /** @type {!Map} */ + peerList: new Map(), + /** @type {!Map} */ + 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} 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} */ (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"); +} diff --git a/sources/wasm-gc-teavm/js/platformWebView.js b/sources/wasm-gc-teavm/js/platformWebView.js new file mode 100644 index 0000000..516af1e --- /dev/null +++ b/sources/wasm-gc-teavm/js/platformWebView.js @@ -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} 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 = "Impossible"; + }else if(!cspSupport || !enableCSP) { + strictCSPMarkup = "Unsupported"; + }else if(options["strictCSPEnable"]) { + strictCSPMarkup = "Enabled"; + }else { + strictCSPMarkup = "Disabled"; + } + var messageAPIMarkup; + if(options["serverMessageAPIEnabled"]) { + messageAPIMarkup = "Enabled"; + }else { + messageAPIMarkup = "Disabled"; + } + currentAllowJavaScript.innerHTML = + "
" + + "

 Allow JavaScript

" + + "

" + + "

Strict CSP: " + strictCSPMarkup + " | " + + "Message API: " + messageAPIMarkup + "

" + + "

Remember my choice

" + + "

 " + + "

"; + 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 = + "

" + + " Content Blocked

" + + "

You chose to block JavaScript execution for this embed

" + + "

"; + 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 = + "

" + + " IFrame Safety Error

" + + "

The content cannot be displayed safely!

" + + "

Check console for more details

"; + } + + /** + * @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"); +} diff --git a/sources/wasm-gc-teavm/js/serverPlatformSingleplayer.js b/sources/wasm-gc-teavm/js/serverPlatformSingleplayer.js new file mode 100644 index 0000000..6423619 --- /dev/null +++ b/sources/wasm-gc-teavm/js/serverPlatformSingleplayer.js @@ -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"); +} diff --git a/sources/wasm-gc-teavm/js/teavm_runtime.js b/sources/wasm-gc-teavm/js/teavm_runtime.js new file mode 100644 index 0000000..3e40dd6 --- /dev/null +++ b/sources/wasm-gc-teavm/js/teavm_runtime.js @@ -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} + */ +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 }; +})(); + +})();