├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── modules ├── common │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ ├── net │ │ └── weavemc │ │ │ └── loader │ │ │ └── api │ │ │ └── util │ │ │ └── InsnDsl.kt │ │ └── org │ │ └── polyfrost │ │ └── spice │ │ └── util │ │ ├── ClassUtil.kt │ │ └── SpiceClassWriter.kt ├── core │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── org │ │ └── polyfrost │ │ └── spice │ │ ├── Constants.kt │ │ ├── ModManifest.kt │ │ ├── Options.kt │ │ ├── Spice.kt │ │ ├── api │ │ └── Mouse.kt │ │ ├── debug │ │ ├── DebugHelper.kt │ │ └── DebugSection.kt │ │ ├── fixes │ │ └── EarlyCrashFix.kt │ │ ├── patcher │ │ ├── Cache.kt │ │ ├── LunarTransformer.kt │ │ ├── OptifineTransformer.kt │ │ ├── fixes │ │ │ ├── OpenAlFixes.kt │ │ │ └── OpenGlFixes.kt │ │ ├── lwjgl │ │ │ ├── EssentialGlobalMouseOverrideTransformer.kt │ │ │ ├── LibraryTransformer.kt │ │ │ ├── LwjglProvider.kt │ │ │ ├── LwjglTransformer.kt │ │ │ └── MemoryStackTransformer.kt │ │ └── util │ │ │ └── AudioHelper.kt │ │ ├── platform │ │ ├── Bootstrap.kt │ │ ├── TransformerBootstrap.kt │ │ └── api │ │ │ ├── IClassTransformer.kt │ │ │ ├── Platform.kt │ │ │ └── Transformer.kt │ │ └── util │ │ ├── OptifineUtil.kt │ │ ├── SystemUtil.kt │ │ └── UrlConnection.kt ├── lwjgl │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── org │ │ ├── lwjgl │ │ ├── LWJGLException.kt │ │ ├── Sys.kt │ │ ├── input │ │ │ ├── Keyboard.kt │ │ │ └── Mouse.kt │ │ └── opengl │ │ │ ├── ContextAttribs.kt │ │ │ ├── ContextCapabilities.kt │ │ │ ├── Display.kt │ │ │ ├── DisplayMode.kt │ │ │ ├── Drawable.kt │ │ │ ├── GLContext.kt │ │ │ ├── PixelFormat.kt │ │ │ └── SharedDrawable.kt │ │ ├── lwjglx │ │ ├── input │ │ │ └── RawInput.kt │ │ └── system │ │ │ └── Monitor.kt │ │ └── polyfrost │ │ └── lwjgl │ │ ├── api │ │ ├── input │ │ │ ├── Keyboard.kt │ │ │ └── Mouse.kt │ │ └── opengl │ │ │ ├── Context.kt │ │ │ └── Display.kt │ │ ├── impl │ │ ├── display │ │ │ └── OpenGlDisplay.kt │ │ └── input │ │ │ ├── Keyboard.kt │ │ │ └── Mouse.kt │ │ ├── platform │ │ └── common │ │ │ ├── Types.kt │ │ │ ├── Util.kt │ │ │ └── opengl │ │ │ ├── GlfwContext.kt │ │ │ └── OpenGlDrawable.kt │ │ └── util │ │ ├── Boolean.kt │ │ └── String.kt └── root.gradle.kts ├── settings.gradle.kts └── versions ├── 1.8.9-fabric └── src │ └── main │ ├── kotlin │ └── org │ │ └── polyfrost │ │ └── spice │ │ └── platform │ │ └── impl │ │ └── fabric │ │ ├── FabricInitializer.kt │ │ ├── FabricPlatform.kt │ │ └── asm │ │ ├── MixinGenerator.kt │ │ └── TransformerPlugin.kt │ └── resources │ ├── fabric.mod.json │ ├── transformer.mixins.json │ └── transformer.mixins.refmap.json ├── build.gradle.kts ├── mainProject ├── mappings ├── fabric-forge-1.8.9.txt └── forge-1.12.2-1.8.9.txt ├── preprocessor.gradle.kts └── src └── main ├── java └── org │ └── polyfrost │ └── spice │ └── mixin │ ├── common │ ├── GuiOverlayDebugMixin.java │ └── MinecraftMixin.java │ └── lwjgl3 │ ├── GuiControlsMixin.java │ ├── MinecraftMixin.java │ └── compat │ └── EssentialGlobalMouseOverrideMixin.java ├── kotlin └── org │ └── polyfrost │ └── spice │ ├── platform │ └── impl │ │ └── forge │ │ ├── ForgeInitializer.kt │ │ ├── ForgePlatform.kt │ │ ├── asm │ │ ├── ClassTransformer.kt │ │ ├── LwjglAccessTracer.kt │ │ └── SpiceTweaker.kt │ │ └── util │ │ ├── LaunchWrapperLogger.kt │ │ └── Relaunch.kt │ └── util │ └── Classpath.kt └── resources ├── mcmod.info └── spice.mixins.json /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Automatically build the project and run any configured tests for every push 2 | # and submitted pull request. This can help catch issues that only occur on 3 | # certain Java versions, and provides a first line of defence against bad commits. 4 | 5 | name: Build 6 | 7 | on: [ push, pull_request, workflow_dispatch ] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | java: [ 14 | "17" # Latest version 15 | ] 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: gradle/wrapper-validation-action@v1 20 | - uses: actions/setup-java@v3 21 | with: 22 | distribution: "temurin" 23 | java-version: ${{ matrix.java }} 24 | 25 | - name: Grant execute permission 26 | run: chmod +x ./gradlew 27 | - name: Sync Gradle 28 | run: ./gradlew 29 | - name: Build with Gradle 30 | run: ./gradlew build 31 | - name: Upload Artifact 32 | uses: actions/upload-artifact@v4.0.0 33 | with: 34 | name: compiled 35 | path: build/libs/*.jar 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # gradle 2 | .gradle/ 3 | build/ 4 | out/ 5 | classes/ 6 | 7 | # eclipse 8 | *.launch 9 | 10 | # idea 11 | .idea/ 12 | *.iml 13 | *.ipr 14 | *.iws 15 | 16 | # vscode 17 | .settings/ 18 | .vscode/ 19 | bin/ 20 | .classpath 21 | .project 22 | 23 | # fabric 24 | run/ 25 | 26 | # macos 27 | *.DS_Store 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spice -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version libs.versions.kotlin.get() apply false 3 | kotlin("plugin.serialization") version libs.versions.kotlin.get() apply false 4 | alias(libs.plugins.pgt.defaults.repo) apply false 5 | idea 6 | } 7 | 8 | val modVer = project.properties["version"] 9 | 10 | version = "$modVer" 11 | group = "org.polyfrost" 12 | 13 | subprojects { 14 | version = rootProject.version 15 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2G 2 | org.gradle.parallel=true 3 | version=1.0.0 4 | 5 | polyfrost.defaults.loom=3 -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | kotlin = "1.9.10" 3 | kotlinx-coroutines = "1.8.1" 4 | kotlinx-serialization = "1.6.2" 5 | lwjgl = "3.3.3" 6 | asm = "5.0.3" 7 | mixins = "0.8.5-SNAPSHOT" # todo i dont think this is needed? 8 | mixinsForge = "0.7.11-SNAPSHOT" 9 | 10 | pgt = "0.6.6" 11 | annotations = "24.1.0" 12 | 13 | [libraries] 14 | asmtree = { module = "org.ow2.asm:asm-tree", version.ref = "asm" } 15 | 16 | lwjgl = { module = "org.lwjgl:lwjgl", version.ref = "lwjgl" } 17 | 18 | lwjglGlfw = { module = "org.lwjgl:lwjgl-glfw", version.ref = "lwjgl" } 19 | lwjglOpenal = { module = "org.lwjgl:lwjgl-openal", version.ref = "lwjgl" } 20 | lwjglOpengl = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl" } 21 | 22 | mixins = { module = "org.spongepowered:mixin", version.ref = "mixins" } 23 | mixinsForge = { module = "org.spongepowered:mixin", version.ref = "mixinsForge" } 24 | 25 | kotlin-common = { module = "org.jetbrains.kotlin:kotlin-stdlib-common", version.ref = "kotlin" } 26 | kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } 27 | kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } 28 | 29 | kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } 30 | kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } 31 | 32 | annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" } 33 | 34 | [bundles] 35 | lwjgl = ["lwjgl", "lwjglGlfw", "lwjglOpenal", "lwjglOpengl"] 36 | kotlin = ["kotlin-reflect", "kotlin-stdlib", "kotlin-common"] 37 | 38 | [plugins] 39 | pgt-main = { id = "org.polyfrost.multi-version", version.ref = "pgt" } 40 | pgt-root = { id = "org.polyfrost.multi-version.root", version.ref = "pgt" } 41 | pgt-defaults-repo = { id = "org.polyfrost.defaults.repo", version.ref = "pgt" } 42 | pgt-defaults-java = { id = "org.polyfrost.defaults.java", version.ref = "pgt" } 43 | pgt-defaults-loom = { id = "org.polyfrost.defaults.loom", version.ref = "pgt" } 44 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Polyfrost/Spice/4fc723e1d72860b750055ec3bea0ce6a4df747ac/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /modules/common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation(libs.asmtree) 3 | } 4 | -------------------------------------------------------------------------------- /modules/common/src/main/kotlin/net/weavemc/loader/api/util/InsnDsl.kt: -------------------------------------------------------------------------------- 1 | package net.weavemc.loader.api.util 2 | 3 | import org.objectweb.asm.Handle 4 | import org.objectweb.asm.MethodVisitor 5 | import org.objectweb.asm.Opcodes 6 | import org.objectweb.asm.tree.* 7 | 8 | @Suppress( 9 | "PropertyName", 10 | "unused", 11 | "FunctionName", 12 | "SpellCheckingInspection", 13 | "NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING", 14 | "NO_EXPLICIT_RETURN_TYPE_IN_API_MODE_WARNING", 15 | "MemberVisibilityCanBePrivate", 16 | ) 17 | sealed class InsnBuilder { 18 | 19 | abstract operator fun AbstractInsnNode.unaryPlus() 20 | abstract operator fun InsnList.unaryPlus() 21 | 22 | fun getstatic(owner: String, name: String, desc: String) = +FieldInsnNode(Opcodes.GETSTATIC, owner, name, desc) 23 | fun putstatic(owner: String, name: String, desc: String) = +FieldInsnNode(Opcodes.PUTSTATIC, owner, name, desc) 24 | fun getfield(owner: String, name: String, desc: String) = +FieldInsnNode(Opcodes.GETFIELD, owner, name, desc) 25 | fun putfield(owner: String, name: String, desc: String) = +FieldInsnNode(Opcodes.PUTFIELD, owner, name, desc) 26 | 27 | fun iinc(`var`: Int, incr: Int) = +IincInsnNode(`var`, incr) 28 | 29 | @get:JvmName("nop") 30 | val nop get() = +InsnNode(Opcodes.NOP) 31 | @get:JvmName("aconst_null") 32 | val aconst_null get() = +InsnNode(Opcodes.ACONST_NULL) 33 | @get:JvmName("iconst_m1") 34 | val iconst_m1 get() = +InsnNode(Opcodes.ICONST_M1) 35 | @get:JvmName("iconst_0") 36 | val iconst_0 get() = +InsnNode(Opcodes.ICONST_0) 37 | @get:JvmName("iconst_1") 38 | val iconst_1 get() = +InsnNode(Opcodes.ICONST_1) 39 | @get:JvmName("iconst_2") 40 | val iconst_2 get() = +InsnNode(Opcodes.ICONST_2) 41 | @get:JvmName("iconst_3") 42 | val iconst_3 get() = +InsnNode(Opcodes.ICONST_3) 43 | @get:JvmName("iconst_4") 44 | val iconst_4 get() = +InsnNode(Opcodes.ICONST_4) 45 | @get:JvmName("iconst_5") 46 | val iconst_5 get() = +InsnNode(Opcodes.ICONST_5) 47 | @get:JvmName("lconst_0") 48 | val lconst_0 get() = +InsnNode(Opcodes.LCONST_0) 49 | @get:JvmName("lconst_1") 50 | val lconst_1 get() = +InsnNode(Opcodes.LCONST_1) 51 | @get:JvmName("fconst_0") 52 | val fconst_0 get() = +InsnNode(Opcodes.FCONST_0) 53 | @get:JvmName("fconst_1") 54 | val fconst_1 get() = +InsnNode(Opcodes.FCONST_1) 55 | @get:JvmName("fconst_2") 56 | val fconst_2 get() = +InsnNode(Opcodes.FCONST_2) 57 | @get:JvmName("dconst_0") 58 | val dconst_0 get() = +InsnNode(Opcodes.DCONST_0) 59 | @get:JvmName("dconst_1") 60 | val dconst_1 get() = +InsnNode(Opcodes.DCONST_1) 61 | @get:JvmName("iaload") 62 | val iaload get() = +InsnNode(Opcodes.IALOAD) 63 | @get:JvmName("laload") 64 | val laload get() = +InsnNode(Opcodes.LALOAD) 65 | @get:JvmName("faload") 66 | val faload get() = +InsnNode(Opcodes.FALOAD) 67 | @get:JvmName("daload") 68 | val daload get() = +InsnNode(Opcodes.DALOAD) 69 | @get:JvmName("aaload") 70 | val aaload get() = +InsnNode(Opcodes.AALOAD) 71 | @get:JvmName("baload") 72 | val baload get() = +InsnNode(Opcodes.BALOAD) 73 | @get:JvmName("caload") 74 | val caload get() = +InsnNode(Opcodes.CALOAD) 75 | @get:JvmName("saload") 76 | val saload get() = +InsnNode(Opcodes.SALOAD) 77 | @get:JvmName("iastore") 78 | val iastore get() = +InsnNode(Opcodes.IASTORE) 79 | @get:JvmName("lastore") 80 | val lastore get() = +InsnNode(Opcodes.LASTORE) 81 | @get:JvmName("fastore") 82 | val fastore get() = +InsnNode(Opcodes.FASTORE) 83 | @get:JvmName("dastore") 84 | val dastore get() = +InsnNode(Opcodes.DASTORE) 85 | @get:JvmName("aastore") 86 | val aastore get() = +InsnNode(Opcodes.AASTORE) 87 | @get:JvmName("bastore") 88 | val bastore get() = +InsnNode(Opcodes.BASTORE) 89 | @get:JvmName("castore") 90 | val castore get() = +InsnNode(Opcodes.CASTORE) 91 | @get:JvmName("sastore") 92 | val sastore get() = +InsnNode(Opcodes.SASTORE) 93 | @get:JvmName("pop") 94 | val pop get() = +InsnNode(Opcodes.POP) 95 | @get:JvmName("pop2") 96 | val pop2 get() = +InsnNode(Opcodes.POP2) 97 | @get:JvmName("dup") 98 | val dup get() = +InsnNode(Opcodes.DUP) 99 | @get:JvmName("dup_x1") 100 | val dup_x1 get() = +InsnNode(Opcodes.DUP_X1) 101 | @get:JvmName("dup_x2") 102 | val dup_x2 get() = +InsnNode(Opcodes.DUP_X2) 103 | @get:JvmName("dup2") 104 | val dup2 get() = +InsnNode(Opcodes.DUP2) 105 | @get:JvmName("dup2_x1") 106 | val dup2_x1 get() = +InsnNode(Opcodes.DUP2_X1) 107 | @get:JvmName("dup2_x2") 108 | val dup2_x2 get() = +InsnNode(Opcodes.DUP2_X2) 109 | @get:JvmName("swap") 110 | val swap get() = +InsnNode(Opcodes.SWAP) 111 | @get:JvmName("iadd") 112 | val iadd get() = +InsnNode(Opcodes.IADD) 113 | @get:JvmName("ladd") 114 | val ladd get() = +InsnNode(Opcodes.LADD) 115 | @get:JvmName("fadd") 116 | val fadd get() = +InsnNode(Opcodes.FADD) 117 | @get:JvmName("dadd") 118 | val dadd get() = +InsnNode(Opcodes.DADD) 119 | @get:JvmName("isub") 120 | val isub get() = +InsnNode(Opcodes.ISUB) 121 | @get:JvmName("lsub") 122 | val lsub get() = +InsnNode(Opcodes.LSUB) 123 | @get:JvmName("fsub") 124 | val fsub get() = +InsnNode(Opcodes.FSUB) 125 | @get:JvmName("dsub") 126 | val dsub get() = +InsnNode(Opcodes.DSUB) 127 | @get:JvmName("imul") 128 | val imul get() = +InsnNode(Opcodes.IMUL) 129 | @get:JvmName("lmul") 130 | val lmul get() = +InsnNode(Opcodes.LMUL) 131 | @get:JvmName("fmul") 132 | val fmul get() = +InsnNode(Opcodes.FMUL) 133 | @get:JvmName("dmul") 134 | val dmul get() = +InsnNode(Opcodes.DMUL) 135 | @get:JvmName("idiv") 136 | val idiv get() = +InsnNode(Opcodes.IDIV) 137 | @get:JvmName("ldiv") 138 | val ldiv get() = +InsnNode(Opcodes.LDIV) 139 | @get:JvmName("fdiv") 140 | val fdiv get() = +InsnNode(Opcodes.FDIV) 141 | @get:JvmName("ddiv") 142 | val ddiv get() = +InsnNode(Opcodes.DDIV) 143 | @get:JvmName("irem") 144 | val irem get() = +InsnNode(Opcodes.IREM) 145 | @get:JvmName("lrem") 146 | val lrem get() = +InsnNode(Opcodes.LREM) 147 | @get:JvmName("frem") 148 | val frem get() = +InsnNode(Opcodes.FREM) 149 | @get:JvmName("drem") 150 | val drem get() = +InsnNode(Opcodes.DREM) 151 | @get:JvmName("ineg") 152 | val ineg get() = +InsnNode(Opcodes.INEG) 153 | @get:JvmName("lneg") 154 | val lneg get() = +InsnNode(Opcodes.LNEG) 155 | @get:JvmName("fneg") 156 | val fneg get() = +InsnNode(Opcodes.FNEG) 157 | @get:JvmName("dneg") 158 | val dneg get() = +InsnNode(Opcodes.DNEG) 159 | @get:JvmName("ishl") 160 | val ishl get() = +InsnNode(Opcodes.ISHL) 161 | @get:JvmName("lshl") 162 | val lshl get() = +InsnNode(Opcodes.LSHL) 163 | @get:JvmName("ishr") 164 | val ishr get() = +InsnNode(Opcodes.ISHR) 165 | @get:JvmName("lshr") 166 | val lshr get() = +InsnNode(Opcodes.LSHR) 167 | @get:JvmName("iushr") 168 | val iushr get() = +InsnNode(Opcodes.IUSHR) 169 | @get:JvmName("lushr") 170 | val lushr get() = +InsnNode(Opcodes.LUSHR) 171 | @get:JvmName("iand") 172 | val iand get() = +InsnNode(Opcodes.IAND) 173 | @get:JvmName("land") 174 | val land get() = +InsnNode(Opcodes.LAND) 175 | @get:JvmName("ior") 176 | val ior get() = +InsnNode(Opcodes.IOR) 177 | @get:JvmName("lor") 178 | val lor get() = +InsnNode(Opcodes.LOR) 179 | @get:JvmName("ixor") 180 | val ixor get() = +InsnNode(Opcodes.IXOR) 181 | @get:JvmName("lxor") 182 | val lxor get() = +InsnNode(Opcodes.LXOR) 183 | @get:JvmName("i2l") 184 | val i2l get() = +InsnNode(Opcodes.I2L) 185 | @get:JvmName("i2f") 186 | val i2f get() = +InsnNode(Opcodes.I2F) 187 | @get:JvmName("i2d") 188 | val i2d get() = +InsnNode(Opcodes.I2D) 189 | @get:JvmName("l2i") 190 | val l2i get() = +InsnNode(Opcodes.L2I) 191 | @get:JvmName("l2f") 192 | val l2f get() = +InsnNode(Opcodes.L2F) 193 | @get:JvmName("l2d") 194 | val l2d get() = +InsnNode(Opcodes.L2D) 195 | @get:JvmName("f2i") 196 | val f2i get() = +InsnNode(Opcodes.F2I) 197 | @get:JvmName("f2l") 198 | val f2l get() = +InsnNode(Opcodes.F2L) 199 | @get:JvmName("f2d") 200 | val f2d get() = +InsnNode(Opcodes.F2D) 201 | @get:JvmName("d2i") 202 | val d2i get() = +InsnNode(Opcodes.D2I) 203 | @get:JvmName("d2l") 204 | val d2l get() = +InsnNode(Opcodes.D2L) 205 | @get:JvmName("d2f") 206 | val d2f get() = +InsnNode(Opcodes.D2F) 207 | @get:JvmName("i2b") 208 | val i2b get() = +InsnNode(Opcodes.I2B) 209 | @get:JvmName("i2c") 210 | val i2c get() = +InsnNode(Opcodes.I2C) 211 | @get:JvmName("i2s") 212 | val i2s get() = +InsnNode(Opcodes.I2S) 213 | @get:JvmName("lcmp") 214 | val lcmp get() = +InsnNode(Opcodes.LCMP) 215 | @get:JvmName("fcmpl") 216 | val fcmpl get() = +InsnNode(Opcodes.FCMPL) 217 | @get:JvmName("fcmpg") 218 | val fcmpg get() = +InsnNode(Opcodes.FCMPG) 219 | @get:JvmName("dcmpl") 220 | val dcmpl get() = +InsnNode(Opcodes.DCMPL) 221 | @get:JvmName("dcmpg") 222 | val dcmpg get() = +InsnNode(Opcodes.DCMPG) 223 | @get:JvmName("ireturn") 224 | val ireturn get() = +InsnNode(Opcodes.IRETURN) 225 | @get:JvmName("lreturn") 226 | val lreturn get() = +InsnNode(Opcodes.LRETURN) 227 | @get:JvmName("freturn") 228 | val freturn get() = +InsnNode(Opcodes.FRETURN) 229 | @get:JvmName("dreturn") 230 | val dreturn get() = +InsnNode(Opcodes.DRETURN) 231 | @get:JvmName("areturn") 232 | val areturn get() = +InsnNode(Opcodes.ARETURN) 233 | @get:JvmName("_return") 234 | val _return get() = +InsnNode(Opcodes.RETURN) 235 | @get:JvmName("arraylength") 236 | val arraylength get() = +InsnNode(Opcodes.ARRAYLENGTH) 237 | @get:JvmName("athrow") 238 | val athrow get() = +InsnNode(Opcodes.ATHROW) 239 | @get:JvmName("monitorenter") 240 | val monitorenter get() = +InsnNode(Opcodes.MONITORENTER) 241 | @get:JvmName("monitorexit") 242 | val monitorexit get() = +InsnNode(Opcodes.MONITOREXIT) 243 | 244 | fun bipush(n: Int) = +IntInsnNode(Opcodes.BIPUSH, n) 245 | fun sipush(n: Int) = +IntInsnNode(Opcodes.SIPUSH, n) 246 | fun newarray(type: Int) = +IntInsnNode(Opcodes.NEWARRAY, type) 247 | 248 | fun ldc(cst: Any) = +LdcInsnNode(cst) 249 | 250 | fun ifeq(label: LabelNode) = +JumpInsnNode(Opcodes.IFEQ, label) 251 | fun ifne(label: LabelNode) = +JumpInsnNode(Opcodes.IFNE, label) 252 | fun iflt(label: LabelNode) = +JumpInsnNode(Opcodes.IFLT, label) 253 | fun ifge(label: LabelNode) = +JumpInsnNode(Opcodes.IFGE, label) 254 | fun ifgt(label: LabelNode) = +JumpInsnNode(Opcodes.IFGT, label) 255 | fun ifle(label: LabelNode) = +JumpInsnNode(Opcodes.IFLE, label) 256 | fun if_icmpeq(label: LabelNode) = +JumpInsnNode(Opcodes.IF_ICMPEQ, label) 257 | fun if_icmpne(label: LabelNode) = +JumpInsnNode(Opcodes.IF_ICMPNE, label) 258 | fun if_icmplt(label: LabelNode) = +JumpInsnNode(Opcodes.IF_ICMPLT, label) 259 | fun if_icmpge(label: LabelNode) = +JumpInsnNode(Opcodes.IF_ICMPGE, label) 260 | fun if_icmpgt(label: LabelNode) = +JumpInsnNode(Opcodes.IF_ICMPGT, label) 261 | fun if_icmple(label: LabelNode) = +JumpInsnNode(Opcodes.IF_ICMPLE, label) 262 | fun if_acmpeq(label: LabelNode) = +JumpInsnNode(Opcodes.IF_ACMPEQ, label) 263 | fun if_acmpne(label: LabelNode) = +JumpInsnNode(Opcodes.IF_ACMPNE, label) 264 | fun goto(label: LabelNode) = +JumpInsnNode(Opcodes.GOTO, label) 265 | fun ifnull(label: LabelNode) = +JumpInsnNode(Opcodes.IFNULL, label) 266 | fun ifnonnull(label: LabelNode) = +JumpInsnNode(Opcodes.IFNONNULL, label) 267 | 268 | fun invokedynamic(name: String, desc: String, bsm: Handle, vararg bsmArgs: Any) = 269 | +InvokeDynamicInsnNode(name, desc, bsm, bsmArgs) 270 | 271 | fun invokevirtual(owner: String, name: String, desc: String) = 272 | +MethodInsnNode(Opcodes.INVOKEVIRTUAL, owner, name, desc, false) 273 | 274 | fun invokespecial(owner: String, name: String, desc: String) = 275 | +MethodInsnNode(Opcodes.INVOKESPECIAL, owner, name, desc, false) 276 | 277 | fun invokestatic(owner: String, name: String, desc: String) = 278 | +MethodInsnNode(Opcodes.INVOKESTATIC, owner, name, desc, false) 279 | 280 | fun invokeinterface(owner: String, name: String, desc: String) = 281 | +MethodInsnNode(Opcodes.INVOKEINTERFACE, owner, name, desc, true) 282 | 283 | fun new(type: String) = +TypeInsnNode(Opcodes.NEW, type) 284 | fun anewarray(type: String) = +TypeInsnNode(Opcodes.ANEWARRAY, type) 285 | fun checkcast(type: String) = +TypeInsnNode(Opcodes.CHECKCAST, type) 286 | fun instanceof(type: String) = +TypeInsnNode(Opcodes.INSTANCEOF, type) 287 | 288 | fun iload(`var`: Int) = +VarInsnNode(Opcodes.ILOAD, `var`) 289 | fun lload(`var`: Int) = +VarInsnNode(Opcodes.LLOAD, `var`) 290 | fun fload(`var`: Int) = +VarInsnNode(Opcodes.FLOAD, `var`) 291 | fun dload(`var`: Int) = +VarInsnNode(Opcodes.DLOAD, `var`) 292 | fun aload(`var`: Int) = +VarInsnNode(Opcodes.ALOAD, `var`) 293 | fun istore(`var`: Int) = +VarInsnNode(Opcodes.ISTORE, `var`) 294 | fun lstore(`var`: Int) = +VarInsnNode(Opcodes.LSTORE, `var`) 295 | fun fstore(`var`: Int) = +VarInsnNode(Opcodes.FSTORE, `var`) 296 | fun dstore(`var`: Int) = +VarInsnNode(Opcodes.DSTORE, `var`) 297 | fun astore(`var`: Int) = +VarInsnNode(Opcodes.ASTORE, `var`) 298 | 299 | fun f_new(numLocal: Int, local: Array?, numStack: Int, stack: Array?) = 300 | +FrameNode(Opcodes.F_NEW, numLocal, local, numStack, stack) 301 | 302 | fun f_full(numLocal: Int, local: Array?, numStack: Int, stack: Array?) = 303 | +FrameNode(Opcodes.F_FULL, numLocal, local, numStack, stack) 304 | 305 | fun f_append(numLocal: Int, local: Array) = 306 | +FrameNode(Opcodes.F_APPEND, numLocal, local, 0, null) 307 | 308 | fun f_chop(numLocal: Int) = 309 | +FrameNode(Opcodes.F_CHOP, numLocal, null, 0, null) 310 | 311 | fun f_same() = 312 | +FrameNode(Opcodes.F_SAME, 0, null, 0, null) 313 | 314 | fun f_same1(stack: Any) = 315 | +FrameNode(Opcodes.F_SAME1, 0, null, 1, arrayOf(stack)) 316 | 317 | fun int(n: Int) = when (n) { 318 | in -1..5 -> +InsnNode(Opcodes.ICONST_0 + n) 319 | in Byte.MIN_VALUE..Byte.MAX_VALUE -> bipush(n) 320 | in Short.MIN_VALUE..Short.MAX_VALUE -> sipush(n) 321 | else -> ldc(n) 322 | } 323 | } 324 | 325 | private class InsnListBuilder : InsnBuilder() { 326 | 327 | val list = InsnList() 328 | override fun AbstractInsnNode.unaryPlus() = list.add(this) 329 | override fun InsnList.unaryPlus() = list.add(this) 330 | } 331 | 332 | private class VisitorInsnBuilder(private val parent: MethodVisitor) : InsnBuilder() { 333 | 334 | override fun AbstractInsnNode.unaryPlus() = accept(parent) 335 | override fun InsnList.unaryPlus() = accept(parent) 336 | } 337 | 338 | public fun asm(block: InsnBuilder.() -> Unit): InsnList = 339 | InsnListBuilder().apply(block).list 340 | 341 | public fun MethodVisitor.visitAsm(block: InsnBuilder.() -> Unit) { 342 | VisitorInsnBuilder(this).run(block) 343 | } 344 | -------------------------------------------------------------------------------- /modules/common/src/main/kotlin/org/polyfrost/spice/util/ClassUtil.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.util 2 | 3 | import org.objectweb.asm.ClassReader 4 | import org.objectweb.asm.ClassReader.SKIP_CODE 5 | import org.objectweb.asm.tree.ClassNode 6 | import org.objectweb.asm.tree.InvokeDynamicInsnNode 7 | import org.objectweb.asm.tree.LdcInsnNode 8 | import org.objectweb.asm.tree.MethodNode 9 | 10 | object ClassUtil 11 | 12 | private val chainCache = mutableMapOf>() 13 | 14 | fun readClass(name: String): ClassNode? = 15 | ClassNode().also { node -> 16 | ClassReader( 17 | ClassUtil::class.java 18 | .getResourceAsStream("${name.replace(".", "/")}.class") 19 | ?.use { it.readBytes() } ?: return null 20 | ).accept(node, SKIP_CODE) 21 | } 22 | 23 | fun getStrings(node: ClassNode): Set = 24 | node.methods.map { 25 | getStrings(it as MethodNode) 26 | }.flatten().toSet() 27 | 28 | fun getStrings(node: MethodNode): Set = 29 | node.instructions.iterator().asSequence() 30 | .filter { insn -> 31 | (insn is LdcInsnNode && insn.cst is String) 32 | || (insn is InvokeDynamicInsnNode && insn.bsmArgs.any { it is String }) 33 | } 34 | .map { 35 | if (it is LdcInsnNode) listOf(it.cst as String) 36 | else (it as InvokeDynamicInsnNode).bsmArgs.filterIsInstance() 37 | } 38 | .flatten().toSet() 39 | 40 | @Suppress("NAME_SHADOWING") 41 | fun classChain(clazz: Class<*>): List> { 42 | val chain = mutableListOf>() 43 | var clazz = clazz 44 | 45 | while (clazz.superclass != null) { 46 | chain.add(clazz) 47 | clazz = clazz.superclass 48 | } 49 | 50 | return chain 51 | } 52 | 53 | fun classChain(node: ClassNode): List { 54 | return chainCache.computeIfAbsent(node.name) { 55 | val chain = mutableListOf() 56 | val newNode = readClass(node.superName) ?: return@computeIfAbsent chain 57 | 58 | chain.addAll(classChain(newNode)) 59 | chain 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /modules/common/src/main/kotlin/org/polyfrost/spice/util/SpiceClassWriter.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.util 2 | 3 | import org.objectweb.asm.ClassReader 4 | import org.objectweb.asm.ClassWriter 5 | 6 | class SpiceClassWriter : ClassWriter { 7 | @Suppress("unused") 8 | constructor(flags: Int) : super(flags) 9 | 10 | @Suppress("unused") 11 | constructor(reader: ClassReader, flags: Int) 12 | : super(reader, flags) 13 | 14 | override fun getCommonSuperClass(a: String, b: String): String? { 15 | val chainA = classChain(readClass(a) ?: return "java/lang/Object") 16 | val chainB = classChain(readClass(b) ?: return "java/lang/Object") 17 | 18 | val commonSuperClasses = mutableSetOf() 19 | 20 | chainA.forEach { if (chainB.contains(it)) commonSuperClasses += it } 21 | 22 | return commonSuperClasses.firstOrNull() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /modules/core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.github.johnrengelman.shadow") 3 | kotlin("plugin.serialization") 4 | } 5 | 6 | dependencies { 7 | compileOnly("org.apache.logging.log4j:log4j-api:2.0-beta9") 8 | compileOnly(project(":modules:lwjgl")) 9 | 10 | compileOnly(rootProject.libs.asmtree) 11 | compileOnly(rootProject.libs.mixins) 12 | compileOnly(rootProject.libs.bundles.lwjgl) 13 | 14 | implementation(rootProject.libs.kotlinx.coroutines) 15 | implementation(rootProject.libs.kotlinx.serialization.json) 16 | } 17 | 18 | tasks.processResources { 19 | from( 20 | project(":modules:lwjgl") 21 | .tasks 22 | .shadowJar 23 | .get() 24 | .archiveFile 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/Constants.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice 2 | 3 | import kotlin.io.path.Path 4 | 5 | val spiceDirectory = Path("spice").toAbsolutePath() 6 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/ModManifest.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class ModManifest( 7 | val version: String? = null 8 | ) 9 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/Options.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlinx.serialization.Transient 5 | 6 | @Serializable 7 | data class LwjglOptions( 8 | var beta: Boolean = false, 9 | var version: String = "3.3.3" 10 | ) 11 | 12 | @Serializable 13 | data class Options( 14 | @JvmField 15 | var rawInput: Boolean, 16 | @JvmField 17 | val lwjgl: LwjglOptions 18 | ) { 19 | @JvmField 20 | @Transient 21 | var needsSave = false 22 | } 23 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/Spice.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice 2 | 3 | import kotlinx.serialization.encodeToString 4 | import kotlinx.serialization.json.Json 5 | import org.apache.logging.log4j.LogManager 6 | import org.lwjgl.Version 7 | import org.lwjgl.glfw.GLFW.* 8 | import org.lwjgl.glfw.GLFWErrorCallback 9 | import org.lwjgl.openal.AL10.AL_VERSION 10 | import org.lwjgl.openal.AL10.alGetString 11 | import org.lwjgl.system.Configuration.GLFW_CHECK_THREAD0 12 | import org.lwjgl.system.MemoryStack 13 | import org.polyfrost.spice.debug.DebugHelper 14 | import org.polyfrost.spice.debug.DebugSection 15 | import org.polyfrost.spice.platform.api.Platform 16 | import org.polyfrost.spice.util.isMac 17 | import org.polyfrost.spice.util.isOptifineLoaded 18 | import kotlin.io.path.createDirectories 19 | import kotlin.io.path.exists 20 | import kotlin.io.path.readText 21 | import kotlin.io.path.writeText 22 | 23 | object Spice { 24 | @JvmStatic 25 | val options: Options by lazy { 26 | val defaultOptions = Options( 27 | rawInput = glfwRawMouseMotionSupported(), 28 | lwjgl = LwjglOptions( 29 | beta = false, 30 | version = "" 31 | ) 32 | ) 33 | 34 | defaultOptions.needsSave = true 35 | 36 | if (configFile.exists()) { 37 | try { 38 | json.decodeFromString(configFile.readText()) 39 | } catch (_: Exception) { 40 | defaultOptions 41 | } 42 | } else { 43 | defaultOptions 44 | } 45 | } 46 | 47 | @JvmStatic 48 | lateinit var version: String 49 | 50 | @JvmStatic 51 | internal val logger = LogManager.getLogger("Spice") 52 | 53 | @JvmStatic 54 | lateinit var platform: Platform 55 | private set 56 | 57 | @JvmStatic 58 | lateinit var glfwVersion: String 59 | private set 60 | 61 | @JvmStatic 62 | lateinit var openalVersion: String 63 | private set 64 | 65 | private val configFile = spiceDirectory.resolve("config.json") 66 | private val json = Json { ignoreUnknownKeys = true } 67 | 68 | @JvmStatic 69 | fun initialize(platform: Platform) { 70 | this.platform = platform 71 | 72 | saveOptions() 73 | 74 | Runtime.getRuntime().addShutdownHook(Thread { 75 | if (options.needsSave) saveOptions() 76 | }) 77 | GLFWErrorCallback.createPrint(System.err).set() 78 | 79 | if (isMac()) GLFW_CHECK_THREAD0.set(false) 80 | if (!glfwInit()) throw RuntimeException("Failed to initialize GLFW") 81 | if (isOptifineLoaded()) logger.warn("OptiFine is enabled! No performance patches will be applied.") 82 | 83 | // todo: store in jar and load 84 | version = "1.0.0" 85 | glfwVersion = MemoryStack 86 | .stackPush() 87 | .use { stack -> 88 | val major = stack.ints(0) 89 | val minor = stack.ints(0) 90 | val patch = stack.ints(0) 91 | 92 | glfwGetVersion(major, minor, patch) 93 | 94 | "${major.get()}.${minor.get()}.${patch.get()}" 95 | } 96 | 97 | logger.info("Spice Version: $version") 98 | logger.info("Platform: ${platform.id}") 99 | 100 | initializeDebugSections() 101 | } 102 | 103 | @JvmStatic 104 | fun cleanup() { 105 | logger.info("Terminating GLFW") 106 | 107 | glfwTerminate() 108 | } 109 | 110 | @JvmStatic 111 | fun saveOptions() { 112 | if (!spiceDirectory.exists()) { 113 | spiceDirectory.createDirectories() 114 | } 115 | 116 | if (!options.needsSave) return 117 | 118 | configFile.writeText(json.encodeToString(options)) 119 | options.needsSave = false 120 | } 121 | 122 | private fun initializeDebugSections() { 123 | val versionDebugSection = DebugSection() 124 | 125 | versionDebugSection.lines += { "Version: $version" } 126 | versionDebugSection.lines += { "GLFW: $glfwVersion" } 127 | versionDebugSection.lines += { 128 | if (!this::openalVersion.isInitialized) openalVersion = alGetString(AL_VERSION)!! 129 | 130 | "OpenAL: $openalVersion" 131 | } 132 | versionDebugSection.lines += { "LWJGL: ${Version.getVersion()}" } 133 | 134 | DebugHelper.sections += versionDebugSection 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/api/Mouse.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.api 2 | 3 | import org.lwjgl.input.Mouse 4 | import org.lwjglx.input.RawInput 5 | 6 | object Mouse { 7 | @JvmStatic 8 | fun isRawInputSupported(): Boolean = RawInput.isRawInputSupported() 9 | @JvmStatic 10 | fun setRawInput(raw: Boolean) = RawInput.useRawInput(raw) 11 | @JvmStatic 12 | fun setX(x: Int) = Mouse.setX(x) 13 | @JvmStatic 14 | fun setY(y: Int) = Mouse.setY(y) 15 | @JvmStatic 16 | fun setEventX(x: Int) = Mouse.setEventX(x) 17 | @JvmStatic 18 | fun setEventY(y: Int) = Mouse.setEventY(y) 19 | } 20 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/debug/DebugHelper.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.debug 2 | 3 | object DebugHelper { 4 | internal val sections = mutableListOf() 5 | 6 | @JvmStatic 7 | fun applyExtraDebugInfo(lines: MutableList) { 8 | sections.forEach { section -> 9 | lines += "" 10 | 11 | section 12 | .lines 13 | .forEach { 14 | lines += "§5[Spice]§r ${it.invoke()}" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/debug/DebugSection.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.debug 2 | 3 | class DebugSection { 4 | internal val lines = mutableListOf<() -> String>() 5 | } 6 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/fixes/EarlyCrashFix.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.fixes 2 | 3 | import org.lwjgl.opengl.GL 4 | import java.util.concurrent.Callable 5 | 6 | fun getFieldValue(name: String, value: Callable): String { 7 | return if (name == "OpenGL" || name == "GL Caps") { 8 | try { 9 | GL.getCapabilities() 10 | 11 | value.call() 12 | } catch (_: IllegalStateException) { 13 | "N/A" 14 | } 15 | } else { 16 | try { 17 | value.call() 18 | } catch (_: Exception) { 19 | "N/A" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/patcher/Cache.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.patcher 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlinx.serialization.encodeToString 5 | import kotlinx.serialization.json.Json 6 | import org.objectweb.asm.ClassReader 7 | import org.objectweb.asm.ClassWriter.COMPUTE_FRAMES 8 | import org.objectweb.asm.tree.ClassNode 9 | import org.objectweb.asm.tree.MethodNode 10 | import org.polyfrost.spice.patcher.lwjgl.LibraryTransformer 11 | import org.polyfrost.spice.patcher.lwjgl.LwjglTransformer 12 | import org.polyfrost.spice.patcher.lwjgl.MemoryStackTransformer 13 | import org.polyfrost.spice.spiceDirectory 14 | import org.polyfrost.spice.util.SpiceClassWriter 15 | import java.util.jar.JarFile 16 | import java.util.jar.JarOutputStream 17 | import java.util.zip.ZipEntry 18 | import kotlin.io.path.createDirectories 19 | import kotlin.io.path.exists 20 | import kotlin.io.path.outputStream 21 | 22 | private val provider by lazy { LwjglTransformer.provider } 23 | private val cacheDirectory = spiceDirectory.resolve(".cache") 24 | 25 | fun currentHash(): String = provider.hash 26 | fun isCached(hash: String): Boolean = cachePath(hash).exists() 27 | 28 | fun loadCache(hash: String): Map = 29 | loadCacheBuffers(hash).mapValues { (_, buffer) -> 30 | ClassNode() 31 | .also { ClassReader(buffer).accept(it, 0) } 32 | } 33 | 34 | fun loadCacheBuffers(hash: String): Map { 35 | val path = cachePath(hash) 36 | val jar = JarFile(path.toFile()) 37 | 38 | val manifest = Json.decodeFromString( 39 | jar 40 | .getInputStream(jar.getEntry("cache-manifest.json")) 41 | .use { 42 | it 43 | .readBytes() 44 | .toString(Charsets.UTF_8) 45 | }) 46 | 47 | return manifest.transformable 48 | .associateWith { transformable -> 49 | jar 50 | .getInputStream(jar.getEntry("$transformable.class")) 51 | .use { it.readBytes() } 52 | } 53 | } 54 | 55 | fun buildCache(hash: String, `in`: List): Pair, Map> { 56 | // todo: abuse coroutines. 57 | val transformers = arrayOf( 58 | LwjglTransformer, 59 | LibraryTransformer, 60 | MemoryStackTransformer 61 | ) 62 | 63 | val transformable = mutableSetOf() 64 | val provider = LwjglTransformer.provider 65 | 66 | val transformed = mutableMapOf() 67 | val buffers = mutableMapOf() 68 | 69 | `in`.forEach { node -> 70 | transformable.add(node.name) 71 | transformers.forEach transform@{ transformer -> 72 | val targets = transformer.targets 73 | 74 | if (targets != null 75 | && !targets.contains(node.name.replace("/", ".")) 76 | ) return@transform 77 | 78 | transformer.transform(node) 79 | } 80 | 81 | node.methods.forEach { method -> 82 | method as MethodNode 83 | 84 | // this fixes stuff because sometimes exceptions are null (????) 85 | if (method.exceptions == null) method.exceptions = mutableListOf() 86 | } 87 | 88 | transformed[node.name] = node 89 | buffers["${node.name}.class"] = 90 | SpiceClassWriter(COMPUTE_FRAMES) 91 | .also { node.accept(it) } 92 | .toByteArray() 93 | } 94 | 95 | provider.allEntries.forEach { entry -> 96 | if (!entry.endsWith(".class") || !entry.startsWith("org/lwjgl/")) return@forEach 97 | 98 | if (!buffers.contains(entry)) { 99 | buffers[entry] = 100 | provider.readFile(entry) ?: return@forEach 101 | } 102 | } 103 | 104 | JarOutputStream(cachePath(hash).outputStream()) 105 | .use { out -> 106 | out.putNextEntry(ZipEntry("cache-manifest.json")) 107 | out.write( 108 | Json.encodeToString( 109 | CacheManifest( 110 | transformable.toList() 111 | ) 112 | ).toByteArray(Charsets.UTF_8) 113 | ) 114 | out.closeEntry() 115 | 116 | buffers.forEach { (name, buffer) -> 117 | out.putNextEntry(ZipEntry(name)) 118 | out.write(buffer) 119 | out.closeEntry() 120 | } 121 | 122 | out.finish() 123 | } 124 | 125 | return Pair(transformed, buffers) 126 | } 127 | 128 | private fun cachePath(hash: String) = 129 | cacheDirectory 130 | .createDirectories() 131 | .resolve("$hash.jar") 132 | 133 | @Serializable 134 | private data class CacheManifest( 135 | val transformable: List 136 | ) 137 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/patcher/LunarTransformer.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.patcher 2 | 3 | import org.objectweb.asm.tree.ClassNode 4 | import org.objectweb.asm.tree.FieldInsnNode 5 | import org.objectweb.asm.tree.MethodNode 6 | import org.polyfrost.spice.platform.api.IClassTransformer 7 | import org.polyfrost.spice.util.getStrings 8 | 9 | object LunarTransformer : IClassTransformer { 10 | override val targets = null 11 | 12 | override fun transform(node: ClassNode) { 13 | if (!node.name.startsWith("com/moonsworth/lunar/")) return 14 | 15 | if (getStrings(node).contains("Can't translate key \u0001")) { 16 | val fromLwjglMethod = node.methods 17 | .find { (it as MethodNode).desc.startsWith("(I)") } as MethodNode 18 | 19 | val index = fromLwjglMethod 20 | .instructions 21 | .iterator() 22 | .asSequence() 23 | .indexOfLast { it is FieldInsnNode && it.owner == "java/lang/System" && it.name == "err" } 24 | 25 | (index..index + 3).map { fromLwjglMethod.instructions[it] } 26 | .forEach { fromLwjglMethod.instructions.remove(it) } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/patcher/OptifineTransformer.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.patcher 2 | 3 | import org.objectweb.asm.Opcodes.INVOKESTATIC 4 | import org.objectweb.asm.tree.ClassNode 5 | import org.objectweb.asm.tree.MethodInsnNode 6 | import org.objectweb.asm.tree.MethodNode 7 | import org.polyfrost.spice.platform.api.IClassTransformer 8 | 9 | object OptifineTransformer : IClassTransformer { 10 | override val targets = arrayOf("net.optifine.shaders.Shaders") 11 | 12 | override fun transform(node: ClassNode) { 13 | node.methods.forEach { method -> 14 | (method as MethodNode) 15 | .instructions 16 | .iterator() 17 | .asSequence() 18 | .filter { 19 | it is MethodInsnNode 20 | && it.owner == "org/lwjgl/opengl/EXTFramebufferObject" 21 | && it.opcode == INVOKESTATIC 22 | } 23 | .forEach { 24 | it as MethodInsnNode 25 | 26 | it.owner = "org/lwjgl/opengl/GL30" 27 | it.name = it.name.replace("EXT", "") 28 | } 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/patcher/fixes/OpenAlFixes.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.patcher.fixes 2 | 3 | import org.polyfrost.spice.patcher.util.AudioHelper 4 | 5 | @Suppress("unused") 6 | object OpenAlFixes { 7 | private val deviceHandleField = 8 | Class 9 | .forName("org.lwjgl.openal.ALCdevice") 10 | .getDeclaredField("device") 11 | 12 | @JvmStatic 13 | fun create() { 14 | try { 15 | AudioHelper.createContext(null, -1, 60, false) 16 | } catch (ex: Throwable) { 17 | throw ex 18 | } 19 | } 20 | 21 | @JvmStatic 22 | fun isCreated() = AudioHelper.isCreated() 23 | 24 | @JvmStatic 25 | fun destroyContext() = AudioHelper.destroyContext() 26 | 27 | @JvmStatic 28 | fun mapDevice(device: Any): Long = deviceHandleField.getLong(device) 29 | } 30 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/patcher/fixes/OpenGlFixes.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.patcher.fixes 2 | 3 | import org.lwjgl.opengl.GL20.glShaderSource 4 | import java.nio.ByteBuffer 5 | 6 | @Suppress("unused") 7 | object OpenGlFixes { 8 | @JvmStatic 9 | fun glShaderSource(count: Int, buffer: ByteBuffer) { 10 | val byteArray = ByteArray(buffer.limit()) 11 | 12 | buffer.position(0) 13 | buffer.get(byteArray) 14 | buffer.position(0) 15 | 16 | glShaderSource(count, String(byteArray)) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/patcher/lwjgl/EssentialGlobalMouseOverrideTransformer.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.patcher.lwjgl 2 | 3 | import net.weavemc.loader.api.util.asm 4 | import org.objectweb.asm.tree.ClassNode 5 | import org.objectweb.asm.tree.MethodNode 6 | import org.polyfrost.spice.platform.api.IClassTransformer 7 | 8 | object EssentialGlobalMouseOverrideTransformer : IClassTransformer { 9 | override val targets = arrayOf("gg.essential.gui.overlay.OverlayManagerImpl\$GlobalMouseOverride") 10 | 11 | override fun transform(node: ClassNode) { 12 | val clinit = node.methods.find { method -> 13 | method as MethodNode 14 | method.name == "" 15 | }!! as MethodNode 16 | clinit.instructions.clear() 17 | clinit.instructions.add(asm { 18 | new("gg/essential/gui/overlay/OverlayManagerImpl\$GlobalMouseOverride") 19 | dup 20 | invokespecial( 21 | "gg/essential/gui/overlay/OverlayManagerImpl\$GlobalMouseOverride", 22 | "", 23 | "()V" 24 | ) 25 | putstatic( 26 | "gg/essential/gui/overlay/OverlayManagerImpl\$GlobalMouseOverride", 27 | "INSTANCE", 28 | "Lgg/essential/gui/overlay/OverlayManagerImpl\$GlobalMouseOverride;" 29 | ) 30 | _return 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/patcher/lwjgl/LibraryTransformer.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.patcher.lwjgl 2 | 3 | import org.objectweb.asm.tree.ClassNode 4 | import org.objectweb.asm.tree.LdcInsnNode 5 | import org.objectweb.asm.tree.MethodNode 6 | import org.polyfrost.spice.platform.api.IClassTransformer 7 | 8 | object LibraryTransformer : IClassTransformer { 9 | override val targets = arrayOf("org.lwjgl.system.Library") 10 | 11 | override fun transform(node: ClassNode) { 12 | node.methods.forEach { method -> 13 | (method as MethodNode).instructions 14 | .iterator() 15 | .asSequence() 16 | .filter { it is LdcInsnNode && it.cst is String && it.cst == "java.library.path" } 17 | .forEach { (it as LdcInsnNode).cst = "spice.library.path" } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/patcher/lwjgl/LwjglProvider.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.patcher.lwjgl 2 | 3 | import org.objectweb.asm.ClassReader 4 | import org.objectweb.asm.tree.ClassNode 5 | import org.polyfrost.spice.util.UrlByteArrayConnection 6 | import java.io.InputStream 7 | import java.net.URL 8 | import java.net.URLConnection 9 | import java.net.URLStreamHandler 10 | import java.security.MessageDigest 11 | import java.util.jar.JarInputStream 12 | 13 | class LwjglProvider { 14 | private val fileCache = mutableMapOf() 15 | 16 | private val jar by lazy { JarInputStream(openStream() ?: return@lazy null) } 17 | 18 | private var closed = false 19 | 20 | @OptIn(ExperimentalStdlibApi::class) 21 | val hash by lazy { 22 | openStream()?.use { 23 | val digest = MessageDigest.getInstance("SHA-1") 24 | 25 | digest 26 | .digest(it.readBytes()) 27 | .toHexString() 28 | } ?: "0" 29 | } 30 | 31 | val url = URL("spice", "", -1, "/", object : URLStreamHandler() { 32 | override fun openConnection(url: URL): URLConnection? { 33 | val buffer = readFile(url.path.replaceFirst("/", "")) ?: return null 34 | 35 | return UrlByteArrayConnection(buffer, url) 36 | } 37 | }) 38 | 39 | val allEntries: Collection by lazy { 40 | if (closed) fileCache.keys 41 | else { 42 | readEntryUntil(null) 43 | 44 | fileCache.keys 45 | } 46 | } 47 | 48 | fun readFile(path: String): ByteArray? { 49 | if (fileCache.contains(path)) return fileCache[path]!! 50 | if (closed || jar == null) return null 51 | 52 | return readEntryUntil(path) 53 | } 54 | 55 | fun getClassNode(name: String): ClassNode? { 56 | val buffer = readFile("$name.class") ?: return null 57 | val classNode = ClassNode() 58 | 59 | ClassReader(buffer).accept(classNode, 0) 60 | 61 | return classNode 62 | } 63 | 64 | private fun openStream(): InputStream? = 65 | LwjglProvider::class.java 66 | .classLoader 67 | .getResource("lwjgl-bundle") 68 | ?.openStream() 69 | 70 | private fun readEntryUntil(path: String?): ByteArray? { 71 | if (closed || jar == null) return null 72 | 73 | while (true) { 74 | val entry = jar!!.nextEntry ?: run { 75 | jar!!.close() 76 | closed = true 77 | 78 | return null 79 | } 80 | 81 | val length = entry.size.toInt() 82 | 83 | if (entry.isDirectory) continue 84 | if (length == -1) continue 85 | 86 | val entryBuffer = ByteArray(length) 87 | var offset = 0 88 | 89 | while (true) { 90 | val read = jar!!.read(entryBuffer, offset, length - offset) 91 | 92 | offset += read 93 | 94 | if (offset == length) break 95 | } 96 | 97 | jar!!.closeEntry() 98 | fileCache[entry.name] = entryBuffer 99 | 100 | if (path != null && entry.name == path) return entryBuffer 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/patcher/lwjgl/LwjglTransformer.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.patcher.lwjgl 2 | 3 | import net.weavemc.loader.api.util.asm 4 | import org.apache.logging.log4j.LogManager 5 | import org.objectweb.asm.Opcodes 6 | import org.objectweb.asm.Opcodes.ACC_NATIVE 7 | import org.objectweb.asm.tree.ClassNode 8 | import org.objectweb.asm.tree.FieldNode 9 | import org.objectweb.asm.tree.InsnList 10 | import org.objectweb.asm.tree.MethodNode 11 | import org.polyfrost.spice.platform.api.IClassTransformer 12 | 13 | private data class InjectedMethod( 14 | val name: String, 15 | val desc: String, 16 | val access: Int, 17 | val instructions: InsnList 18 | ) 19 | 20 | object LwjglTransformer : IClassTransformer { 21 | private val logger = LogManager.getLogger("Spice/Transformer")!! 22 | 23 | private val injectableMethods = mapOf( 24 | "org/lwjgl/openal/AL" to listOf( 25 | InjectedMethod( 26 | "create", 27 | "()V", 28 | Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, 29 | asm { 30 | invokestatic( 31 | "org/polyfrost/spice/patcher/fixes/OpenAlFixes", 32 | "create", 33 | "()V" 34 | ) 35 | _return 36 | } 37 | ), 38 | InjectedMethod( 39 | "isCreated", 40 | "()Z", 41 | Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, 42 | asm { 43 | invokestatic( 44 | "org/polyfrost/spice/patcher/fixes/OpenAlFixes", 45 | "isCreated", 46 | "()Z" 47 | ) 48 | ireturn 49 | } 50 | ), 51 | InjectedMethod( 52 | "destroy", 53 | "()V", 54 | Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, 55 | asm { 56 | invokestatic( 57 | "org/polyfrost/spice/patcher/fixes/OpenAlFixes", 58 | "destroyContext", 59 | "()V" 60 | ) 61 | _return 62 | } 63 | ) 64 | ), 65 | "org/lwjgl/openal/ALC10" to listOf( 66 | InjectedMethod( 67 | "alcGetString", 68 | "(Lorg/lwjgl/openal/ALCdevice;I)Ljava/lang/String;", 69 | Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, 70 | asm { 71 | aload(0) 72 | invokestatic( 73 | "org/polyfrost/spice/patcher/fixes/OpenAlFixes", 74 | "mapDevice", 75 | "(Ljava/lang/Object;)J", 76 | ) 77 | iload(1) 78 | invokestatic( 79 | "org/lwjgl/openal/ALC10", 80 | "alGetString", 81 | "(JI)Ljava/lang/String;" 82 | ) 83 | 84 | areturn 85 | } 86 | ) 87 | ), 88 | "org/lwjgl/opengl/GL20" to listOf( 89 | InjectedMethod( 90 | "glShaderSource", 91 | "(ILjava/nio/ByteBuffer;)V", 92 | Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, 93 | asm { 94 | iload(0) 95 | aload(1) 96 | 97 | invokestatic( 98 | "org/polyfrost/spice/patcher/fixes/OpenGlFixes", 99 | "glShaderSource", 100 | "(ILjava/nio/ByteBuffer;)V" 101 | ) 102 | 103 | _return 104 | } 105 | ) 106 | ) 107 | ) 108 | private val overloadSuffixes = listOf( 109 | "v", "i_v", "b", 110 | "s", "i", "i64", 111 | "f", "d", "ub", 112 | "us", "ui", "ui64" 113 | ).flatMap { suffix -> 114 | if (!suffix.endsWith("v")) listOf(suffix, suffix + "v") 115 | else listOf(suffix) 116 | }.sortedByDescending { suffix -> suffix.length } 117 | 118 | private val extensionPrefixes = listOf( 119 | "ARB", 120 | "NV", 121 | "NVX", 122 | "ATI", 123 | "3DLABS", 124 | "SUN", 125 | "SGI", 126 | "SGIX", 127 | "SGIS", 128 | "INTEL", 129 | "3DFX", 130 | "IBM", 131 | "MESA", 132 | "GREMEDY", 133 | "OML", 134 | "OES", 135 | "PGI", 136 | "I3D", 137 | "INGR", 138 | "MTX" 139 | ).sortedByDescending { suffix -> suffix.length } 140 | 141 | override val targets = null 142 | 143 | val provider = LwjglProvider() 144 | 145 | override fun transform(node: ClassNode) { 146 | if (!node.name.startsWith("org/lwjgl")) return 147 | if (node.name == "org/lwjgl/opengl/PixelFormat") return 148 | 149 | val patch = 150 | provider.getClassNode(node.name) 151 | ?: return 152 | 153 | logger.debug("Patching ${node.name} with ${patch.name}") 154 | 155 | node.superName = patch.superName 156 | node.interfaces = patch.interfaces 157 | node.access = patch.access 158 | 159 | node.sourceFile = patch.sourceFile 160 | node.sourceDebug = patch.sourceDebug 161 | 162 | node.version = patch.version 163 | 164 | val oldMethods = node.methods.filterIsInstance() 165 | val patchMethods = patch.methods.filterIsInstance() 166 | 167 | val addedMethods = patchMethods 168 | .filter { method -> 169 | !oldMethods.any { old -> old.name == method.name && old.desc == method.desc } 170 | } 171 | val removedMethods = oldMethods 172 | .filter { method -> 173 | !patchMethods.any { patch -> patch.name == method.name && patch.desc == method.desc } 174 | } 175 | 176 | node.methods.clear() 177 | 178 | if (node.name != "org/lwjgl/opengl/ContextCapabilities") node.fields.clear() 179 | else { 180 | node 181 | .fields 182 | .forEach { field -> 183 | (field as FieldNode).access = field.access and Opcodes.ACC_FINAL.inv() 184 | } 185 | } 186 | 187 | patch 188 | .methods 189 | .forEach { method -> 190 | node.methods.add(method) 191 | } 192 | 193 | patch 194 | .fields 195 | .forEach { field -> 196 | if (!node.fields.any { (it as FieldNode).name == (field as FieldNode).name && it.desc == field.desc }) 197 | node.fields.add(field) 198 | } 199 | 200 | val renames = removedMethods 201 | .filter { method -> 202 | method.access and ACC_NATIVE == 0 203 | && ( 204 | method.name.startsWith("al") 205 | || method.name.startsWith("gl") 206 | ) 207 | } 208 | .mapNotNull { method -> 209 | addedMethods.find { added -> 210 | added.desc == method.desc 211 | && stripOverloadSuffix(added.name, getFunctionEndIndex(added.name)) == method.name 212 | }?.let { added -> added.name to Pair(method.name, method.desc) } 213 | } 214 | .toMap() 215 | 216 | node 217 | .methods 218 | .forEach { method -> 219 | renames[(method as MethodNode).name]?.run { 220 | if (second == method.desc) method.name = first 221 | } 222 | } 223 | 224 | injectableMethods[node.name]?.forEach { injection -> 225 | node.methods.removeIf { method -> 226 | method as MethodNode 227 | method.name == injection.name && method.desc == injection.desc 228 | } 229 | 230 | val method = MethodNode() 231 | 232 | method.name = injection.name 233 | method.desc = injection.desc 234 | method.access = injection.access 235 | method.instructions = injection.instructions 236 | 237 | node.methods.add(method) 238 | } 239 | } 240 | 241 | private fun stripOverloadSuffix(name: String, end: Int = name.length): String { 242 | val suffix = overloadSuffixes.find { suffix -> 243 | if (end != name.length) name.substring(0.. name.endsWith(prefix) } 254 | ?.let { prefix -> name.length - prefix.length } ?: name.length 255 | } 256 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/patcher/lwjgl/MemoryStackTransformer.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.patcher.lwjgl 2 | 3 | import net.weavemc.loader.api.util.asm 4 | import org.objectweb.asm.Opcodes.ARETURN 5 | import org.objectweb.asm.tree.ClassNode 6 | import org.objectweb.asm.tree.MethodNode 7 | import org.polyfrost.spice.platform.api.IClassTransformer 8 | 9 | object MemoryStackTransformer : IClassTransformer { 10 | override val targets = arrayOf("org.lwjgl.system.MemoryStack") 11 | 12 | override fun transform(node: ClassNode) { 13 | val method = node.methods.find { method -> 14 | method as MethodNode 15 | method.name == "create" && method.desc == "(Ljava/nio/ByteBuffer;)Lorg/lwjgl/system/MemoryStack;" 16 | }!! as MethodNode 17 | 18 | method 19 | .instructions 20 | .insertBefore( 21 | method 22 | .instructions 23 | .toArray() 24 | .find { insn -> insn.opcode == ARETURN }, 25 | asm { checkcast("org/lwjgl/system/MemoryStack") } 26 | ) 27 | } 28 | } -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/patcher/util/AudioHelper.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.patcher.util 2 | 3 | import org.lwjgl.openal.AL 4 | import org.lwjgl.openal.ALC 5 | import org.lwjgl.openal.ALC10.* 6 | import org.lwjgl.system.MemoryStack 7 | import org.lwjgl.system.MemoryUtil.NULL 8 | import java.nio.IntBuffer 9 | 10 | object AudioHelper { 11 | private var deviceHandle = -1L 12 | private var contextHandle = -1L 13 | 14 | @JvmStatic 15 | fun createContext(device: String?, frequency: Int, refresh: Int, synchronized: Boolean) { 16 | val deviceHandle = alcOpenDevice(device) 17 | 18 | if (deviceHandle == NULL) throw RuntimeException("Failed to open device") 19 | 20 | val deviceCapabilities = ALC.createCapabilities(deviceHandle) 21 | 22 | contextHandle = 23 | if (frequency == -1) { 24 | alcCreateContext(deviceHandle, null as IntBuffer?) 25 | } else MemoryStack 26 | .stackPush() 27 | .use { stack -> 28 | val buffer = stack.callocInt(7) 29 | 30 | buffer 31 | .put(ALC_FREQUENCY) 32 | .put(frequency) 33 | .put(ALC_REFRESH) 34 | .put(refresh) 35 | .put(ALC_SYNC) 36 | .put(if (synchronized) ALC_TRUE else ALC_FALSE) 37 | .put(0) 38 | 39 | alcCreateContext(deviceHandle, buffer) 40 | } 41 | 42 | if (contextHandle == NULL) throw RuntimeException("Failed to create OpenAL context") 43 | 44 | alcMakeContextCurrent(contextHandle) 45 | AL.createCapabilities(deviceCapabilities) 46 | } 47 | 48 | @JvmStatic 49 | @Suppress("unused") 50 | fun isCreated(): Boolean { 51 | return contextHandle != -1L 52 | } 53 | 54 | @JvmStatic 55 | @Suppress("unused") 56 | fun destroyContext() { 57 | if (contextHandle != -1L) { 58 | alcMakeContextCurrent(0L) 59 | alcDestroyContext(contextHandle) 60 | 61 | contextHandle = -1L 62 | } 63 | 64 | if (deviceHandle != -1L) { 65 | alcCloseDevice(deviceHandle) 66 | 67 | deviceHandle = -1L 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/platform/Bootstrap.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.platform 2 | 3 | import org.polyfrost.spice.Spice 4 | import org.polyfrost.spice.platform.api.Platform 5 | 6 | fun bootstrap(platform: Platform) { 7 | Spice.initialize(platform) 8 | } 9 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/platform/TransformerBootstrap.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.platform 2 | 3 | import org.polyfrost.spice.patcher.lwjgl.EssentialGlobalMouseOverrideTransformer 4 | import org.polyfrost.spice.patcher.lwjgl.LwjglTransformer 5 | import org.polyfrost.spice.platform.api.Transformer 6 | 7 | fun bootstrapTransformer(transformer: Transformer) { 8 | transformer.appendToClassPath(LwjglTransformer.provider.url) 9 | transformer.addTransformer(EssentialGlobalMouseOverrideTransformer) 10 | } 11 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/platform/api/IClassTransformer.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.platform.api 2 | 3 | import org.objectweb.asm.tree.ClassNode 4 | 5 | interface IClassTransformer { 6 | /** 7 | * @return The class names that this transformer should transform 8 | * If null, the transformer will transform all classes 9 | * Format like net.minecraft.client.Minecraft, not like net/minecraft/client/Minecraft 10 | */ 11 | val targets: Array? 12 | 13 | fun transform(node: ClassNode) 14 | } 15 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/platform/api/Platform.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.platform.api 2 | 3 | interface Platform { 4 | val id: ID 5 | 6 | enum class ID { 7 | Agent, 8 | Forge, 9 | Fabric; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/platform/api/Transformer.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.platform.api 2 | 3 | import java.net.URL 4 | 5 | interface Transformer { 6 | fun addTransformer(transformer: IClassTransformer) 7 | 8 | fun appendToClassPath(url: URL) 9 | } -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/util/OptifineUtil.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.util 2 | 3 | fun isOptifineLoaded(): Boolean = 4 | try { 5 | Class.forName("net.optifine.Lang") 6 | 7 | System.getProperty("spice.forcePerfPatches", "false") != "true" 8 | } catch (_: ClassNotFoundException) { 9 | false 10 | } 11 | 12 | fun getOptifineVersion(): String = 13 | Class.forName("net.optifine.Config").getField("VERSION").get(null) as String 14 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/util/SystemUtil.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.util 2 | 3 | import org.lwjgl.system.Platform 4 | import org.lwjgl.system.Platform.MACOSX 5 | 6 | fun isMac(): Boolean = 7 | Platform.get() == MACOSX 8 | -------------------------------------------------------------------------------- /modules/core/src/main/kotlin/org/polyfrost/spice/util/UrlConnection.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.util 2 | 3 | import java.io.ByteArrayInputStream 4 | import java.io.InputStream 5 | import java.net.URL 6 | import java.net.URLConnection 7 | 8 | class UrlByteArrayConnection(private val bytes: ByteArray, url: URL) : URLConnection(url) { 9 | override fun connect() = 10 | throw UnsupportedOperationException() 11 | 12 | override fun getInputStream(): InputStream = 13 | ByteArrayInputStream(bytes) 14 | } 15 | -------------------------------------------------------------------------------- /modules/lwjgl/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.github.johnrengelman.shadow") 3 | } 4 | 5 | version = rootProject.libs.versions.lwjgl 6 | 7 | dependencies { 8 | val lwjglVersion = rootProject.libs.versions.lwjgl 9 | val platforms = arrayOf( 10 | "linux", 11 | "windows", "windows-x86", 12 | "macos-arm64", "macos" 13 | ) 14 | 15 | implementation(rootProject.libs.bundles.lwjgl) 16 | 17 | platforms.forEach { implementation("org.lwjgl:lwjgl:$lwjglVersion:natives-$it") } 18 | 19 | arrayOf( 20 | "opengl", 21 | "openal", 22 | "glfw" 23 | ).forEach { module -> 24 | platforms.forEach { runtimeOnly("org.lwjgl:lwjgl-$module:$lwjglVersion:natives-$it") } 25 | } 26 | } 27 | 28 | tasks.jar { 29 | archiveFileName = "lwjgl-no-deps.jar" 30 | destinationDirectory.set(layout.buildDirectory.dir("badjars")) 31 | } 32 | 33 | tasks.shadowJar { 34 | archiveFileName = "lwjgl-bundle" 35 | } 36 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/lwjgl/LWJGLException.kt: -------------------------------------------------------------------------------- 1 | package org.lwjgl 2 | 3 | @Suppress("unused") 4 | class LWJGLException : Exception { 5 | constructor() : super() 6 | constructor(cause: Throwable) : super(cause) 7 | constructor(message: String) : super(message) 8 | constructor(message: String, cause: Throwable) : super(message, cause) 9 | } 10 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/lwjgl/Sys.kt: -------------------------------------------------------------------------------- 1 | package org.lwjgl 2 | 3 | object Sys { 4 | @JvmStatic 5 | fun getVersion(): String = Version.getVersion() 6 | @JvmStatic 7 | fun getTime(): Long = System.nanoTime() 8 | @JvmStatic 9 | fun getTimerResolution(): Long = 1000000000 10 | @JvmStatic 11 | fun initialize() { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/lwjgl/input/Keyboard.kt: -------------------------------------------------------------------------------- 1 | package org.lwjgl.input 2 | 3 | import org.polyfrost.lwjgl.api.input.IKeyboard 4 | import kotlin.reflect.jvm.javaField 5 | 6 | object Keyboard { 7 | internal lateinit var implementation: IKeyboard 8 | 9 | @JvmStatic fun create() {} 10 | 11 | @JvmStatic 12 | fun destroy() { 13 | implementation.destroy() 14 | 15 | ::implementation.javaField?.set(null, null) // horrible practice grrr I hate... 16 | } 17 | 18 | @JvmStatic fun isCreated(): Boolean = ::implementation.isInitialized 19 | 20 | @JvmStatic fun areRepeatEventsEnabled(): Boolean = implementation.areRepeatEventsEnabled() 21 | @JvmStatic fun enableRepeatEvents(enable: Boolean) = implementation.enableRepeatEvents(enable) 22 | 23 | @JvmStatic fun getKeyCount(): Int = implementation.getKeyCount() 24 | @JvmStatic fun getKeyIndex(name: String): Int = implementation.getKeyIndex(name) 25 | @JvmStatic fun getKeyName(key: Int): String = implementation.getKeyName(key) 26 | @JvmStatic fun isKeyDown(key: Int): Boolean = implementation.isKeyDown(key) 27 | 28 | @JvmStatic fun getEventKey(): Int = implementation.getEventKey() 29 | @JvmStatic fun getEventCharacter(): Char = implementation.getEventCharacter() 30 | @JvmStatic fun getEventKeyState(): Boolean = implementation.getEventKeyState() 31 | @JvmStatic fun getEventNanoseconds(): Long = implementation.getEventNanoseconds() 32 | @JvmStatic fun isRepeatEvent(): Boolean = implementation.isRepeatEvent() 33 | 34 | @JvmStatic fun getNumKeyboardEvents(): Int = implementation.getNumKeyboardEvents() 35 | 36 | @JvmStatic fun next(): Boolean = implementation.next() 37 | @JvmStatic fun poll() = implementation.poll() 38 | 39 | const val KEY_NONE = 0x00 40 | const val KEY_ESCAPE = 0x01 41 | const val KEY_1 = 0x02 42 | const val KEY_2 = 0x03 43 | const val KEY_3 = 0x04 44 | const val KEY_4 = 0x05 45 | const val KEY_5 = 0x06 46 | const val KEY_6 = 0x07 47 | const val KEY_7 = 0x08 48 | const val KEY_8 = 0x09 49 | const val KEY_9 = 0x0A 50 | const val KEY_0 = 0x0B 51 | const val KEY_MINUS = 0x0C 52 | const val KEY_EQUALS = 0x0D 53 | const val KEY_BACK = 0x0E 54 | const val KEY_TAB = 0x0F 55 | const val KEY_Q = 0x10 56 | const val KEY_W = 0x11 57 | const val KEY_E = 0x12 58 | const val KEY_R = 0x13 59 | const val KEY_T = 0x14 60 | const val KEY_Y = 0x15 61 | const val KEY_U = 0x16 62 | const val KEY_I = 0x17 63 | const val KEY_O = 0x18 64 | const val KEY_P = 0x19 65 | const val KEY_LBRACKET = 0x1A 66 | const val KEY_RBRACKET = 0x1B 67 | const val KEY_RETURN = 0x1C 68 | const val KEY_LCONTROL = 0x1D 69 | const val KEY_A = 0x1E 70 | const val KEY_S = 0x1F 71 | const val KEY_D = 0x20 72 | const val KEY_F = 0x21 73 | const val KEY_G = 0x22 74 | const val KEY_H = 0x23 75 | const val KEY_J = 0x24 76 | const val KEY_K = 0x25 77 | const val KEY_L = 0x26 78 | const val KEY_SEMICOLON = 0x27 79 | const val KEY_APOSTROPHE = 0x28 80 | const val KEY_GRAVE = 0x29 81 | const val KEY_LSHIFT = 0x2A 82 | const val KEY_BACKSLASH = 0x2B 83 | const val KEY_Z = 0x2C 84 | const val KEY_X = 0x2D 85 | const val KEY_C = 0x2E 86 | const val KEY_V = 0x2F 87 | const val KEY_B = 0x30 88 | const val KEY_N = 0x31 89 | const val KEY_M = 0x32 90 | const val KEY_COMMA = 0x33 91 | const val KEY_PERIOD = 0x34 92 | const val KEY_SLASH = 0x35 93 | const val KEY_RSHIFT = 0x36 94 | const val KEY_MULTIPLY = 0x37 95 | const val KEY_LMENU = 0x38 96 | const val KEY_SPACE = 0x39 97 | const val KEY_CAPITAL = 0x3A 98 | const val KEY_F1 = 0x3B 99 | const val KEY_F2 = 0x3C 100 | const val KEY_F3 = 0x3D 101 | const val KEY_F4 = 0x3E 102 | const val KEY_F5 = 0x3F 103 | const val KEY_F6 = 0x40 104 | const val KEY_F7 = 0x41 105 | const val KEY_F8 = 0x42 106 | const val KEY_F9 = 0x43 107 | const val KEY_F10 = 0x44 108 | const val KEY_NUMLOCK = 0x45 109 | const val KEY_SCROLL = 0x46 110 | const val KEY_NUMPAD7 = 0x47 111 | const val KEY_NUMPAD8 = 0x48 112 | const val KEY_NUMPAD9 = 0x49 113 | const val KEY_SUBTRACT = 0x4A 114 | const val KEY_NUMPAD4 = 0x4B 115 | const val KEY_NUMPAD5 = 0x4C 116 | const val KEY_NUMPAD6 = 0x4D 117 | const val KEY_ADD = 0x4E 118 | const val KEY_NUMPAD1 = 0x4F 119 | const val KEY_NUMPAD2 = 0x50 120 | const val KEY_NUMPAD3 = 0x51 121 | const val KEY_NUMPAD0 = 0x52 122 | const val KEY_DECIMAL = 0x53 123 | const val KEY_F11 = 0x57 124 | const val KEY_F12 = 0x58 125 | const val KEY_F13 = 0x64 126 | const val KEY_F14 = 0x65 127 | const val KEY_F15 = 0x66 128 | const val KEY_F16 = 0x67 129 | const val KEY_F17 = 0x68 130 | const val KEY_F18 = 0x69 131 | const val KEY_KANA = 0x70 132 | const val KEY_F19 = 0x71 133 | const val KEY_CONVERT = 0x79 134 | const val KEY_NOCONVERT = 0x7B 135 | const val KEY_YEN = 0x7D 136 | const val KEY_NUMPADEQUALS = 0x8D 137 | const val KEY_CIRCUMFLEX = 0x90 138 | const val KEY_AT = 0x91 139 | const val KEY_COLON = 0x92 140 | const val KEY_UNDERLINE = 0x93 141 | const val KEY_KANJI = 0x94 142 | const val KEY_STOP = 0x95 143 | const val KEY_AX = 0x96 144 | const val KEY_UNLABELED = 0x97 145 | const val KEY_NUMPADENTER = 0x9C 146 | const val KEY_RCONTROL = 0x9D 147 | const val KEY_SECTION = 0xA7 148 | const val KEY_NUMPADCOMMA = 0xB3 149 | const val KEY_DIVIDE = 0xB5 150 | const val KEY_SYSRQ = 0xB7 151 | const val KEY_RMENU = 0xB8 152 | const val KEY_FUNCTION = 0xC4 153 | const val KEY_PAUSE = 0xC5 154 | const val KEY_HOME = 0xC7 155 | const val KEY_UP = 0xC8 156 | const val KEY_PRIOR = 0xC9 157 | const val KEY_LEFT = 0xCB 158 | const val KEY_RIGHT = 0xCD 159 | const val KEY_END = 0xCF 160 | const val KEY_DOWN = 0xD0 161 | const val KEY_NEXT = 0xD1 162 | const val KEY_INSERT = 0xD2 163 | const val KEY_DELETE = 0xD3 164 | const val KEY_CLEAR = 0xDA 165 | const val KEY_LMETA = 0xDB 166 | const val KEY_LWIN = KEY_LMETA 167 | const val KEY_RMETA = 0xDC 168 | const val KEY_RWIN = KEY_RMETA 169 | const val KEY_APPS = 0xDD 170 | const val KEY_POWER = 0xDE 171 | const val KEY_SLEEP = 0xDF 172 | } -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/lwjgl/input/Mouse.kt: -------------------------------------------------------------------------------- 1 | package org.lwjgl.input 2 | 3 | import org.polyfrost.lwjgl.api.input.IMouse 4 | import kotlin.reflect.jvm.javaField 5 | 6 | object Mouse { 7 | internal lateinit var implementation: IMouse 8 | 9 | @JvmStatic fun create() {} 10 | 11 | @JvmStatic 12 | fun destroy() { 13 | implementation.destroy() 14 | 15 | ::implementation.javaField?.set(null, null) // horrible practice grrr I hate... 16 | } 17 | 18 | @JvmStatic fun isCreated() = ::implementation.isInitialized 19 | 20 | @JvmStatic fun getX(): Int = implementation.getX() 21 | @JvmStatic fun getY(): Int = implementation.getY() 22 | @JvmStatic fun setX(x: Int) = implementation.setX(x) 23 | @JvmStatic fun setY(y: Int) = implementation.setY(y) 24 | @JvmStatic fun getDX(): Int = implementation.getDX() 25 | @JvmStatic fun getDY(): Int = implementation.getDY() 26 | @JvmStatic fun getDWheel(): Int = implementation.getDWheel() 27 | 28 | @JvmStatic fun getEventButton(): Int = implementation.getEventButton() 29 | @JvmStatic fun getEventButtonState(): Boolean = implementation.getEventButtonState() 30 | @JvmStatic fun getEventDWheel(): Int = implementation.getEventDWheel() 31 | @JvmStatic fun getEventDX(): Int = implementation.getEventDX() 32 | @JvmStatic fun getEventDY(): Int = implementation.getEventDY() 33 | @JvmStatic fun getEventX(): Int = implementation.getEventX() 34 | @JvmStatic fun getEventY(): Int = implementation.getEventY() 35 | @JvmStatic fun setEventX(x: Int) = implementation.setEventX(x) 36 | @JvmStatic fun setEventY(y: Int) = implementation.setEventY(y) 37 | @JvmStatic fun getEventNanoseconds(): Long = implementation.getEventNanoseconds() 38 | 39 | @JvmStatic fun next(): Boolean = implementation.next() 40 | @JvmStatic fun poll() = implementation.poll() 41 | 42 | @JvmStatic fun getButtonCount(): Int = implementation.getButtonCount() 43 | @JvmStatic fun getButtonIndex(name: String): Int = implementation.getButtonIndex(name) 44 | @JvmStatic fun getButtonName(button: Int): String = implementation.getButtonName(button) 45 | @JvmStatic fun isButtonDown(button: Int): Boolean = implementation.isButtonDown(button) 46 | 47 | @JvmStatic fun hasWheel(): Boolean = true 48 | 49 | @JvmStatic fun isGrabbed(): Boolean = implementation.isGrabbed() 50 | @JvmStatic fun setGrabbed(grabbed: Boolean) = implementation.setGrabbed(grabbed) 51 | 52 | @JvmStatic fun setCursorPosition(x: Int, y: Int) = implementation.setCursorPosition(x, y) 53 | 54 | @JvmStatic fun isInsideWindow(): Boolean = implementation.isInsideWindow() 55 | } -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/lwjgl/opengl/ContextAttribs.kt: -------------------------------------------------------------------------------- 1 | package org.lwjgl.opengl 2 | 3 | import org.lwjgl.opengl.GLXARBContextFlushControl.* 4 | import org.lwjgl.opengl.GLXARBCreateContext.* 5 | import org.lwjgl.opengl.GLXARBCreateContextProfile.* 6 | import org.lwjgl.opengl.GLXARBCreateContextRobustness.* 7 | import org.lwjgl.opengl.GLXARBRobustnessApplicationIsolation.GLX_CONTEXT_RESET_ISOLATION_BIT_ARB 8 | import org.lwjgl.opengl.GLXEXTCreateContextES2Profile.GLX_CONTEXT_ES2_PROFILE_BIT_EXT 9 | import org.lwjgl.opengl.WGLARBCreateContext.WGL_CONTEXT_LAYER_PLANE_ARB 10 | 11 | class ContextAttribs : Cloneable { 12 | private var majorVersion: Int 13 | private var minorVersion: Int 14 | 15 | private var profileMask: Int 16 | private var contextFlags: Int 17 | 18 | private var resetNotificationStrategy = NO_RESET_NOTIFICATION_ARB 19 | private var releaseBehavior = CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 20 | 21 | private var layerPlane: Int = 0 22 | 23 | constructor() : this(1, 0) 24 | 25 | @JvmOverloads 26 | constructor(major: Int, minor: Int, profileMask: Int = CONTEXT_CORE_PROFILE_BIT_ARB, contextFlags: Int = 0) { 27 | majorVersion = major 28 | minorVersion = minor 29 | 30 | this.profileMask = profileMask 31 | this.contextFlags = contextFlags 32 | } 33 | 34 | fun getMajorVersion(): Int = majorVersion 35 | fun getMinorVersion(): Int = minorVersion 36 | 37 | fun getProfileMask(): Int = profileMask 38 | fun getContextFlags(): Int = contextFlags 39 | 40 | fun getContextReleaseBehavior(): Int = releaseBehavior 41 | 42 | fun getLayerPlane(): Int = layerPlane 43 | 44 | fun isLoseContextOnReset(): Boolean = resetNotificationStrategy == LOSE_CONTEXT_ON_RESET_ARB 45 | fun isContextResetIsolation(): Boolean = hasFlag(CONTEXT_RESET_ISOLATION_BIT_ARB) 46 | fun getContextResetNotificationStrategy(): Int = resetNotificationStrategy 47 | 48 | 49 | fun isDebug(): Boolean = hasFlag(CONTEXT_DEBUG_BIT_ARB) 50 | fun isForwardCompatible(): Boolean = hasFlag(CONTEXT_FORWARD_COMPATIBLE_BIT_ARB) 51 | fun isRobustAccess(): Boolean = hasFlag(CONTEXT_ROBUST_ACCESS_BIT_ARB) 52 | 53 | fun isProfileCore(): Boolean = hasProfile(CONTEXT_CORE_PROFILE_BIT_ARB) 54 | fun isProfileCompatibility(): Boolean = hasProfile(CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB) 55 | fun isProfileES(): Boolean = hasProfile(CONTEXT_ES2_PROFILE_BIT_EXT) 56 | 57 | fun withContextReleaseBehavior(behavior: Int): ContextAttribs = 58 | clone().also { it.releaseBehavior = behavior } 59 | 60 | fun withLayer(layer: Int): ContextAttribs = 61 | clone().also { it.layerPlane = layer } 62 | 63 | fun withLoseContextOnReset(lose: Boolean): ContextAttribs = 64 | clone().also { 65 | if (lose) it.resetNotificationStrategy = LOSE_CONTEXT_ON_RESET_ARB 66 | else it.resetNotificationStrategy = NO_RESET_NOTIFICATION_ARB 67 | } 68 | 69 | fun withContextResetIsolation(isolation: Boolean): ContextAttribs = 70 | toggleFlag(CONTEXT_RESET_ISOLATION_BIT_ARB, isolation) 71 | 72 | fun withResetNotificationStrategy(strategy: Int): ContextAttribs = 73 | clone().also { it.resetNotificationStrategy = strategy } 74 | 75 | fun withDebug(debug: Boolean): ContextAttribs = 76 | toggleFlag(CONTEXT_DEBUG_BIT_ARB, debug) 77 | 78 | fun withForwardCompatible(compat: Boolean): ContextAttribs = 79 | toggleFlag(CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, compat) 80 | 81 | fun withRobustAccess(access: Boolean): ContextAttribs = 82 | toggleFlag(CONTEXT_ROBUST_ACCESS_BIT_ARB, access) 83 | 84 | fun withProfileCore(core: Boolean): ContextAttribs = 85 | toggleProfile(CONTEXT_CORE_PROFILE_BIT_ARB, core) 86 | 87 | fun withProfileCompatibility(compat: Boolean): ContextAttribs = 88 | toggleProfile(CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, compat) 89 | 90 | fun withProfileES(es: Boolean): ContextAttribs = 91 | toggleProfile(CONTEXT_ES2_PROFILE_BIT_EXT, es) 92 | 93 | private fun hasProfile(profile: Int) = profile == profileMask 94 | private fun hasFlag(flag: Int): Boolean = contextFlags and flag != 0 95 | 96 | private fun toggleProfile(profile: Int, enabled: Boolean): ContextAttribs = 97 | if (enabled == hasProfile(profile)) this 98 | else clone().also { it.profileMask = if (enabled) profile else 0 } 99 | 100 | private fun toggleFlag(flag: Int, enabled: Boolean): ContextAttribs = 101 | if (enabled == hasFlag(flag)) this 102 | else clone().also { it.contextFlags = it.contextFlags xor flag } 103 | 104 | override fun clone(): ContextAttribs = super.clone() as ContextAttribs 105 | 106 | companion object { 107 | @JvmStatic 108 | val CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 109 | @JvmStatic 110 | val CONTEXT_CORE_PROFILE_BIT_ARB = GLX_CONTEXT_CORE_PROFILE_BIT_ARB 111 | @JvmStatic 112 | val CONTEXT_DEBUG_BIT_ARB = GLX_CONTEXT_CORE_PROFILE_BIT_ARB 113 | @JvmStatic 114 | val CONTEXT_ES2_PROFILE_BIT_EXT = GLX_CONTEXT_ES2_PROFILE_BIT_EXT 115 | @JvmStatic 116 | val CONTEXT_FLAGS_ARB = GLX_CONTEXT_FLAGS_ARB 117 | @JvmStatic 118 | val CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 119 | @JvmStatic 120 | val CONTEXT_LAYER_PLANE_ARB = WGL_CONTEXT_LAYER_PLANE_ARB 121 | @JvmStatic 122 | val CONTEXT_MAJOR_VERSION_ARB = GLX_CONTEXT_MAJOR_VERSION_ARB 123 | @JvmStatic 124 | val CONTEXT_MINOR_VERSION_ARB = GLX_CONTEXT_MINOR_VERSION_ARB 125 | @JvmStatic 126 | val CONTEXT_PROFILE_MASK_ARB = GLX_CONTEXT_PROFILE_MASK_ARB 127 | @JvmStatic 128 | val CONTEXT_RELEASE_BEHAVIOR_ARB = GLX_CONTEXT_RELEASE_BEHAVIOR_ARB 129 | 130 | // this is *intentional*! lwjgl2 had this typo, so unfortunately we need to keep it 131 | @JvmStatic 132 | val CONTEXT_RELEASE_BEHABIOR_ARB = CONTEXT_RELEASE_BEHAVIOR_ARB 133 | @JvmStatic 134 | val CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB = GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 135 | @JvmStatic 136 | val CONTEXT_RELEASE_BEHAVIOR_NONE_ARB = GLX_CONTEXT_RELEASE_BEHAVIOR_NONE_ARB 137 | @JvmStatic 138 | val CONTEXT_RESET_ISOLATION_BIT_ARB = GLX_CONTEXT_RESET_ISOLATION_BIT_ARB 139 | @JvmStatic 140 | val CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB = GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB 141 | @JvmStatic 142 | val CONTEXT_ROBUST_ACCESS_BIT_ARB = GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB 143 | @JvmStatic 144 | val LOSE_CONTEXT_ON_RESET_ARB = GLX_LOSE_CONTEXT_ON_RESET_ARB 145 | @JvmStatic 146 | val NO_RESET_NOTIFICATION_ARB = GLX_NO_RESET_NOTIFICATION_ARB 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/lwjgl/opengl/ContextCapabilities.kt: -------------------------------------------------------------------------------- 1 | package org.lwjgl.opengl 2 | 3 | class ContextCapabilities { 4 | init { 5 | val capabilities = GL.getCapabilities() 6 | 7 | GLCapabilities::class 8 | .java 9 | .fields 10 | .forEach { 11 | try { 12 | val field = ContextCapabilities::class.java.getField(it.name) 13 | 14 | field.set(this, it.get(capabilities)) 15 | } catch (_: Throwable) { 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/lwjgl/opengl/Display.kt: -------------------------------------------------------------------------------- 1 | package org.lwjgl.opengl 2 | 3 | import org.lwjgl.BufferUtils 4 | import org.lwjglx.system.Monitor 5 | import org.polyfrost.lwjgl.api.opengl.CreationParameters 6 | import org.polyfrost.lwjgl.api.opengl.IDisplay 7 | import org.polyfrost.lwjgl.impl.display.OpenGlDisplay 8 | import org.polyfrost.lwjgl.util.toInt 9 | import java.awt.Canvas 10 | import java.nio.ByteBuffer 11 | 12 | object Display { 13 | private var implementation: IDisplay? = null 14 | 15 | private var icon = arrayOf() 16 | 17 | private var title: String? = null 18 | private var resizable: Boolean? = null 19 | private var displayMode: DisplayMode? = null 20 | private var vsync: Boolean? = null 21 | private var swapInterval: Int? = null 22 | private var fullscreen: Boolean? = null 23 | 24 | /** 25 | * Create the OpenGL context with the given minimum parameters. 26 | * 27 | * @param format the desired pixel format 28 | * @param attribs the context's attributes 29 | */ 30 | @JvmStatic 31 | @JvmOverloads 32 | fun create(format: PixelFormat = PixelFormat(), attribs: ContextAttribs = ContextAttribs()) { 33 | // todo: lifecycle events 34 | implementation = OpenGlDisplay( 35 | CreationParameters( 36 | displayMode, 37 | title, 38 | resizable, 39 | vsync?.toInt() ?: swapInterval ?: 0, 40 | fullscreen 41 | ), 42 | format, 43 | attribs 44 | ) 45 | 46 | if (icon.isNotEmpty()) implementation!!.setIcon(icon) 47 | } 48 | 49 | /** 50 | * Create the OpenGL context with the given minimum parameters. 51 | * 52 | * @param format the desired pixel format 53 | * @param sharedDrawable the drawable with which the display shares its context 54 | * @param attribs the context's attributes 55 | */ 56 | @JvmStatic 57 | @JvmOverloads 58 | fun create(format: PixelFormat, sharedDrawable: Drawable, attribs: ContextAttribs = ContextAttribs()) { 59 | 60 | } 61 | 62 | @JvmStatic 63 | fun getAdapter(): String = useImpl { getAdapter() } ?: "N/A" 64 | @JvmStatic 65 | fun getVersion(): String = useImpl { getVersion() } ?: "N/A" 66 | @JvmStatic 67 | fun getDisplayMode(): DisplayMode = useImpl { getDisplayMode() } ?: displayMode ?: throw NullPointerException() 68 | 69 | @JvmStatic 70 | fun setDisplayMode(mode: DisplayMode) { 71 | withImpl({ setDisplayMode(mode) }) { displayMode = mode } 72 | } 73 | 74 | @JvmStatic 75 | fun setDisplayModeAndFullscreen(mode: DisplayMode) { 76 | withImpl({ setDisplayModeAndFullscreen(mode) }) { 77 | displayMode = mode 78 | fullscreen = true 79 | } 80 | } 81 | 82 | @JvmStatic 83 | fun getDesktopDisplayMode(): DisplayMode = Monitor.getPrimaryMonitor().getDisplayMode() 84 | @JvmStatic 85 | fun getAvailableDisplayModes(): Array = Monitor.getPrimaryMonitor().getAvailableDisplayModes() 86 | @JvmStatic 87 | fun getDrawable(): Drawable = useImpl { getDrawable() } ?: throw NullPointerException() 88 | @JvmStatic 89 | fun getParent(): Canvas? = useImpl { getParent() } 90 | @JvmStatic 91 | fun setParent(parent: Canvas?) { 92 | withImpl { setParent(parent) } 93 | } 94 | 95 | @JvmStatic 96 | fun setInitialBackground(red: Float, green: Float, blue: Float) { 97 | withImpl { setInitialBackground(red, green, blue) } 98 | } 99 | 100 | @JvmStatic 101 | fun getWidth(): Int = useImpl { getWidth() } ?: 0 102 | @JvmStatic 103 | fun getHeight(): Int = useImpl { getHeight() } ?: 0 104 | @JvmStatic 105 | fun getX(): Int = useImpl { getX() } ?: 0 106 | @JvmStatic 107 | fun getY(): Int = useImpl { getY() } ?: 0 108 | 109 | @JvmStatic 110 | fun setLocation(x: Int, y: Int) { 111 | withImpl { setLocation(x, y) } 112 | } 113 | 114 | @JvmStatic 115 | fun getTitle(): String = useImpl { getTitle() } ?: "LWJGL" 116 | 117 | @JvmStatic 118 | fun setTitle(title: String) { 119 | withImpl({ setTitle(title) }) { this.title = title } 120 | } 121 | 122 | @JvmStatic 123 | fun getPixelScaleFactor(): Float = useImpl { getPixelScaleFactor() } ?: 1.0f 124 | 125 | @JvmStatic 126 | fun setDisplayConfiguration(gamma: Float, brightness: Float, contrast: Float) { 127 | withImpl { setDisplayConfiguration(gamma, brightness, contrast) } 128 | } 129 | 130 | @JvmStatic 131 | fun isActive(): Boolean = useImpl { isActive() } ?: false 132 | @JvmStatic 133 | fun isCreated(): Boolean = implementation != null 134 | 135 | @JvmStatic 136 | fun setIcon(icons: Array): Int = 137 | useImpl({ setIcon(icons) }) { 138 | icon = icons.map { icon -> 139 | val clone = BufferUtils.createByteBuffer(icon.limit()) 140 | val start = icon.position() 141 | 142 | icon.position(0) 143 | 144 | clone.put(icon) 145 | clone.flip() 146 | 147 | icon.position(start) 148 | 149 | clone 150 | }.toTypedArray() 151 | 0 152 | } 153 | 154 | @JvmStatic 155 | fun isCurrent(): Boolean = useImpl { isCurrent() } ?: false 156 | @JvmStatic 157 | fun isDirty(): Boolean = useImpl { isDirty() } ?: false 158 | @JvmStatic 159 | fun isVisible(): Boolean = useImpl { isVisible() } ?: false 160 | @JvmStatic 161 | fun isResizable(): Boolean = useImpl { isResizable() } ?: resizable ?: true 162 | 163 | @JvmStatic 164 | fun setResizable(resizable: Boolean) { 165 | withImpl({ setResizable(resizable) }) { this.resizable = resizable } 166 | } 167 | 168 | @JvmStatic 169 | fun isFullscreen(): Boolean = 170 | useImpl { isFullscreen() } ?: fullscreen ?: false 171 | 172 | @JvmStatic 173 | fun setFullscreen(enabled: Boolean) { 174 | withImpl({ setFullscreen(enabled) }) { fullscreen = enabled } 175 | } 176 | 177 | @JvmStatic 178 | fun isCloseRequested(): Boolean = useImpl { isCloseRequested() } ?: false 179 | @JvmStatic 180 | fun wasResized(): Boolean = useImpl { wasResized() } ?: false 181 | 182 | @JvmStatic 183 | fun makeCurrent() { 184 | withImpl { makeCurrent() } 185 | } 186 | 187 | @JvmStatic 188 | fun releaseContext() { 189 | withImpl { Display.releaseContext() } 190 | } 191 | 192 | @JvmStatic 193 | fun destroy() { 194 | withImpl { destroy() } 195 | } 196 | 197 | @JvmStatic 198 | fun sync(fps: Int) { 199 | withImpl { sync(fps) } 200 | } 201 | 202 | @JvmStatic 203 | fun setSwapInterval(interval: Int) { 204 | withImpl({ setSwapInterval(interval) }) { swapInterval = interval } 205 | } 206 | 207 | @JvmStatic 208 | fun setVSyncEnabled(sync: Boolean) { 209 | withImpl({ setVSyncEnabled(sync) }) { vsync = sync } 210 | } 211 | 212 | @JvmStatic 213 | fun update() { withImpl { update() } } 214 | @JvmStatic 215 | fun update(process: Boolean) { withImpl { update(process) } } 216 | 217 | @JvmStatic 218 | fun processMessages() { withImpl { processMessages() } } 219 | @JvmStatic 220 | fun swapBuffers() { withImpl { swapBuffers() } } 221 | 222 | @JvmStatic 223 | private fun withImpl(block: IDisplay.() -> Unit) = implementation?.block() 224 | @JvmStatic 225 | private fun withImpl(block: IDisplay.() -> Unit, otherwise: () -> Unit) = implementation?.block() ?: otherwise() 226 | 227 | @JvmStatic 228 | private fun useImpl(block: IDisplay.() -> R): R? = implementation?.block() 229 | @JvmStatic 230 | private fun useImpl(block: IDisplay.() -> R, otherwise: () -> R): R = implementation?.block() ?: otherwise() 231 | } 232 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/lwjgl/opengl/DisplayMode.kt: -------------------------------------------------------------------------------- 1 | package org.lwjgl.opengl 2 | 3 | @Suppress("unused") 4 | class DisplayMode internal constructor( 5 | private val width: Int, 6 | private val height: Int, 7 | private val frequency: Int, 8 | private val bitDepth: Int 9 | ) { 10 | constructor(width: Int, height: Int) : this(width, height, 0, 0) 11 | 12 | fun getWidth(): Int = width 13 | fun getHeight(): Int = height 14 | fun getFrequency(): Int = frequency 15 | fun getBitsPerPixel(): Int = bitDepth 16 | } 17 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/lwjgl/opengl/Drawable.kt: -------------------------------------------------------------------------------- 1 | package org.lwjgl.opengl 2 | 3 | import org.lwjgl.PointerBuffer 4 | 5 | interface Drawable { 6 | fun makeCurrent() 7 | fun isCurrent(): Boolean 8 | 9 | fun releaseContext() 10 | fun destroy() 11 | 12 | fun setCLSharingProperties(properties: PointerBuffer) 13 | } 14 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/lwjgl/opengl/GLContext.kt: -------------------------------------------------------------------------------- 1 | package org.lwjgl.opengl 2 | 3 | import kotlin.concurrent.getOrSet 4 | 5 | object GLContext { 6 | private val capabilities = ThreadLocal() 7 | 8 | @JvmStatic 9 | fun getCapabilities(): ContextCapabilities { 10 | return capabilities.getOrSet { 11 | ContextCapabilities() 12 | } 13 | } 14 | 15 | @JvmStatic 16 | fun getFunctionAddress(function: String): Long = 17 | GL.getFunctionProvider()?.getFunctionAddress(function) ?: 0 18 | } 19 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/lwjgl/opengl/PixelFormat.kt: -------------------------------------------------------------------------------- 1 | package org.lwjgl.opengl 2 | 3 | /** 4 | * This class describes pixel format properties for an OpenGL context. 5 | * Instants of this class are immutable. An example of the expected way to set the PixelFormat property values is the following: PixelFormat pf = new PixelFormat().withDepthBits(24).withSamples(4).withSRGB(true); 6 | * 7 | * WARNING: Some pixel formats are known to cause troubles on certain buggy drivers. Example: Under Windows, specifying samples != 0 will enable the ARB pixel format selection path, which could trigger a crash. 8 | */ 9 | class PixelFormat : Cloneable { 10 | private var bitsPerPixel: Int 11 | private var alpha: Int 12 | private var depth: Int 13 | private var stencil: Int 14 | private var samples: Int 15 | private var colorSamples: Int = 0 16 | private var auxBuffers: Int 17 | private var accumulationBitsPerPixel: Int 18 | private var accumulationAlpha: Int 19 | private var stereo: Boolean 20 | private var floatingPoint: Boolean 21 | private var floatingPointPacked: Boolean = false 22 | private var srgb: Boolean = false 23 | 24 | /** 25 | * Default pixel format is minimum 8 bits depth, and no alpha nor stencil requirements. 26 | */ 27 | constructor() : this(alpha = 0, depth = 8, stencil = 0) 28 | 29 | @JvmOverloads 30 | constructor( 31 | bitsPerPixel: Int = 24, 32 | alpha: Int, 33 | depth: Int, 34 | stencil: Int, 35 | samples: Int = 0 36 | ) : this( 37 | bitsPerPixel, 38 | alpha, 39 | depth, 40 | stencil, 41 | samples, 42 | 0, 43 | 0, 44 | 0, 45 | false 46 | ) 47 | 48 | @JvmOverloads 49 | constructor( 50 | bitsPerPixel: Int, 51 | alpha: Int, 52 | depth: Int, 53 | stencil: Int, 54 | samples: Int, 55 | auxBuffers: Int, 56 | accumulationBitsPerPixel: Int, 57 | accumulationAlpha: Int, 58 | stereo: Boolean, 59 | floatingPoint: Boolean = false 60 | ) { 61 | this.bitsPerPixel = bitsPerPixel 62 | this.alpha = alpha 63 | this.depth = depth 64 | this.stencil = stencil 65 | this.samples = samples 66 | this.auxBuffers = auxBuffers 67 | this.accumulationBitsPerPixel = accumulationBitsPerPixel 68 | this.accumulationAlpha = accumulationAlpha 69 | this.stereo = stereo 70 | this.floatingPoint = floatingPoint 71 | } 72 | 73 | fun getAccumulationAlpha(): Int = accumulationAlpha 74 | fun getAccumulationBitsPerPixel(): Int = accumulationBitsPerPixel 75 | fun getAlphaBits(): Int = alpha 76 | fun getDepthBits(): Int = depth 77 | fun getStencilBits(): Int = stencil 78 | fun getBitsPerPixel(): Int = bitsPerPixel 79 | fun getSamples(): Int = samples 80 | fun getAuxBuffers(): Int = auxBuffers 81 | fun isSRGB(): Boolean = srgb 82 | fun isStereo(): Boolean = stereo 83 | fun isFloatingPoint(): Boolean = floatingPoint 84 | 85 | /** 86 | * Returns a new PixelFormat object with the same properties as this PixelFormat and the new alpha bits in the accumulation buffer value. 87 | */ 88 | fun withAccumulationAlpha(bits: Int): PixelFormat = 89 | clone().also { it.accumulationAlpha = bits } 90 | 91 | /** 92 | * Returns a new PixelFormat object with the same properties as this PixelFormat and the new bits per pixel in the accumulation buffer value. 93 | */ 94 | fun withAccumulationBitsPerPixel(bits: Int): PixelFormat = 95 | clone().also { it.accumulationBitsPerPixel = bits } 96 | 97 | /** 98 | * Returns a new PixelFormat object with the same properties as this PixelFormat and the new alpha bits value. 99 | */ 100 | fun withAlphaBits(bits: Int): PixelFormat = 101 | clone().also { it.alpha = bits } 102 | 103 | /** 104 | * Returns a new PixelFormat object with the same properties as this PixelFormat and the new depth bits value. 105 | */ 106 | fun withDepthBits(bits: Int): PixelFormat = 107 | clone().also { it.depth = bits } 108 | 109 | /** 110 | * Returns a new PixelFormat object with the same properties as this PixelFormat and the new stencil bits value. 111 | */ 112 | fun withStencilBits(bits: Int): PixelFormat = 113 | clone().also { it.stencil = bits } 114 | 115 | /** 116 | * Returns a new PixelFormat object with the same properties as this PixelFormat and the new bits per pixel value. 117 | */ 118 | fun withBitsPerPixel(bits: Int): PixelFormat = 119 | clone().also { it.bitsPerPixel = bits } 120 | 121 | /** 122 | * Returns a new PixelFormat object with the same properties as this PixelFormat and the new samples value. 123 | */ 124 | fun withSamples(samples: Int) = 125 | clone().also { it.samples = samples } 126 | 127 | /** 128 | * Returns a new PixelFormat object with the same properties as this PixelFormat and the new color samples and coverage samples values. 129 | */ 130 | @JvmOverloads 131 | fun withCoverageSamples(colorSamples: Int, coverageSamples: Int = this.samples): PixelFormat = 132 | clone().also { 133 | it.colorSamples = colorSamples 134 | it.samples = coverageSamples 135 | } 136 | 137 | /** 138 | * Returns a new PixelFormat object with the same properties as this PixelFormat and the new auxiliary buffers value. 139 | */ 140 | fun withAuxBuffers(buffers: Int): PixelFormat = 141 | clone().also { it.auxBuffers = buffers } 142 | 143 | /** 144 | * Returns a new PixelFormat object with the same properties as this PixelFormat and the new sRGB value. 145 | */ 146 | fun withSRGB(srgb: Boolean): PixelFormat = 147 | clone().also { it.srgb = srgb } 148 | 149 | /** 150 | * Returns a new PixelFormat object with the same properties as this PixelFormat and the new stereo value. 151 | */ 152 | fun withStereo(stereo: Boolean): PixelFormat = 153 | clone().also { it.stereo = stereo } 154 | 155 | /** 156 | * Returns a new PixelFormat object with the same properties as this PixelFormat and the new floating point value. 157 | */ 158 | fun withFloatingPoint(floating: Boolean): PixelFormat = 159 | clone().also { 160 | it.floatingPoint = floating 161 | 162 | if (floating) it.floatingPointPacked = false 163 | } 164 | 165 | /** 166 | * Returns a new PixelFormat object with the same properties as this PixelFormat and the new packed floating point value. 167 | */ 168 | fun withFloatingPointPacked(packed: Boolean): PixelFormat = 169 | clone().also { 170 | it.floatingPointPacked = packed 171 | 172 | if (packed) it.floatingPoint = false 173 | } 174 | 175 | override fun clone(): PixelFormat { 176 | return super.clone() as PixelFormat 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/lwjgl/opengl/SharedDrawable.kt: -------------------------------------------------------------------------------- 1 | package org.lwjgl.opengl 2 | 3 | import org.lwjgl.PointerBuffer 4 | import org.polyfrost.lwjgl.api.opengl.IContext 5 | import org.polyfrost.lwjgl.api.opengl.IShareableContext 6 | import org.polyfrost.lwjgl.platform.common.opengl.OpenGlDrawable 7 | import kotlin.reflect.jvm.jvmName 8 | 9 | class SharedDrawable(private val share: Drawable) : Drawable { 10 | internal val context: IContext 11 | 12 | init { 13 | require(share is OpenGlDrawable) { "expected ${OpenGlDrawable::class.jvmName}, got: ${share::class.jvmName}" } 14 | require(share.context is IShareableContext) { "expected a shareable context" } 15 | 16 | context = share.context.makeShared() 17 | } 18 | 19 | override fun makeCurrent() = context.makeCurrent() 20 | 21 | override fun isCurrent(): Boolean = context.isCurrent() 22 | 23 | override fun releaseContext() = context.release() 24 | 25 | override fun destroy() = context.destroy() 26 | 27 | override fun setCLSharingProperties(properties: PointerBuffer) { 28 | TODO("Not yet implemented") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/lwjglx/input/RawInput.kt: -------------------------------------------------------------------------------- 1 | package org.lwjglx.input 2 | 3 | import org.lwjgl.glfw.GLFW.glfwRawMouseMotionSupported 4 | import org.lwjgl.input.Mouse 5 | import org.polyfrost.lwjgl.impl.input.MouseImpl 6 | 7 | object RawInput { 8 | private var useRawInput: Boolean = false 9 | 10 | @JvmStatic fun isRawInputSupported(): Boolean = glfwRawMouseMotionSupported() 11 | 12 | @JvmStatic fun useRawInput(state: Boolean) { 13 | val impl = if (Mouse.isCreated()) Mouse.implementation else null 14 | 15 | useRawInput = state 16 | 17 | if (impl != null && impl is MouseImpl) { 18 | impl.setRawInput(state) 19 | } 20 | } 21 | 22 | @JvmStatic fun isUsingRawInput(): Boolean = useRawInput 23 | } -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/lwjglx/system/Monitor.kt: -------------------------------------------------------------------------------- 1 | package org.lwjglx.system 2 | 3 | import org.lwjgl.glfw.GLFW.* 4 | import org.lwjgl.glfw.GLFWVidMode 5 | import org.lwjgl.opengl.DisplayMode 6 | 7 | class Monitor internal constructor(internal val handle: Long) { 8 | fun getDisplayMode(): DisplayMode = videoModeToDisplayMode(glfwGetVideoMode(handle)!!) 9 | 10 | fun getAvailableDisplayModes(): Array { 11 | val modes = glfwGetVideoModes(handle)!! 12 | val modeArray = Array(modes.limit()) { null } 13 | 14 | for (i in 0.. { 30 | val monitors = glfwGetMonitors() ?: return arrayOf() 31 | val monitorArray = LongArray(monitors.limit()) { 0 } 32 | 33 | return monitorArray 34 | .map { Monitor(it) } 35 | .toTypedArray() 36 | } 37 | 38 | private fun videoModeToDisplayMode(mode: GLFWVidMode): DisplayMode = 39 | DisplayMode( 40 | mode.width(), 41 | mode.height(), 42 | mode.refreshRate(), 43 | mode.redBits() + mode.greenBits() + mode.blueBits() 44 | ) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/polyfrost/lwjgl/api/input/Keyboard.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.lwjgl.api.input 2 | 3 | interface IKeyboard { 4 | fun destroy() 5 | 6 | fun areRepeatEventsEnabled(): Boolean 7 | fun enableRepeatEvents(enable: Boolean) 8 | 9 | fun getKeyCount(): Int 10 | fun getKeyIndex(name: String): Int 11 | fun getKeyName(key: Int): String 12 | fun isKeyDown(key: Int): Boolean 13 | 14 | fun getEventKey(): Int 15 | fun getEventCharacter(): Char 16 | fun getEventKeyState(): Boolean 17 | fun getEventNanoseconds(): Long 18 | fun isRepeatEvent(): Boolean 19 | 20 | fun getNumKeyboardEvents(): Int 21 | 22 | fun next(): Boolean 23 | fun poll() 24 | } 25 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/polyfrost/lwjgl/api/input/Mouse.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.lwjgl.api.input 2 | 3 | interface IMouse { 4 | fun destroy() 5 | 6 | fun getX(): Int 7 | fun getY(): Int 8 | fun setX(x: Int) 9 | fun setY(y: Int) 10 | 11 | fun getDX(): Int 12 | fun getDY(): Int 13 | fun getDWheel(): Int 14 | 15 | fun getEventButton(): Int 16 | fun getEventButtonState(): Boolean 17 | fun getEventDWheel(): Int 18 | fun getEventDX(): Int 19 | fun getEventDY(): Int 20 | fun getEventX(): Int 21 | fun getEventY(): Int 22 | fun setEventX(x: Int) 23 | fun setEventY(y: Int) 24 | fun getEventNanoseconds(): Long 25 | 26 | fun next(): Boolean 27 | fun poll() 28 | 29 | fun getButtonCount(): Int 30 | fun getButtonIndex(name: String): Int 31 | fun getButtonName(button: Int): String 32 | fun isButtonDown(button: Int): Boolean 33 | 34 | fun isGrabbed(): Boolean 35 | fun setGrabbed(grabbed: Boolean) 36 | 37 | fun setCursorPosition(x: Int, y: Int) 38 | fun isInsideWindow(): Boolean 39 | } 40 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/polyfrost/lwjgl/api/opengl/Context.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.lwjgl.api.opengl 2 | 3 | import org.lwjgl.PointerBuffer 4 | 5 | interface IContext { 6 | fun makeCurrent() 7 | fun isCurrent(): Boolean 8 | 9 | fun release() 10 | fun destroy() 11 | 12 | fun setCLSharingProperties(properties: PointerBuffer) 13 | } 14 | 15 | interface IShareableContext { 16 | fun makeShared(): IContext 17 | } 18 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/polyfrost/lwjgl/api/opengl/Display.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.lwjgl.api.opengl 2 | 3 | import org.lwjgl.opengl.DisplayMode 4 | import org.lwjgl.opengl.Drawable 5 | import java.awt.Canvas 6 | import java.nio.ByteBuffer 7 | 8 | interface IDisplay { 9 | /** 10 | * Get the driver adapter string. 11 | */ 12 | fun getAdapter(): String 13 | 14 | /** 15 | * Get the driver version. 16 | */ 17 | fun getVersion(): String 18 | 19 | 20 | /** 21 | * Return the current display mode, as set by setDisplayMode(). 22 | */ 23 | fun getDisplayMode(): DisplayMode 24 | 25 | /** 26 | * Set the current display mode. 27 | */ 28 | fun setDisplayMode(mode: DisplayMode) 29 | 30 | /** 31 | * Set the mode of the context. 32 | */ 33 | fun setDisplayModeAndFullscreen(mode: DisplayMode) 34 | 35 | /** 36 | * Return the initial desktop display mode. 37 | */ 38 | fun getDesktopDisplayMode(): DisplayMode 39 | 40 | /** 41 | * Returns the entire list of possible fullscreen display modes as an array, in no particular order. 42 | */ 43 | fun getAvailableDisplayModes(): Array 44 | 45 | 46 | /** 47 | * Fetch the Drawable from the Display. 48 | */ 49 | fun getDrawable(): Drawable 50 | 51 | /** 52 | * Return the last parent set with setParent(). 53 | */ 54 | fun getParent(): Canvas? 55 | 56 | /** 57 | * Set the parent of the Display. 58 | */ 59 | fun setParent(parent: Canvas?) 60 | 61 | /** 62 | * Set the initial color of the Display. 63 | */ 64 | fun setInitialBackground(red: Float, green: Float, blue: Float) 65 | 66 | 67 | fun getWidth(): Int 68 | fun getHeight(): Int 69 | fun getX(): Int 70 | fun getY(): Int 71 | fun setLocation(x: Int, y: Int) 72 | 73 | 74 | fun getTitle(): String 75 | fun setTitle(title: String) 76 | 77 | 78 | fun getPixelScaleFactor(): Float 79 | 80 | /** 81 | * Set the display configuration to the specified gamma, brightness and contrast. 82 | */ 83 | fun setDisplayConfiguration(gamma: Float, brightness: Float, contrast: Float) 84 | 85 | 86 | fun isActive(): Boolean 87 | fun isCreated(): Boolean 88 | 89 | /** 90 | * Sets one or more icons for the Display. 91 | */ 92 | fun setIcon(icons: Array): Int 93 | 94 | 95 | /** 96 | * Returns true if the Display's context is current in the current thread. 97 | */ 98 | fun isCurrent(): Boolean 99 | 100 | /** 101 | * Determine if the window's contents have been damaged by external events. 102 | */ 103 | fun isDirty(): Boolean 104 | 105 | fun isVisible(): Boolean 106 | fun isResizable(): Boolean 107 | fun setResizable(resizable: Boolean) 108 | fun isFullscreen(): Boolean 109 | fun setFullscreen(fullscreen: Boolean) 110 | fun isCloseRequested(): Boolean 111 | 112 | 113 | fun wasResized(): Boolean 114 | 115 | 116 | /** 117 | * Make the Display the current rendering context for GL calls. 118 | */ 119 | fun makeCurrent() 120 | 121 | /** 122 | * Release the Display context. 123 | */ 124 | fun releaseContext() 125 | 126 | /** 127 | * Destroy the Display. 128 | */ 129 | fun destroy() 130 | 131 | 132 | /** 133 | * An accurate sync method that will attempt to run at a constant frame rate. 134 | */ 135 | fun sync(fps: Int) 136 | 137 | /** 138 | * Set the buffer swap interval. 139 | */ 140 | fun setSwapInterval(interval: Int) 141 | 142 | /** 143 | * Enable or disable vertical monitor synchronization. 144 | */ 145 | fun setVSyncEnabled(sync: Boolean) 146 | 147 | 148 | /** 149 | * Update the window. 150 | */ 151 | fun update() 152 | 153 | /** 154 | * Update the window. 155 | */ 156 | fun update(process: Boolean) 157 | 158 | /** 159 | * Process operating system events. 160 | */ 161 | fun processMessages() 162 | 163 | /** 164 | * Swap the display buffers. 165 | */ 166 | fun swapBuffers() 167 | 168 | fun setReady() 169 | } 170 | 171 | internal data class CreationParameters( 172 | val displayMode: DisplayMode?, 173 | val title: String?, 174 | val resizable: Boolean?, 175 | val swapInterval: Int?, 176 | val fullscreen: Boolean? 177 | ) 178 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/polyfrost/lwjgl/impl/display/OpenGlDisplay.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.lwjgl.impl.display 2 | 3 | import org.lwjgl.glfw.GLFW.* 4 | import org.lwjgl.glfw.GLFWImage 5 | import org.lwjgl.input.Keyboard 6 | import org.lwjgl.input.Mouse 7 | import org.lwjgl.opengl.ContextAttribs 8 | import org.lwjgl.opengl.DisplayMode 9 | import org.lwjgl.opengl.Drawable 10 | import org.lwjgl.opengl.PixelFormat 11 | import org.lwjgl.system.MemoryStack 12 | import org.lwjgl.system.MemoryUtil.NULL 13 | import org.lwjglx.system.Monitor 14 | import org.polyfrost.lwjgl.api.opengl.CreationParameters 15 | import org.polyfrost.lwjgl.api.opengl.IDisplay 16 | import org.polyfrost.lwjgl.impl.input.KeyboardImpl 17 | import org.polyfrost.lwjgl.impl.input.MouseImpl 18 | import org.polyfrost.lwjgl.platform.common.GLFWmonitor 19 | import org.polyfrost.lwjgl.platform.common.GLFWwindow 20 | import org.polyfrost.lwjgl.platform.common.opengl.GlfwContext 21 | import org.polyfrost.lwjgl.platform.common.opengl.OpenGlDrawable 22 | import org.polyfrost.lwjgl.util.toInt 23 | import java.awt.Canvas 24 | import java.nio.ByteBuffer 25 | import kotlin.math.sqrt 26 | 27 | class OpenGlDisplay internal constructor( 28 | creationParameters: CreationParameters, 29 | format: PixelFormat, 30 | attribs: ContextAttribs 31 | ) : IDisplay { 32 | private var handle: GLFWwindow? = null 33 | private var drawable: Drawable 34 | 35 | private var title = "LWJGL" 36 | 37 | private var fullscreenOn: GLFWmonitor? = null 38 | private var previouslyFullscreen = false 39 | 40 | private var width = 0 41 | private var height = 0 42 | private var x = 0 43 | private var y = 0 44 | 45 | private var resizable = creationParameters.resizable ?: true 46 | private var didResize = true 47 | private var primaryDisplayMode = Monitor.getPrimaryMonitor().getDisplayMode() 48 | 49 | private var displayMode = creationParameters.displayMode ?: DisplayMode(640, 480, primaryDisplayMode.getFrequency(), primaryDisplayMode.getBitsPerPixel()) 50 | private var modeNeedsUpdate = false 51 | 52 | init { 53 | glfwDefaultWindowHints() 54 | 55 | GlfwContext.setWindowHints(attribs) 56 | 57 | glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE) 58 | glfwWindowHint(GLFW_FOCUS_ON_SHOW, GLFW_TRUE) 59 | glfwWindowHint(GLFW_RESIZABLE, (creationParameters.resizable ?: true).toInt()) 60 | 61 | width = displayMode.getWidth() 62 | height = displayMode.getHeight() 63 | title = creationParameters.title ?: "LWJGL" 64 | 65 | handle = glfwCreateWindow( 66 | width, height, title, NULL, NULL 67 | ) 68 | attachCallbacks() 69 | 70 | drawable = OpenGlDrawable(GlfwContext(handle!!, attribs)) 71 | drawable.makeCurrent() 72 | 73 | Keyboard.implementation = KeyboardImpl(handle!!) 74 | Mouse.implementation = MouseImpl(handle!!, this) 75 | 76 | glfwShowWindow(handle!!) 77 | centerWindow() 78 | updateMode() 79 | 80 | if (!didResize) didResize = width != displayMode.getWidth() || height != displayMode.getHeight() 81 | } 82 | 83 | override fun getAdapter(): String = "" 84 | 85 | override fun getVersion(): String = "" 86 | 87 | override fun getDisplayMode(): DisplayMode = displayMode 88 | 89 | override fun setDisplayMode(mode: DisplayMode) { 90 | displayMode = mode 91 | modeNeedsUpdate = true 92 | } 93 | 94 | override fun setDisplayModeAndFullscreen(mode: DisplayMode) { 95 | val monitor = Monitor.getPrimaryMonitor() 96 | fullscreenOn = monitor.handle 97 | 98 | setDisplayMode(mode) 99 | } 100 | 101 | override fun getDesktopDisplayMode(): DisplayMode = Monitor.getPrimaryMonitor().getDisplayMode() 102 | 103 | override fun getAvailableDisplayModes(): Array = Monitor.getPrimaryMonitor().getAvailableDisplayModes() 104 | 105 | override fun getDrawable(): Drawable = drawable 106 | 107 | override fun getParent(): Canvas? = null 108 | 109 | override fun setParent(parent: Canvas?) { 110 | 111 | } 112 | 113 | override fun setInitialBackground(red: Float, green: Float, blue: Float) { 114 | 115 | } 116 | 117 | override fun getWidth(): Int = width 118 | 119 | override fun getHeight(): Int = height 120 | 121 | override fun getX(): Int = x 122 | 123 | override fun getY(): Int = y 124 | 125 | override fun setLocation(x: Int, y: Int) { 126 | handle?.let { 127 | glfwSetWindowPos(it, x, y) 128 | updatePosition() 129 | } 130 | } 131 | 132 | override fun getTitle(): String = title 133 | 134 | override fun setTitle(title: String) { 135 | handle?.let { glfwSetWindowTitle(it, title) } 136 | } 137 | 138 | override fun getPixelScaleFactor(): Float = 1.0f 139 | 140 | override fun setDisplayConfiguration(gamma: Float, brightness: Float, contrast: Float) { 141 | TODO("Is this even supported by GLFW?") 142 | } 143 | 144 | override fun isActive(): Boolean = handle?.let { glfwGetWindowAttrib(it, GLFW_FOCUSED) } == GLFW_TRUE 145 | 146 | override fun isCreated(): Boolean = handle != null 147 | 148 | override fun setIcon(icons: Array): Int { 149 | val images = GLFWImage.create(icons.size) 150 | 151 | icons.forEach { icon -> 152 | val dimension = sqrt((icon.limit() / 4).toDouble()).toInt() 153 | 154 | GLFWImage 155 | .malloc() 156 | .use { images.put(it.set(dimension, dimension, icon)) } 157 | } 158 | 159 | images.flip() 160 | handle?.let { glfwSetWindowIcon(it, images) } 161 | 162 | return icons.size 163 | } 164 | 165 | override fun isCurrent(): Boolean = drawable.isCurrent() 166 | 167 | override fun isDirty(): Boolean = false 168 | 169 | override fun isVisible(): Boolean = true 170 | 171 | override fun isResizable(): Boolean = resizable 172 | 173 | override fun setResizable(resizable: Boolean) { 174 | handle?.let { 175 | this.resizable = resizable 176 | 177 | glfwSetWindowAttrib(it, GLFW_RESIZABLE, resizable.toInt()) 178 | } 179 | } 180 | 181 | override fun isFullscreen(): Boolean = fullscreenOn != null 182 | 183 | override fun setFullscreen(fullscreen: Boolean) { 184 | val monitor = Monitor.getPrimaryMonitor() 185 | 186 | if (fullscreen) { 187 | if (fullscreenOn != monitor.handle) { 188 | fullscreenOn = monitor.handle 189 | modeNeedsUpdate = true 190 | } 191 | } else { 192 | if (fullscreenOn != null) { 193 | fullscreenOn = null 194 | modeNeedsUpdate = true 195 | } 196 | } 197 | } 198 | 199 | override fun isCloseRequested(): Boolean = handle?.let { glfwWindowShouldClose(it) } ?: false 200 | 201 | override fun wasResized(): Boolean = didResize 202 | 203 | override fun makeCurrent() = drawable.makeCurrent() 204 | 205 | override fun releaseContext() = drawable.releaseContext() 206 | 207 | override fun destroy() { 208 | glfwDestroyWindow(handle!!) 209 | 210 | handle = null 211 | } 212 | 213 | override fun sync(fps: Int) { 214 | 215 | } 216 | 217 | override fun setSwapInterval(interval: Int) { 218 | glfwSwapInterval(interval) 219 | } 220 | 221 | override fun setVSyncEnabled(sync: Boolean) { 222 | setSwapInterval(sync.toInt()) 223 | } 224 | 225 | override fun update() { 226 | update(true) 227 | } 228 | 229 | override fun update(process: Boolean) { 230 | processMessages() 231 | swapBuffers() 232 | 233 | // todo: maybe hacky? review lwjgl2 implementation of full screen 234 | if (modeNeedsUpdate) updateMode() 235 | } 236 | 237 | override fun processMessages() { 238 | didResize = false 239 | 240 | Mouse.poll() 241 | Keyboard.poll() 242 | 243 | glfwPollEvents() 244 | } 245 | 246 | override fun swapBuffers() { 247 | handle?.let { glfwSwapBuffers(it) } 248 | } 249 | 250 | override fun setReady() { 251 | handle?.let { glfwShowWindow(it) } 252 | } 253 | 254 | private fun centerWindow() { 255 | val primaryDisplayMode = Monitor.getPrimaryMonitor().getDisplayMode() 256 | 257 | setLocation((primaryDisplayMode.getWidth() / 2) - (width / 2), (primaryDisplayMode.getHeight()) / 2 - (height / 2)) 258 | } 259 | 260 | private fun updateMode() { 261 | modeNeedsUpdate = false 262 | 263 | handle?.let { 264 | if (fullscreenOn != null) { 265 | if (!previouslyFullscreen) { 266 | previouslyFullscreen = true 267 | } 268 | 269 | glfwSetWindowMonitor( 270 | it, 271 | fullscreenOn!!, 272 | 0, 273 | 0, 274 | displayMode.getWidth(), 275 | displayMode.getHeight(), 276 | displayMode.getFrequency() 277 | ) 278 | updateSize() 279 | } else { 280 | if (previouslyFullscreen) { 281 | glfwSetWindowMonitor( 282 | it, 283 | NULL, 284 | 0, 285 | 0, 286 | displayMode.getWidth(), 287 | displayMode.getHeight(), 288 | 0 289 | ) 290 | 291 | previouslyFullscreen = false 292 | 293 | updateSize() 294 | centerWindow() 295 | } else { 296 | glfwSetWindowSize(it, displayMode.getWidth(), displayMode.getHeight()) 297 | 298 | updateSize() 299 | updatePosition() 300 | } 301 | } 302 | } 303 | } 304 | 305 | private fun updatePosition() { 306 | handle?.let { 307 | MemoryStack.stackPush().use { stack -> 308 | val previousX = x 309 | val previousY = y 310 | 311 | val xPtr = stack.mallocInt(1) 312 | val yPtr = stack.mallocInt(1) 313 | 314 | glfwGetWindowPos(it, xPtr, yPtr) 315 | 316 | x = xPtr.get() 317 | y = yPtr.get() 318 | 319 | if (x != previousX || y != previousY) onMove(it, x, y) 320 | } 321 | } 322 | } 323 | 324 | private fun updateSize() { 325 | handle?.let { 326 | MemoryStack.stackPush().use { stack -> 327 | val previousWidth = width 328 | val previousHeight = height 329 | 330 | val widthPtr = stack.mallocInt(1) 331 | val heightPtr = stack.mallocInt(1) 332 | 333 | glfwGetWindowSize(it, widthPtr, heightPtr) 334 | 335 | width = widthPtr.get() 336 | height = heightPtr.get() 337 | 338 | if (width != previousWidth || height != previousHeight) { onResize(it, width, height) } 339 | } 340 | } 341 | } 342 | 343 | private fun onResize(window: Long, width: Int, height: Int) { 344 | this.width = width 345 | this.height = height 346 | 347 | displayMode = DisplayMode( 348 | width, 349 | height, 350 | displayMode.getFrequency(), 351 | displayMode.getBitsPerPixel() 352 | ) 353 | didResize = true 354 | } 355 | 356 | private fun onMove(window: Long, x: Int, y: Int) { 357 | this.x = x 358 | this.y = y 359 | } 360 | 361 | private fun attachCallbacks() { 362 | handle?.let { 363 | glfwSetFramebufferSizeCallback(it, ::onResize) 364 | glfwSetWindowPosCallback(it, ::onMove) 365 | } 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/polyfrost/lwjgl/impl/input/Keyboard.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.lwjgl.impl.input 2 | 3 | import org.lwjgl.glfw.GLFW.* 4 | import org.lwjgl.input.Keyboard 5 | import org.polyfrost.lwjgl.api.input.IKeyboard 6 | import org.polyfrost.lwjgl.platform.common.GLFWwindow 7 | import java.util.* 8 | import kotlin.collections.ArrayDeque 9 | import kotlin.collections.HashMap 10 | 11 | private data class KeyboardEvent( 12 | var key: Int, 13 | var keyState: Boolean, 14 | var character: Char, 15 | var repeatEvent: Boolean, 16 | var timestamp: Long 17 | ) 18 | 19 | class KeyboardImpl(private val window: GLFWwindow) : IKeyboard { 20 | private val size = GLFW_KEY_LAST + 1 21 | 22 | private val keyStates = BooleanArray(size) { false } 23 | 24 | private val keyNames = Array(size) { "" } 25 | private val keyNameMappings = HashMap(size) 26 | 27 | private val lwjglMappings = IntArray(size) { 0x00 } 28 | 29 | private val events = ArrayDeque() 30 | private val unusedEvents = Stack() 31 | 32 | private var repeatEventsEnabled = true 33 | private var currentEvent: KeyboardEvent? = null 34 | 35 | init { 36 | createKeyMappings() 37 | createNameMappings() 38 | 39 | attachCallbacks() 40 | } 41 | 42 | override fun destroy() {} 43 | 44 | override fun areRepeatEventsEnabled(): Boolean = repeatEventsEnabled 45 | override fun enableRepeatEvents(enable: Boolean) { 46 | repeatEventsEnabled = enable 47 | } 48 | 49 | override fun getKeyCount(): Int = size 50 | override fun getKeyIndex(name: String): Int = keyNameMappings[name]!! 51 | override fun getKeyName(key: Int): String = keyNames[key] 52 | override fun isKeyDown(key: Int): Boolean = keyStates[key] 53 | 54 | override fun getEventKey(): Int = currentEvent?.key ?: 0 55 | override fun getEventCharacter(): Char = currentEvent?.character ?: 0.toChar() 56 | override fun getEventKeyState(): Boolean = currentEvent?.keyState ?: false 57 | override fun getEventNanoseconds(): Long = currentEvent?.timestamp ?: 0 58 | override fun isRepeatEvent(): Boolean = currentEvent?.repeatEvent ?: false 59 | 60 | override fun getNumKeyboardEvents(): Int = events.size 61 | 62 | override fun next(): Boolean { 63 | if (currentEvent != null) unusedEvents += currentEvent 64 | 65 | return if (events.isNotEmpty()) { 66 | currentEvent = events.removeFirst() 67 | 68 | true 69 | } else { 70 | currentEvent = null 71 | 72 | false 73 | } 74 | } 75 | 76 | override fun poll() {} 77 | 78 | @Suppress("UNUSED_PARAMETER") 79 | private fun keyHandler(window: Long, key: Int, scancode: Int, action: Int, mods: Int) { 80 | if (action == GLFW_REPEAT || key == -1) return 81 | 82 | events.addLast( 83 | createEvent( 84 | lwjglMappings[key], 85 | action == GLFW_PRESS, 86 | 0.toChar(), 87 | false, 88 | System.nanoTime() 89 | ) 90 | ) 91 | 92 | keyStates[lwjglMappings[key]] = action == GLFW_PRESS 93 | } 94 | 95 | @Suppress("UNUSED_PARAMETER") 96 | private fun characterHandler(window: Long, codepoint: Int) { 97 | events.addLast( 98 | createEvent( 99 | -1, 100 | true, 101 | codepoint.toChar(), 102 | false, 103 | System.nanoTime() 104 | ) 105 | ) 106 | } 107 | 108 | private fun createEvent( 109 | key: Int, 110 | keyState: Boolean, 111 | character: Char, 112 | repeatEvent: Boolean, 113 | timestamp: Long 114 | ): KeyboardEvent { 115 | return if (unusedEvents.isNotEmpty()) { 116 | val event = unusedEvents.pop() 117 | 118 | event.key = key 119 | event.keyState = keyState 120 | event.character = character 121 | event.repeatEvent = repeatEvent 122 | event.timestamp = timestamp 123 | 124 | event 125 | } else { 126 | KeyboardEvent(key, keyState, character, repeatEvent, timestamp) 127 | } 128 | } 129 | 130 | private fun attachCallbacks() { 131 | glfwSetKeyCallback(window, ::keyHandler) 132 | glfwSetCharCallback(window, ::characterHandler) 133 | } 134 | 135 | private fun createKeyMappings() { 136 | lwjglMappings[0x00] = Keyboard.KEY_NONE 137 | lwjglMappings[GLFW_KEY_SPACE] = Keyboard.KEY_SPACE 138 | lwjglMappings[GLFW_KEY_APOSTROPHE] = Keyboard.KEY_APOSTROPHE 139 | lwjglMappings[GLFW_KEY_COMMA] = Keyboard.KEY_COMMA 140 | lwjglMappings[GLFW_KEY_MINUS] = Keyboard.KEY_MINUS 141 | lwjglMappings[GLFW_KEY_PERIOD] = Keyboard.KEY_PERIOD 142 | lwjglMappings[GLFW_KEY_SLASH] = Keyboard.KEY_SLASH 143 | lwjglMappings[GLFW_KEY_0] = Keyboard.KEY_0 144 | lwjglMappings[GLFW_KEY_1] = Keyboard.KEY_1 145 | lwjglMappings[GLFW_KEY_2] = Keyboard.KEY_2 146 | lwjglMappings[GLFW_KEY_3] = Keyboard.KEY_3 147 | lwjglMappings[GLFW_KEY_4] = Keyboard.KEY_4 148 | lwjglMappings[GLFW_KEY_5] = Keyboard.KEY_5 149 | lwjglMappings[GLFW_KEY_6] = Keyboard.KEY_6 150 | lwjglMappings[GLFW_KEY_7] = Keyboard.KEY_7 151 | lwjglMappings[GLFW_KEY_8] = Keyboard.KEY_8 152 | lwjglMappings[GLFW_KEY_9] = Keyboard.KEY_9 153 | lwjglMappings[GLFW_KEY_SEMICOLON] = Keyboard.KEY_SEMICOLON 154 | lwjglMappings[GLFW_KEY_EQUAL] = Keyboard.KEY_EQUALS 155 | lwjglMappings[GLFW_KEY_A] = Keyboard.KEY_A 156 | lwjglMappings[GLFW_KEY_B] = Keyboard.KEY_B 157 | lwjglMappings[GLFW_KEY_C] = Keyboard.KEY_C 158 | lwjglMappings[GLFW_KEY_D] = Keyboard.KEY_D 159 | lwjglMappings[GLFW_KEY_E] = Keyboard.KEY_E 160 | lwjglMappings[GLFW_KEY_F] = Keyboard.KEY_F 161 | lwjglMappings[GLFW_KEY_G] = Keyboard.KEY_G 162 | lwjglMappings[GLFW_KEY_H] = Keyboard.KEY_H 163 | lwjglMappings[GLFW_KEY_I] = Keyboard.KEY_I 164 | lwjglMappings[GLFW_KEY_J] = Keyboard.KEY_J 165 | lwjglMappings[GLFW_KEY_K] = Keyboard.KEY_K 166 | lwjglMappings[GLFW_KEY_L] = Keyboard.KEY_L 167 | lwjglMappings[GLFW_KEY_M] = Keyboard.KEY_M 168 | lwjglMappings[GLFW_KEY_N] = Keyboard.KEY_N 169 | lwjglMappings[GLFW_KEY_O] = Keyboard.KEY_O 170 | lwjglMappings[GLFW_KEY_P] = Keyboard.KEY_P 171 | lwjglMappings[GLFW_KEY_Q] = Keyboard.KEY_Q 172 | lwjglMappings[GLFW_KEY_R] = Keyboard.KEY_R 173 | lwjglMappings[GLFW_KEY_S] = Keyboard.KEY_S 174 | lwjglMappings[GLFW_KEY_T] = Keyboard.KEY_T 175 | lwjglMappings[GLFW_KEY_U] = Keyboard.KEY_U 176 | lwjglMappings[GLFW_KEY_V] = Keyboard.KEY_V 177 | lwjglMappings[GLFW_KEY_W] = Keyboard.KEY_W 178 | lwjglMappings[GLFW_KEY_X] = Keyboard.KEY_X 179 | lwjglMappings[GLFW_KEY_Y] = Keyboard.KEY_Y 180 | lwjglMappings[GLFW_KEY_Z] = Keyboard.KEY_Z 181 | lwjglMappings[GLFW_KEY_LEFT_BRACKET] = Keyboard.KEY_LBRACKET 182 | lwjglMappings[GLFW_KEY_BACKSLASH] = Keyboard.KEY_BACKSLASH 183 | lwjglMappings[GLFW_KEY_RIGHT_BRACKET] = Keyboard.KEY_RBRACKET 184 | lwjglMappings[GLFW_KEY_GRAVE_ACCENT] = Keyboard.KEY_GRAVE 185 | lwjglMappings[GLFW_KEY_ESCAPE] = Keyboard.KEY_ESCAPE 186 | lwjglMappings[GLFW_KEY_ENTER] = Keyboard.KEY_RETURN 187 | lwjglMappings[GLFW_KEY_TAB] = Keyboard.KEY_TAB 188 | lwjglMappings[GLFW_KEY_BACKSPACE] = Keyboard.KEY_BACK 189 | lwjglMappings[GLFW_KEY_INSERT] = Keyboard.KEY_INSERT 190 | lwjglMappings[GLFW_KEY_DELETE] = Keyboard.KEY_DELETE 191 | lwjglMappings[GLFW_KEY_RIGHT] = Keyboard.KEY_RIGHT 192 | lwjglMappings[GLFW_KEY_LEFT] = Keyboard.KEY_LEFT 193 | lwjglMappings[GLFW_KEY_DOWN] = Keyboard.KEY_DOWN 194 | lwjglMappings[GLFW_KEY_UP] = Keyboard.KEY_UP 195 | lwjglMappings[GLFW_KEY_PAGE_UP] = Keyboard.KEY_PRIOR 196 | lwjglMappings[GLFW_KEY_PAGE_DOWN] = Keyboard.KEY_NEXT 197 | lwjglMappings[GLFW_KEY_HOME] = Keyboard.KEY_HOME 198 | lwjglMappings[GLFW_KEY_END] = Keyboard.KEY_END 199 | lwjglMappings[GLFW_KEY_CAPS_LOCK] = Keyboard.KEY_CAPITAL 200 | lwjglMappings[GLFW_KEY_SCROLL_LOCK] = Keyboard.KEY_SCROLL 201 | lwjglMappings[GLFW_KEY_NUM_LOCK] = Keyboard.KEY_NUMLOCK 202 | lwjglMappings[GLFW_KEY_PAUSE] = Keyboard.KEY_PAUSE 203 | lwjglMappings[GLFW_KEY_F1] = Keyboard.KEY_F1 204 | lwjglMappings[GLFW_KEY_F2] = Keyboard.KEY_F2 205 | lwjglMappings[GLFW_KEY_F3] = Keyboard.KEY_F3 206 | lwjglMappings[GLFW_KEY_F4] = Keyboard.KEY_F4 207 | lwjglMappings[GLFW_KEY_F5] = Keyboard.KEY_F5 208 | lwjglMappings[GLFW_KEY_F6] = Keyboard.KEY_F6 209 | lwjglMappings[GLFW_KEY_F7] = Keyboard.KEY_F7 210 | lwjglMappings[GLFW_KEY_F8] = Keyboard.KEY_F8 211 | lwjglMappings[GLFW_KEY_F9] = Keyboard.KEY_F9 212 | lwjglMappings[GLFW_KEY_F10] = Keyboard.KEY_F10 213 | lwjglMappings[GLFW_KEY_F11] = Keyboard.KEY_F11 214 | lwjglMappings[GLFW_KEY_F12] = Keyboard.KEY_F12 215 | lwjglMappings[GLFW_KEY_F13] = Keyboard.KEY_F13 216 | lwjglMappings[GLFW_KEY_F14] = Keyboard.KEY_F14 217 | lwjglMappings[GLFW_KEY_F15] = Keyboard.KEY_F15 218 | lwjglMappings[GLFW_KEY_F16] = Keyboard.KEY_F16 219 | lwjglMappings[GLFW_KEY_F17] = Keyboard.KEY_F17 220 | lwjglMappings[GLFW_KEY_F18] = Keyboard.KEY_F18 221 | lwjglMappings[GLFW_KEY_F19] = Keyboard.KEY_F19 222 | lwjglMappings[GLFW_KEY_KP_0] = Keyboard.KEY_NUMPAD0 223 | lwjglMappings[GLFW_KEY_KP_1] = Keyboard.KEY_NUMPAD1 224 | lwjglMappings[GLFW_KEY_KP_2] = Keyboard.KEY_NUMPAD2 225 | lwjglMappings[GLFW_KEY_KP_3] = Keyboard.KEY_NUMPAD3 226 | lwjglMappings[GLFW_KEY_KP_4] = Keyboard.KEY_NUMPAD4 227 | lwjglMappings[GLFW_KEY_KP_5] = Keyboard.KEY_NUMPAD5 228 | lwjglMappings[GLFW_KEY_KP_6] = Keyboard.KEY_NUMPAD6 229 | lwjglMappings[GLFW_KEY_KP_7] = Keyboard.KEY_NUMPAD7 230 | lwjglMappings[GLFW_KEY_KP_8] = Keyboard.KEY_NUMPAD8 231 | lwjglMappings[GLFW_KEY_KP_9] = Keyboard.KEY_NUMPAD9 232 | lwjglMappings[GLFW_KEY_KP_DECIMAL] = Keyboard.KEY_DECIMAL 233 | lwjglMappings[GLFW_KEY_KP_DIVIDE] = Keyboard.KEY_DIVIDE 234 | lwjglMappings[GLFW_KEY_KP_MULTIPLY] = Keyboard.KEY_MULTIPLY 235 | lwjglMappings[GLFW_KEY_KP_SUBTRACT] = Keyboard.KEY_SUBTRACT 236 | lwjglMappings[GLFW_KEY_KP_ADD] = Keyboard.KEY_ADD 237 | lwjglMappings[GLFW_KEY_KP_ENTER] = Keyboard.KEY_NUMPADENTER 238 | lwjglMappings[GLFW_KEY_KP_EQUAL] = Keyboard.KEY_NUMPADEQUALS 239 | lwjglMappings[GLFW_KEY_LEFT_SHIFT] = Keyboard.KEY_LSHIFT 240 | lwjglMappings[GLFW_KEY_LEFT_CONTROL] = Keyboard.KEY_LCONTROL 241 | lwjglMappings[GLFW_KEY_LEFT_ALT] = Keyboard.KEY_LMENU 242 | lwjglMappings[GLFW_KEY_LEFT_SUPER] = Keyboard.KEY_LMETA 243 | lwjglMappings[GLFW_KEY_RIGHT_SHIFT] = Keyboard.KEY_RSHIFT 244 | lwjglMappings[GLFW_KEY_RIGHT_CONTROL] = Keyboard.KEY_RCONTROL 245 | lwjglMappings[GLFW_KEY_RIGHT_ALT] = Keyboard.KEY_RMENU 246 | lwjglMappings[GLFW_KEY_RIGHT_SUPER] = Keyboard.KEY_RMETA 247 | } 248 | 249 | private fun createNameMappings() { 250 | Keyboard::class 251 | .java 252 | .fields 253 | .forEach { field -> 254 | if (field.name.startsWith("KEY_")) { 255 | val key = field.get(Keyboard) as Int 256 | val name = field.name.replace("KEY_", "") 257 | 258 | keyNames[key] = name 259 | keyNameMappings[name] = key 260 | } 261 | } 262 | } 263 | } -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/polyfrost/lwjgl/impl/input/Mouse.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.lwjgl.impl.input 2 | 3 | import org.lwjgl.glfw.GLFW.* 4 | import org.lwjgl.opengl.Display 5 | import org.lwjgl.system.MemoryStack 6 | import org.lwjglx.input.RawInput 7 | import org.polyfrost.lwjgl.api.input.IMouse 8 | import org.polyfrost.lwjgl.api.opengl.IDisplay 9 | import org.polyfrost.lwjgl.platform.common.GLFWwindow 10 | import org.polyfrost.lwjgl.util.toInt 11 | import java.util.Stack 12 | 13 | private data class MouseEvent( 14 | var button: Int, 15 | var buttonState: Boolean, 16 | var scrollDelta: Int, 17 | var xDelta: Int, 18 | var yDelta: Int, 19 | var x: Int, 20 | var y: Int, 21 | var timestamp: Long 22 | ) 23 | 24 | class MouseImpl(private val window: GLFWwindow, private val display: IDisplay) : IMouse { 25 | private val size = 16 26 | 27 | private val buttonStates = BooleanArray(size) { false } 28 | 29 | private val buttonNames = Array(size) { "" } 30 | private val buttonNameMappings = HashMap(size) 31 | 32 | private val events = ArrayDeque() 33 | private val unusedEvents = Stack() 34 | 35 | private var grabbed = false 36 | 37 | private var x = 0 38 | private var y = 0 39 | 40 | private var xDelta = 0.0 41 | private var yDelta = 0.0 42 | 43 | private var scrollDelta = 0 44 | 45 | private var currentEvent: MouseEvent? = null 46 | 47 | init { 48 | setRawInput(RawInput.isUsingRawInput()) 49 | createNameMappings() 50 | 51 | attachCallbacks() 52 | setInitialPosition() 53 | } 54 | 55 | fun setRawInput(raw: Boolean) { 56 | glfwSetInputMode(window, GLFW_RAW_MOUSE_MOTION, raw.toInt()) 57 | } 58 | 59 | override fun destroy() {} 60 | 61 | override fun getX(): Int = x 62 | override fun getY(): Int = display.getHeight() - y 63 | override fun setX(x: Int) { 64 | this.x = x 65 | } 66 | override fun setY(y: Int) { 67 | this.y = display.getHeight() - y 68 | } 69 | override fun getDX(): Int = xDelta.toInt() 70 | override fun getDY(): Int = yDelta.toInt() 71 | override fun getDWheel(): Int = scrollDelta 72 | 73 | override fun getEventButton(): Int = currentEvent?.button ?: -1 74 | override fun getEventButtonState(): Boolean = currentEvent?.buttonState ?: false 75 | override fun getEventDWheel(): Int = currentEvent?.scrollDelta ?: 0 76 | override fun getEventDX(): Int = currentEvent?.xDelta ?: 0 77 | override fun getEventDY(): Int = currentEvent?.yDelta ?: 0 78 | override fun getEventX(): Int = currentEvent?.x ?: 0 79 | override fun getEventY(): Int = currentEvent?.y ?: 0 80 | override fun setEventX(x: Int) { 81 | currentEvent?.x = x 82 | } 83 | override fun setEventY(y: Int) { 84 | currentEvent?.y = y 85 | } 86 | override fun getEventNanoseconds(): Long = currentEvent?.timestamp ?: 0 87 | 88 | override fun next(): Boolean { 89 | if (currentEvent != null) unusedEvents += currentEvent 90 | 91 | return if (events.isNotEmpty()) { 92 | currentEvent = events.removeFirst() 93 | 94 | true 95 | } else { 96 | currentEvent = null 97 | 98 | false 99 | } 100 | } 101 | override fun poll() { 102 | xDelta = 0.0 103 | yDelta = 0.0 104 | scrollDelta = 0 105 | } 106 | 107 | override fun getButtonCount(): Int = size 108 | override fun getButtonIndex(name: String): Int = buttonNameMappings[name]!! 109 | override fun getButtonName(button: Int): String = buttonNames[button] 110 | override fun isButtonDown(button: Int): Boolean = buttonStates[button] 111 | 112 | override fun isGrabbed(): Boolean = grabbed 113 | override fun setGrabbed(grabbed: Boolean) { 114 | this.grabbed = grabbed 115 | 116 | glfwSetInputMode(window, GLFW_CURSOR, if (grabbed) GLFW_CURSOR_DISABLED else GLFW_CURSOR_NORMAL) 117 | } 118 | 119 | override fun setCursorPosition(x: Int, y: Int) { 120 | grabbed = false 121 | 122 | glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL) 123 | glfwSetCursorPos(window, x.toDouble(), y.toDouble()) 124 | 125 | this.x = x 126 | this.y = y 127 | } 128 | 129 | override fun isInsideWindow() = Display.isVisible() 130 | 131 | @Suppress("UNUSED_PARAMETER") 132 | private fun mouseButtonHandler(window: Long, button: Int, action: Int, mods: Int) { 133 | buttonStates[button] = action == GLFW_PRESS 134 | 135 | events.addLast( 136 | createEvent( 137 | button, 138 | action == GLFW_PRESS, 139 | 0, 140 | xDelta.toInt(), 141 | yDelta.toInt(), 142 | x, 143 | (display.getHeight() - y), 144 | System.nanoTime() 145 | ) 146 | ) 147 | } 148 | 149 | @Suppress("UNUSED_PARAMETER") 150 | private fun mouseMoveHandler(window: Long, x: Double, y: Double) { 151 | val xDelta = x - this.x 152 | val yDelta = (y - this.y) * -1 153 | 154 | this.x = x.toInt() 155 | this.y = y.toInt() 156 | 157 | this.xDelta += xDelta 158 | this.yDelta += yDelta 159 | 160 | events.addLast( 161 | createEvent( 162 | -1, 163 | false, 164 | 0, 165 | xDelta.toInt(), 166 | yDelta.toInt(), 167 | if (grabbed) xDelta.toInt() else this.x, 168 | if (grabbed) yDelta.toInt() else (display.getHeight() - this.y), 169 | System.nanoTime() 170 | ) 171 | ) 172 | } 173 | 174 | @Suppress("UNUSED_PARAMETER") 175 | private fun mouseScrollHandler(window: Long, x: Double, y: Double) { 176 | scrollDelta += y.toInt() 177 | 178 | events.addLast( 179 | createEvent( 180 | -1, 181 | false, 182 | y.toInt(), 183 | xDelta.toInt(), 184 | yDelta.toInt(), 185 | this.x, 186 | (display.getHeight() - this.y), 187 | System.nanoTime() 188 | ) 189 | ) 190 | } 191 | 192 | private fun createEvent( 193 | button: Int, 194 | buttonState: Boolean, 195 | wheelDelta: Int, 196 | deltaX: Int, 197 | deltaY: Int, 198 | x: Int, 199 | y: Int, 200 | timestamp: Long 201 | ): MouseEvent { 202 | return if (unusedEvents.isNotEmpty()) { 203 | val event = unusedEvents.pop() 204 | 205 | event.button = button 206 | event.buttonState = buttonState 207 | event.scrollDelta = wheelDelta 208 | event.xDelta = deltaX 209 | event.yDelta = deltaY 210 | event.x = x 211 | event.y = y 212 | event.timestamp = timestamp 213 | 214 | event 215 | } else { 216 | MouseEvent( 217 | button, 218 | buttonState, 219 | wheelDelta, 220 | deltaX, 221 | deltaY, 222 | x, 223 | y, 224 | timestamp 225 | ) 226 | } 227 | } 228 | 229 | private fun attachCallbacks() { 230 | glfwSetMouseButtonCallback(window, ::mouseButtonHandler) 231 | glfwSetCursorPosCallback(window, ::mouseMoveHandler) 232 | glfwSetScrollCallback(window, ::mouseScrollHandler) 233 | } 234 | 235 | private fun setInitialPosition() { 236 | MemoryStack 237 | .stackPush() 238 | .use { stack -> 239 | val x = stack.doubles(0.0) 240 | val y = stack.doubles(0.0) 241 | 242 | glfwGetCursorPos(window, x, y) 243 | 244 | this.x = x.get().toInt() 245 | this.y = y.get().toInt() 246 | } 247 | } 248 | 249 | private fun createNameMappings() { 250 | for (button in 0..) = 6 | check(capability.get()) { "${capability.name} is required but is not supported" } 7 | 8 | enum class OperatingSystem { 9 | Linux, 10 | Windows, 11 | MacOS; 12 | 13 | companion object { 14 | fun detect(): OperatingSystem { 15 | val name = System.getProperty("os.name").lowercase() 16 | 17 | return when { 18 | name.startsWith("windows") -> Windows 19 | name.contains("linux") -> Linux 20 | name.contains("macos") 21 | || name.contains("darwin") -> MacOS 22 | 23 | else -> throw Exception("Unsupported operating system") 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/polyfrost/lwjgl/platform/common/opengl/GlfwContext.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.lwjgl.platform.common.opengl 2 | 3 | import org.lwjgl.PointerBuffer 4 | import org.lwjgl.glfw.GLFW.* 5 | import org.lwjgl.opengl.ContextAttribs 6 | import org.lwjgl.opengl.ContextAttribs.Companion.CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 7 | import org.lwjgl.opengl.ContextAttribs.Companion.CONTEXT_RELEASE_BEHAVIOR_NONE_ARB 8 | import org.lwjgl.opengl.GL 9 | import org.lwjgl.system.MemoryUtil.NULL 10 | import org.polyfrost.lwjgl.api.opengl.IContext 11 | import org.polyfrost.lwjgl.api.opengl.IShareableContext 12 | import org.polyfrost.lwjgl.platform.common.GLFWwindow 13 | import org.polyfrost.lwjgl.util.toInt 14 | 15 | class GlfwContext(private val window: GLFWwindow, private val attribs: ContextAttribs) : IContext, IShareableContext { 16 | private val handle: Long 17 | 18 | init { 19 | val current = glfwGetCurrentContext() 20 | 21 | glfwMakeContextCurrent(window) 22 | handle = glfwGetCurrentContext() 23 | glfwMakeContextCurrent(current) 24 | } 25 | 26 | override fun makeCurrent() { 27 | glfwMakeContextCurrent(window) 28 | 29 | runCatching { GL.getCapabilities() }.onFailure { GL.createCapabilities() } 30 | } 31 | 32 | override fun isCurrent(): Boolean = glfwGetCurrentContext() == handle 33 | 34 | override fun release() { 35 | glfwMakeContextCurrent(NULL) 36 | } 37 | 38 | override fun destroy() { 39 | glfwDestroyWindow(window) 40 | } 41 | 42 | override fun setCLSharingProperties(properties: PointerBuffer) { 43 | TODO("Not yet implemented") 44 | } 45 | 46 | override fun makeShared(): IContext { 47 | glfwDefaultWindowHints() 48 | setWindowHints(attribs) 49 | 50 | glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE) 51 | 52 | return GlfwContext( 53 | glfwCreateWindow(16, 16, "", NULL, window), 54 | attribs 55 | ) 56 | } 57 | 58 | companion object { 59 | fun setWindowHints(attribs: ContextAttribs) { 60 | val robustness = when { 61 | !attribs.isRobustAccess() -> GLFW_NO_ROBUSTNESS 62 | attribs.isLoseContextOnReset() -> GLFW_LOSE_CONTEXT_ON_RESET 63 | !attribs.isLoseContextOnReset() -> GLFW_NO_RESET_NOTIFICATION 64 | else -> GLFW_NO_ROBUSTNESS 65 | } 66 | 67 | val profile = when { 68 | (attribs.getMajorVersion() < 3 69 | || (attribs.getMajorVersion() == 3 && attribs.getMinorVersion() < 2)) -> GLFW_OPENGL_ANY_PROFILE 70 | 71 | attribs.isProfileCore() -> GLFW_OPENGL_CORE_PROFILE 72 | attribs.isProfileCompatibility() -> GLFW_OPENGL_COMPAT_PROFILE 73 | else -> GLFW_OPENGL_ANY_PROFILE 74 | } 75 | 76 | val releaseBehavior = when (attribs.getContextReleaseBehavior()) { 77 | CONTEXT_RELEASE_BEHAVIOR_NONE_ARB -> GLFW_RELEASE_BEHAVIOR_NONE 78 | CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB -> GLFW_RELEASE_BEHAVIOR_FLUSH 79 | else -> GLFW_ANY_RELEASE_BEHAVIOR 80 | } 81 | 82 | glfwWindowHint(GLFW_CLIENT_API, if (attribs.isProfileES()) GLFW_OPENGL_ES_API else GLFW_OPENGL_API) 83 | glfwWindowHint(GLFW_OPENGL_PROFILE, profile) 84 | 85 | if (attribs.getMajorVersion() >= 3) glfwWindowHint( 86 | GLFW_OPENGL_FORWARD_COMPAT, 87 | attribs.isForwardCompatible().toInt() 88 | ) 89 | 90 | glfwWindowHint(GLFW_CONTEXT_DEBUG, attribs.isDebug().toInt()) 91 | glfwWindowHint(GLFW_CONTEXT_RELEASE_BEHAVIOR, releaseBehavior) 92 | glfwWindowHint(GLFW_CONTEXT_ROBUSTNESS, robustness) 93 | 94 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, attribs.getMajorVersion()) 95 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, attribs.getMinorVersion()) 96 | 97 | glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE) 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/polyfrost/lwjgl/platform/common/opengl/OpenGlDrawable.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.lwjgl.platform.common.opengl 2 | 3 | import org.lwjgl.PointerBuffer 4 | import org.lwjgl.opengl.Drawable 5 | import org.polyfrost.lwjgl.api.opengl.IContext 6 | 7 | class OpenGlDrawable internal constructor(internal val context: IContext) : Drawable { 8 | override fun makeCurrent() = context.makeCurrent() 9 | 10 | override fun isCurrent(): Boolean = context.isCurrent() 11 | 12 | override fun releaseContext() = context.release() 13 | 14 | override fun destroy() = context.destroy() 15 | 16 | override fun setCLSharingProperties(properties: PointerBuffer) { 17 | TODO("Not yet implemented") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/polyfrost/lwjgl/util/Boolean.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.lwjgl.util 2 | 3 | fun Boolean.toByte(): Byte = if (this) 1 else 0 4 | fun Boolean.toUByte(): UByte = toByte().toUByte() 5 | 6 | fun Boolean.toShort(): Short = toByte().toShort() 7 | fun Boolean.toUShort(): UShort = toByte().toUShort() 8 | 9 | fun Boolean.toInt(): Int = toByte().toInt() 10 | fun Boolean.toUInt(): UInt = toByte().toUInt() 11 | 12 | fun Boolean.toLong(): Long = toByte().toLong() 13 | fun Boolean.toULong(): ULong = toByte().toULong() 14 | -------------------------------------------------------------------------------- /modules/lwjgl/src/main/kotlin/org/polyfrost/lwjgl/util/String.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.lwjgl.util 2 | 3 | import org.lwjgl.system.MemoryUtil 4 | import java.nio.ByteBuffer 5 | 6 | private val stringAddresses = mutableMapOf() 7 | 8 | fun addr(string: String, nullTerminated: Boolean = true) = 9 | stringAddresses.computeIfAbsent("$string$nullTerminated") { MemoryUtil.memUTF16(string, nullTerminated) } 10 | -------------------------------------------------------------------------------- /modules/root.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | plugins { 4 | idea 5 | } 6 | 7 | val rootModuleProject = project 8 | 9 | subprojects { 10 | apply(plugin = "java-library") 11 | apply(plugin = "kotlin") 12 | 13 | dependencies { 14 | "implementation"(rootProject.libs.annotations) 15 | if (name != "common" && name != "lwjgl") "implementation"(project(":modules:common")) 16 | "implementation"(rootProject.libs.bundles.kotlin) 17 | } 18 | 19 | //base.archiveBaseName = name 20 | 21 | configure { 22 | toolchain { 23 | languageVersion.set(JavaLanguageVersion.of(8)) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | mavenCentral() 5 | maven("https://repo.polyfrost.org/releases") { 6 | name = "Polyfrost Releases" 7 | } 8 | } 9 | } 10 | 11 | dependencyResolutionManagement { 12 | repositories { 13 | mavenCentral() 14 | maven("https://repo.polyfrost.org/releases") 15 | } 16 | } 17 | 18 | rootProject.name = "Spice" 19 | 20 | include(":modules") 21 | project(":modules").apply { 22 | buildFileName = "root.gradle.kts" 23 | } 24 | 25 | listOf( 26 | "lwjgl", 27 | "common", 28 | "core" 29 | ).forEach { module -> 30 | include(":modules:$module") 31 | } 32 | 33 | include(":platform") 34 | project(":platform").apply { 35 | projectDir = file("versions/") 36 | buildFileName = "preprocessor.gradle.kts" 37 | } 38 | 39 | listOf( 40 | "1.8.9-forge", 41 | "1.8.9-fabric", 42 | "1.12.2-fabric", 43 | "1.12.2-forge" 44 | ).forEach { version -> 45 | include(":platform:$version") 46 | project(":platform:$version").apply { 47 | projectDir = file("versions/$version") 48 | buildFileName = "../build.gradle.kts" 49 | } 50 | } -------------------------------------------------------------------------------- /versions/1.8.9-fabric/src/main/kotlin/org/polyfrost/spice/platform/impl/fabric/FabricInitializer.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.platform.impl.fabric 2 | 3 | import net.fabricmc.api.ModInitializer 4 | 5 | class FabricInitializer : ModInitializer { 6 | override fun onInitialize() {} 7 | } 8 | -------------------------------------------------------------------------------- /versions/1.8.9-fabric/src/main/kotlin/org/polyfrost/spice/platform/impl/fabric/FabricPlatform.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.platform.impl.fabric 2 | 3 | import org.polyfrost.spice.platform.api.Platform 4 | 5 | class FabricPlatform : Platform { 6 | init { 7 | instance = this 8 | } 9 | 10 | override val id = Platform.ID.Fabric 11 | 12 | companion object { 13 | var instance: FabricPlatform = FabricPlatform() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /versions/1.8.9-fabric/src/main/kotlin/org/polyfrost/spice/platform/impl/fabric/asm/MixinGenerator.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.platform.impl.fabric.asm 2 | 3 | import org.objectweb.asm.ClassWriter 4 | import org.objectweb.asm.Opcodes 5 | import org.objectweb.asm.Type 6 | 7 | fun generateMixin(base: String, target: String): ByteArray { 8 | val writer = ClassWriter(0) 9 | 10 | writer.visit( 11 | 52, 12 | Opcodes.ACC_PUBLIC or Opcodes.ACC_ABSTRACT or Opcodes.ACC_INTERFACE, 13 | "$base/$target", 14 | null, 15 | "java/lang/Object", 16 | null 17 | ) 18 | 19 | val mixinAnnotation = writer.visitAnnotation("Lorg/spongepowered/asm/mixin/Mixin;", false) 20 | val targetAnnotation = mixinAnnotation.visitArray("value") 21 | 22 | targetAnnotation.visit(null, Type.getType("L$target;")) 23 | targetAnnotation.visitEnd() 24 | 25 | mixinAnnotation.visitEnd() 26 | 27 | writer.visitEnd() 28 | 29 | return writer.toByteArray() 30 | } 31 | -------------------------------------------------------------------------------- /versions/1.8.9-fabric/src/main/kotlin/org/polyfrost/spice/platform/impl/fabric/asm/TransformerPlugin.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.platform.impl.fabric.asm 2 | 3 | import com.google.common.base.Stopwatch 4 | import org.apache.logging.log4j.LogManager 5 | import org.objectweb.asm.ClassReader 6 | import org.objectweb.asm.tree.ClassNode 7 | import org.polyfrost.spice.patcher.buildCache 8 | import org.polyfrost.spice.patcher.currentHash 9 | import org.polyfrost.spice.patcher.isCached 10 | import org.polyfrost.spice.patcher.loadCache 11 | import org.polyfrost.spice.platform.api.IClassTransformer 12 | import org.polyfrost.spice.platform.api.Transformer 13 | import org.polyfrost.spice.platform.bootstrapTransformer 14 | import org.polyfrost.spice.util.collectResources 15 | import org.polyfrost.spice.util.UrlByteArrayConnection 16 | import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin 17 | import org.spongepowered.asm.mixin.extensibility.IMixinInfo 18 | import java.lang.reflect.Method 19 | import java.net.URL 20 | import java.net.URLClassLoader 21 | import java.net.URLConnection 22 | import java.net.URLStreamHandler 23 | import java.util.concurrent.TimeUnit 24 | 25 | class TransformerPlugin : IMixinConfigPlugin, Transformer { 26 | private lateinit var transformerCache: Map 27 | 28 | private val mixinCache = mutableMapOf() 29 | private val classMapping = mutableMapOf() 30 | 31 | private val transformers = mutableListOf() 32 | private val transformedClasses = mutableSetOf() 33 | 34 | private lateinit var addUrl: Method 35 | private lateinit var loader: ClassLoader 36 | 37 | private val logger = LogManager.getLogger("Spice/Fabric/Transformer")!! 38 | 39 | @Suppress("UnstableApiUsage") 40 | override fun onLoad(mixinPackage: String) { 41 | logger.info("Initializing Fabric transformer") 42 | 43 | loader = javaClass.classLoader 44 | addUrl = loader 45 | .javaClass 46 | .declaredMethods 47 | .find { it.parameterCount == 1 && it.returnType == Void.TYPE && it.parameterTypes[0] == URL::class.java }!! 48 | .also { it.isAccessible = true } 49 | 50 | val urlLoaderField = loader 51 | .javaClass 52 | .declaredFields 53 | .find { it.type.superclass == URLClassLoader::class.java }!! 54 | .also { it.isAccessible = true } 55 | 56 | val base = mixinPackage.replace(".", "/") 57 | val urlLoader = urlLoaderField.get(loader) as URLClassLoader 58 | 59 | val stopwatch = Stopwatch.createStarted() 60 | 61 | transformerCache = loadCache(urlLoader) 62 | transformerCache.keys.forEach { classMapping["/$base/$it.class"] = it } 63 | 64 | stopwatch.stop() 65 | logger.info("Ready in ${stopwatch.elapsed(TimeUnit.MILLISECONDS)}ms") 66 | 67 | appendToClassPath(URL("transformer", null, -1, "/", object : URLStreamHandler() { 68 | override fun openConnection(url: URL): URLConnection? { 69 | return UrlByteArrayConnection( 70 | mixinCache.computeIfAbsent(classMapping[url.path] ?: return null) { target -> 71 | generateMixin(base, target) 72 | }, url 73 | ) 74 | } 75 | })) 76 | bootstrapTransformer(this) 77 | } 78 | 79 | override fun getRefMapperConfig(): String? = null 80 | 81 | override fun shouldApplyMixin(targetClassName: String, mixinClassName: String): Boolean = true 82 | 83 | override fun acceptTargets(myTargets: MutableSet?, otherTargets: MutableSet?) {} 84 | 85 | override fun getMixins(): List = 86 | classMapping.values 87 | .toList() 88 | .map { it.replace("/", ".") } 89 | 90 | override fun preApply( 91 | targetClassName: String, 92 | targetClass: ClassNode, 93 | mixinClassName: String, 94 | mixinInfo: IMixinInfo 95 | ) { 96 | val cached = transformerCache[targetClass.name] 97 | 98 | if (transformedClasses.contains(targetClassName)) return 99 | if (cached != null) { 100 | targetClass.version = cached.version 101 | targetClass.access = cached.access 102 | targetClass.name = cached.name 103 | targetClass.signature = cached.signature 104 | targetClass.superName = cached.superName 105 | targetClass.interfaces = cached.interfaces 106 | targetClass.sourceFile = cached.sourceFile 107 | targetClass.sourceDebug = cached.sourceDebug 108 | targetClass.module = cached.module 109 | targetClass.outerClass = cached.outerClass 110 | targetClass.outerMethod = cached.outerMethod 111 | targetClass.outerMethodDesc = cached.outerMethodDesc 112 | targetClass.visibleAnnotations = cached.visibleAnnotations 113 | targetClass.invisibleAnnotations = cached.invisibleAnnotations 114 | targetClass.visibleTypeAnnotations = cached.visibleTypeAnnotations 115 | targetClass.invisibleTypeAnnotations = cached.invisibleTypeAnnotations 116 | targetClass.attrs = cached.attrs 117 | targetClass.innerClasses = cached.innerClasses 118 | targetClass.nestHostClass = cached.nestHostClass 119 | targetClass.nestMembers = cached.nestMembers 120 | targetClass.permittedSubclasses = cached.permittedSubclasses 121 | targetClass.recordComponents = cached.recordComponents 122 | targetClass.fields = cached.fields 123 | targetClass.methods = cached.methods 124 | } 125 | 126 | transformers 127 | .filter { 128 | val targets = it.targets 129 | 130 | targets == null 131 | || targets.contains(targetClassName.replace("/", ".")) 132 | } 133 | .forEach { it.transform(targetClass) } 134 | transformedClasses += targetClassName 135 | } 136 | 137 | override fun postApply( 138 | targetClassName: String, 139 | targetClass: ClassNode, 140 | mixinClassName: String, 141 | mixinInfo: IMixinInfo 142 | ) { 143 | if (!mixinCache.containsKey(mixinClassName)) return 144 | 145 | targetClass.interfaces.remove(mixinClassName.replace(".", "/")) 146 | mixinCache.remove(mixinClassName) 147 | } 148 | 149 | override fun addTransformer(transformer: IClassTransformer) { 150 | transformers += transformer 151 | } 152 | 153 | override fun appendToClassPath(url: URL) { 154 | logger.info("Appending $url to the classpath") 155 | 156 | addUrl(loader, url) 157 | } 158 | 159 | @Suppress("UnstableApiUsage") 160 | private fun loadCache(urlLoader: URLClassLoader): Map { 161 | val hash = currentHash() 162 | val stopwatch = Stopwatch.createUnstarted() 163 | 164 | return if (!isCached(hash)) { 165 | logger.info("Cache does not contain an entry for $hash, beginning build") 166 | logger.info("Searching for LWJGL classes") 167 | stopwatch.start() 168 | 169 | val resources = 170 | collectResources(urlLoader.urLs) 171 | .filter { 172 | it.endsWith(".class") 173 | && it.startsWith("org/lwjgl/") 174 | } 175 | 176 | stopwatch.stop() 177 | 178 | logger.info("Found ${resources.size} LWJGL classes in ${stopwatch.elapsed(TimeUnit.MILLISECONDS)}ms") 179 | 180 | stopwatch.reset() 181 | stopwatch.start() 182 | 183 | val classes = resources.mapNotNull { resource -> 184 | ClassNode().also { node -> 185 | ClassReader( 186 | loader.getResourceAsStream(resource) 187 | ?.use { it.readBytes() } ?: return@mapNotNull null).accept(node, 0) 188 | } 189 | } 190 | 191 | logger.info("Transforming ${classes.size} classes") 192 | 193 | val transformed = buildCache(hash, classes) 194 | 195 | stopwatch.stop() 196 | 197 | logger.info("Transformed ${transformed.first.size}/${classes.size} classes") 198 | logger.info("Built cache in ${stopwatch.elapsed(TimeUnit.MILLISECONDS)}ms") 199 | 200 | transformed.first 201 | } else { 202 | logger.info("Loading classes from cache $hash") 203 | stopwatch.start() 204 | 205 | val cache = loadCache(hash) 206 | 207 | stopwatch.stop() 208 | logger.info("Loaded ${cache.size} cached classes in ${stopwatch.elapsed(TimeUnit.MILLISECONDS)}ms") 209 | 210 | cache 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /versions/1.8.9-fabric/src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "spice", 4 | "version": "${version}", 5 | "name": "Spice", 6 | "description": "optimization & qol mod for minecraft 1.8 and 1.12", 7 | "authors": [ 8 | "Polyfrost", 9 | "zani" 10 | ], 11 | "contact": { 12 | "homepage": "https://github.com/Polyfrost/Spice#readme", 13 | "sources": "https://github.com/Polyfrost/Spice" 14 | }, 15 | "license": "GPL-3.0", 16 | "environment": "client", 17 | "entrypoints": { 18 | "main": [ 19 | "org.polyfrost.spice.platform.impl.fabric.FabricInitializer" 20 | ] 21 | }, 22 | "mixins": [ 23 | "spice.mixins.json", 24 | "transformer.mixins.json" 25 | ], 26 | "depends": { 27 | "fabricloader": ">=0.14.19", 28 | "minecraft": "${mcVersionStr}" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /versions/1.8.9-fabric/src/main/resources/transformer.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": false, 3 | "minVersion": "0.7", 4 | "package": "org.polyfrost.spice.transformer.generated", 5 | "compatibilityLevel": "JAVA_8", 6 | "mixins": [], 7 | "refmap": "transformer.mixins.refmap.json", 8 | "plugin": "org.polyfrost.spice.platform.impl.fabric.asm.TransformerPlugin", 9 | "injectors": { 10 | "defaultRequire": 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /versions/1.8.9-fabric/src/main/resources/transformer.mixins.refmap.json: -------------------------------------------------------------------------------- 1 | { 2 | "mappings": {}, 3 | "data": {} 4 | } 5 | -------------------------------------------------------------------------------- /versions/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | import org.polyfrost.gradle.util.noServerRunConfigs 4 | 5 | plugins { 6 | kotlin("jvm") version libs.versions.kotlin.get() 7 | id(libs.plugins.pgt.main.get().pluginId) 8 | id(libs.plugins.pgt.defaults.repo.get().pluginId) 9 | id(libs.plugins.pgt.defaults.java.get().pluginId) 10 | id(libs.plugins.pgt.defaults.loom.get().pluginId) 11 | id("com.github.johnrengelman.shadow") 12 | `java-library` 13 | } 14 | 15 | val tweakClass = "org.polyfrost.spice.platform.impl.forge.asm.SpiceTweaker" 16 | 17 | base.archivesName = "Spice-${platform}" 18 | 19 | kotlin { 20 | jvmToolchain(8) 21 | } 22 | 23 | loom { 24 | noServerRunConfigs() 25 | runConfigs { 26 | "client" { 27 | if (project.platform.isLegacyForge) { 28 | programArgs("--tweakClass", tweakClass) 29 | } 30 | property("mixin.debug.export", "true") 31 | property("debugBytecode", "true") 32 | property("forge.logging.console.level", "debug") 33 | if (org.gradle.internal.os.OperatingSystem.current().isMacOsX) { 34 | property("fml.earlyprogresswindow", "false") 35 | } 36 | } 37 | } 38 | if (project.platform.isForge) { 39 | forge { 40 | mixinConfig("spice.mixins.json") 41 | } 42 | } 43 | mixin.defaultRefmapName.set("spice.mixins.refmap.json") 44 | } 45 | 46 | val shadowImpl by configurations.creating { 47 | configurations.implementation.get().extendsFrom(this) 48 | } 49 | 50 | dependencies { 51 | shadowImpl(project(":modules:core")) { 52 | if (platform.isFabric) { 53 | exclude("org.apache.logging.log4j") 54 | exclude("org.ow2.asm") 55 | } else { 56 | isTransitive = false 57 | } 58 | } 59 | 60 | if (platform.isLegacyFabric) { // Legacy Fabric bug 61 | compileOnly(runtimeOnly("org.apache.logging.log4j:log4j-core:2.8.1")!!) 62 | compileOnly(runtimeOnly("org.apache.logging.log4j:log4j-api:2.8.1")!!) 63 | } 64 | 65 | shadowImpl(rootProject.libs.kotlinx.coroutines) 66 | 67 | if (platform.isLegacyForge) { 68 | shadowImpl(rootProject.libs.kotlinx.serialization.json) 69 | shadowImpl(project(":modules:common")) { 70 | isTransitive = false 71 | } 72 | } else { 73 | compileOnly(project(":modules:lwjgl")) 74 | } 75 | 76 | if (platform.isLegacyForge) { 77 | shadowImpl(libs.mixinsForge) { 78 | isTransitive = false 79 | } 80 | } else { 81 | compileOnly(libs.mixins) 82 | } 83 | 84 | if (!platform.isLegacyFabric) { 85 | modRuntimeOnly( 86 | "me.djtheredstoner:DevAuth-" + 87 | (if (platform.isForge) { 88 | if (platform.isLegacyForge) "forge-legacy" else "forge-latest" 89 | } else "fabric") 90 | + ":1.2.1" 91 | ) 92 | } 93 | } 94 | 95 | tasks { 96 | jar { 97 | dependsOn(shadowJar) 98 | archiveBaseName.set("spice") 99 | archiveClassifier.set("no-deps") 100 | 101 | destinationDirectory.set(layout.buildDirectory.dir("badjars")) 102 | if (platform.isLegacyForge) { 103 | manifest { 104 | attributes += mapOf( 105 | "ModSide" to "CLIENT", 106 | "ForceLoadAsMod" to true, 107 | "TweakOrder" to "0", 108 | "MixinConfigs" to "spice.mixins.json", 109 | "TweakClass" to tweakClass 110 | ) 111 | } 112 | } 113 | } 114 | 115 | shadowJar { 116 | destinationDirectory.set(layout.buildDirectory.dir("badjars")) 117 | configurations = listOf(shadowImpl) 118 | } 119 | 120 | withType(Jar::class) { 121 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 122 | exclude("META-INF/com.android.tools/**") 123 | exclude("META-INF/proguard/**") 124 | if (platform.isFabric) { 125 | exclude("mcmod.info", "META-INF/mods.toml") 126 | } else { 127 | exclude("fabric.mod.json") 128 | if (platform.isLegacyForge) { 129 | exclude("**/mods.toml") 130 | exclude( 131 | "**/module-info.class", 132 | "**/package-info.class", 133 | "META-INF/proguard/**", 134 | "META-INF/maven/**", 135 | "META-INF/versions/**", 136 | "META-INF/com.android.tools/**", 137 | ) 138 | } 139 | } 140 | } 141 | 142 | processResources { 143 | from( 144 | project(":modules:lwjgl") 145 | .tasks 146 | .shadowJar 147 | .get() 148 | .archiveFile 149 | ) 150 | 151 | inputs.property("id", rootProject.properties["mod_id"].toString()) 152 | inputs.property("name", rootProject.name) 153 | inputs.property("java", 8) 154 | inputs.property("version", version) 155 | inputs.property( 156 | "mcVersionStr", 157 | if (platform.isFabric) platform.mcVersionStr.substringBeforeLast('.') + ".x" else platform.mcVersionStr 158 | ) 159 | 160 | val id = inputs.properties["id"] 161 | val name = inputs.properties["name"] 162 | val version = inputs.properties["version"] 163 | val mcVersionStr = inputs.properties["mcVersionStr"].toString() 164 | val java = inputs.properties["java"].toString().toInt() 165 | val javaLevel = "JAVA-$java" 166 | 167 | filesMatching(listOf("mcmod.info", "spice.mixins.json", "**/mods.toml", "fabric.mod.json")) { 168 | expand( 169 | mapOf( 170 | "id" to id, 171 | "name" to name, 172 | "java" to java, 173 | "java_level" to javaLevel, 174 | "version" to version, 175 | "mcVersionStr" to mcVersionStr 176 | ) 177 | ) 178 | } 179 | } 180 | 181 | remapJar { 182 | inputFile.set(shadowJar.get().archiveFile) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /versions/mainProject: -------------------------------------------------------------------------------- 1 | 1.8.9-forge -------------------------------------------------------------------------------- /versions/mappings/fabric-forge-1.8.9.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Polyfrost/Spice/4fc723e1d72860b750055ec3bea0ce6a4df747ac/versions/mappings/fabric-forge-1.8.9.txt -------------------------------------------------------------------------------- /versions/mappings/forge-1.12.2-1.8.9.txt: -------------------------------------------------------------------------------- 1 | net.minecraft.crash.CrashReportCategory addDetail() net.minecraft.crash.CrashReportCategory addCrashSectionCallable() -------------------------------------------------------------------------------- /versions/preprocessor.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version libs.versions.kotlin.get() apply false 3 | id(libs.plugins.pgt.root.get().pluginId) 4 | } 5 | 6 | preprocess { 7 | // FOR ALL NEW VERSIONS ENSURE TO UPDATE settings.gradle.kts ! 8 | 9 | val forge10809 = createNode("1.8.9-forge", 10809, "srg") 10 | val fabric10809 = createNode("1.8.9-fabric", 10809, "yarn") 11 | val forge11202 = createNode("1.12.2-forge", 11202, "srg") 12 | val fabric11202 = createNode("1.12.2-fabric", 11202, "yarn") 13 | 14 | fabric11202.link(fabric10809) 15 | fabric10809.link(forge10809, file("mappings/fabric-forge-1.8.9.txt")) 16 | forge11202.link(forge10809, file("mappings/forge-1.12.2-1.8.9.txt")) 17 | } -------------------------------------------------------------------------------- /versions/src/main/java/org/polyfrost/spice/mixin/common/GuiOverlayDebugMixin.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.mixin.common; 2 | 3 | import net.minecraft.client.gui.GuiOverlayDebug; 4 | import org.polyfrost.spice.debug.DebugHelper; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Unique; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 10 | import org.spongepowered.asm.mixin.injection.callback.LocalCapture; 11 | 12 | import java.util.List; 13 | 14 | @Mixin(GuiOverlayDebug.class) 15 | public abstract class GuiOverlayDebugMixin { 16 | @Unique 17 | private static final String REDUCED_DEBUG_DESC = 18 | //#if FABRIC 19 | //#if MC>=11200 20 | //$$ "Lnet/minecraft/client/MinecraftClient;method_13408()Z"; // i love incomplete mappings. todo switch to mcp for legacy fabric as well? 21 | //#else 22 | //$$ "Lnet/minecraft/client/gui/hud/DebugHud;hasReducedDebugInfo()Z"; 23 | //#endif 24 | //#elseif MC>=11200 25 | //$$ "Lnet/minecraft/client/Minecraft;isReducedDebug()Z"; 26 | //#else 27 | "Lnet/minecraft/client/gui/GuiOverlayDebug;isReducedDebug()Z"; 28 | //#endif 29 | @Inject(method = "getDebugInfoRight", locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", target = REDUCED_DEBUG_DESC)) 30 | private void addSpiceDebugInfo(CallbackInfoReturnable> cir, long max, long total, long free, long used, List lines) { 31 | DebugHelper.applyExtraDebugInfo(lines); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /versions/src/main/java/org/polyfrost/spice/mixin/common/MinecraftMixin.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.mixin.common; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import net.minecraft.crash.CrashReport; 5 | import net.minecraft.crash.CrashReportCategory; 6 | import org.polyfrost.spice.Spice; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Unique; 9 | import org.spongepowered.asm.mixin.injection.At; 10 | import org.spongepowered.asm.mixin.injection.Inject; 11 | import org.spongepowered.asm.mixin.injection.Redirect; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 14 | 15 | import java.util.concurrent.Callable; 16 | 17 | import static org.polyfrost.spice.fixes.EarlyCrashFixKt.getFieldValue; 18 | import static org.polyfrost.spice.platform.BootstrapKt.bootstrap; 19 | 20 | @Mixin(Minecraft.class) 21 | public abstract class MinecraftMixin { 22 | @Unique 23 | private static final String ADD_CRASH_SECTION_DESC = 24 | //#if MC>=11200 25 | //#if FABRIC 26 | //$$ "Lnet/minecraft/util/crash/CrashReportSection;add(Ljava/lang/String;Lnet/minecraft/util/crash/CrashCallable;)V"; 27 | //#else 28 | //$$ "Lnet/minecraft/crash/CrashReportCategory;addDetail(Ljava/lang/String;Lnet/minecraft/crash/ICrashReportDetail;)V"; 29 | //#endif 30 | //#elseif FABRIC 31 | //$$ "Lnet/minecraft/util/crash/CrashReportSection;add(Ljava/lang/String;Ljava/util/concurrent/Callable;)V"; 32 | //#else 33 | "Lnet/minecraft/crash/CrashReportCategory;addCrashSectionCallable(Ljava/lang/String;Ljava/util/concurrent/Callable;)V"; 34 | //#endif 35 | @Inject(method = "addGraphicsAndWorldToCrashReport", at = @At("HEAD")) 36 | private void addSpiceInfo(CrashReport instance, CallbackInfoReturnable cir) { 37 | instance.getCategory().addCrashSectionCallable("Spice", Spice::getVersion); 38 | instance.getCategory().addCrashSectionCallable("GLFW", Spice::getGlfwVersion); 39 | } 40 | 41 | // in case we cause a crash too early 42 | @Redirect(method = "addGraphicsAndWorldToCrashReport", at = @At(value = "INVOKE", target = ADD_CRASH_SECTION_DESC)) 43 | private void fixupCrashReport(CrashReportCategory instance, String name, 44 | //#if MC>=11200 45 | //#if FABRIC 46 | //$$ net.minecraft.util.crash.CrashCallable value 47 | //#else 48 | //$$ net.minecraft.crash.ICrashReportDetail value 49 | //#endif 50 | //#else 51 | Callable value 52 | //#endif 53 | 54 | ) { 55 | instance.addCrashSection(name, getFieldValue(name, value)); 56 | } 57 | 58 | @Inject(method = "run", at = @At("HEAD")) 59 | private void initialize(CallbackInfo ci) { 60 | bootstrap(org.polyfrost.spice.platform.impl. 61 | //#if FABRIC 62 | //$$ fabric.FabricPlatform 63 | //#else 64 | forge.ForgePlatform 65 | //#endif 66 | .Companion.getInstance()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /versions/src/main/java/org/polyfrost/spice/mixin/lwjgl3/GuiControlsMixin.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.mixin.lwjgl3; 2 | 3 | import net.minecraft.client.gui.GuiButton; 4 | import net.minecraft.client.gui.GuiControls; 5 | import net.minecraft.client.gui.GuiScreen; 6 | import net.minecraft.client.settings.GameSettings; 7 | import org.polyfrost.spice.Options; 8 | import org.polyfrost.spice.Spice; 9 | import org.polyfrost.spice.api.Mouse; 10 | import org.spongepowered.asm.mixin.Final; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Shadow; 13 | import org.spongepowered.asm.mixin.Unique; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Inject; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 17 | 18 | @Mixin(GuiControls.class) 19 | public abstract class GuiControlsMixin extends GuiScreen { 20 | 21 | @Shadow @Final private static GameSettings.Options[] optionsArr; 22 | @Unique 23 | private final Options spice$options = Spice.getOptions(); 24 | @Unique 25 | private GuiButton spice$rawInputButton; 26 | @Unique 27 | private static final String INIT_GUI_DESC = 28 | //#if FABRIC 29 | //$$ "init"; 30 | //#else 31 | "initGui"; 32 | //#endif 33 | 34 | @Unique 35 | private static final String BUTTON_PRESSED_DESC = 36 | //#if FABRIC 37 | //$$ "buttonClicked"; 38 | //#else 39 | "actionPerformed"; 40 | //#endif 41 | 42 | @SuppressWarnings("AccessStaticViaInstance") // preprocessor requires `this` to be present 43 | @Inject(method = INIT_GUI_DESC, at = @At("TAIL")) 44 | private void injectButton(CallbackInfo ci) { 45 | if (!Mouse.isRawInputSupported()) return; 46 | 47 | spice$rawInputButton = new GuiButton( 48 | 300, 49 | width / 2 - 155 + this.optionsArr.length % 2 * 160, 50 | 18 + 24 * (this.optionsArr.length >> 1), 51 | 150, 52 | 20, 53 | spice$formatButtonText() 54 | ); 55 | 56 | this.buttonList.add(spice$rawInputButton); 57 | } 58 | 59 | @Inject(method = BUTTON_PRESSED_DESC, at = @At("HEAD"), cancellable = true) 60 | private void handleAction(GuiButton button, CallbackInfo ci) { 61 | if (!Mouse.isRawInputSupported()) return; 62 | 63 | if (button.id == 200) { 64 | Spice.saveOptions(); 65 | } 66 | 67 | if (button.id == spice$rawInputButton.id) { 68 | spice$options.rawInput = !spice$options.rawInput; 69 | spice$options.needsSave = true; 70 | 71 | Mouse.setRawInput(spice$options.rawInput); 72 | 73 | button.displayString = spice$formatButtonText(); 74 | 75 | ci.cancel(); 76 | } 77 | } 78 | 79 | @Unique 80 | private String spice$formatButtonText() { 81 | return "Raw Input: " + (spice$options.rawInput ? "ON" : "OFF"); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /versions/src/main/java/org/polyfrost/spice/mixin/lwjgl3/MinecraftMixin.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.mixin.lwjgl3; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import org.polyfrost.spice.Spice; 5 | import org.polyfrost.spice.api.Mouse; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | @Mixin(Minecraft.class) 12 | public abstract class MinecraftMixin { 13 | @Inject(method = "createDisplay", at = @At("TAIL")) 14 | private void setupRawInput(CallbackInfo ci) { 15 | if (!Mouse.isRawInputSupported()) return; 16 | 17 | Mouse.setRawInput(Spice.getOptions().rawInput); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /versions/src/main/java/org/polyfrost/spice/mixin/lwjgl3/compat/EssentialGlobalMouseOverrideMixin.java: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.mixin.lwjgl3.compat; 2 | 3 | import net.minecraft.client.Minecraft; 4 | import org.polyfrost.spice.api.Mouse; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.Overwrite; 7 | import org.spongepowered.asm.mixin.Pseudo; 8 | 9 | @Pseudo 10 | @Mixin(targets = "gg.essential.gui.overlay.OverlayManagerImpl$GlobalMouseOverride", remap = false) 11 | public class EssentialGlobalMouseOverrideMixin { 12 | /** 13 | * @author Wyvest 14 | * @reason Fix silly Essential reflection 15 | */ 16 | @Overwrite 17 | public final void set(double mouseX, double mouseY) { 18 | int trueX = (int)mouseX; 19 | int trueY = Minecraft.getMinecraft().displayHeight - (int)mouseY - 1; 20 | Mouse.setX(trueX); 21 | Mouse.setY(trueY); 22 | Mouse.setEventX(trueX); 23 | Mouse.setEventY(trueY); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /versions/src/main/kotlin/org/polyfrost/spice/platform/impl/forge/ForgeInitializer.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.platform.impl.forge 2 | //#if FORGE 3 | 4 | import net.minecraftforge.fml.common.Mod 5 | 6 | @Mod(modid = "spice", useMetadata = true) 7 | class ForgeInitializer { 8 | } 9 | //#endif -------------------------------------------------------------------------------- /versions/src/main/kotlin/org/polyfrost/spice/platform/impl/forge/ForgePlatform.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.platform.impl.forge 2 | //#if FORGE 3 | 4 | import org.polyfrost.spice.platform.api.Platform 5 | 6 | class ForgePlatform : Platform { 7 | init { 8 | instance = this 9 | } 10 | 11 | override val id = Platform.ID.Forge 12 | 13 | companion object { 14 | var instance: ForgePlatform = ForgePlatform() 15 | } 16 | } 17 | //#endif -------------------------------------------------------------------------------- /versions/src/main/kotlin/org/polyfrost/spice/platform/impl/forge/asm/ClassTransformer.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.platform.impl.forge.asm 2 | //#if FORGE 3 | 4 | import com.google.common.base.Stopwatch 5 | import net.minecraft.launchwrapper.Launch 6 | import net.minecraft.launchwrapper.LogWrapper 7 | import org.apache.logging.log4j.LogManager 8 | import org.objectweb.asm.ClassReader 9 | import org.objectweb.asm.ClassWriter.COMPUTE_FRAMES 10 | import org.objectweb.asm.tree.ClassNode 11 | import org.polyfrost.spice.patcher.buildCache 12 | import org.polyfrost.spice.patcher.currentHash 13 | import org.polyfrost.spice.patcher.isCached 14 | import org.polyfrost.spice.patcher.loadCacheBuffers 15 | import org.polyfrost.spice.platform.api.IClassTransformer 16 | import org.polyfrost.spice.platform.api.Transformer 17 | import org.polyfrost.spice.platform.bootstrapTransformer 18 | import org.polyfrost.spice.platform.impl.forge.util.LaunchWrapperLogger 19 | import org.polyfrost.spice.util.SpiceClassWriter 20 | import org.polyfrost.spice.util.collectResources 21 | import java.net.URL 22 | import java.net.URLClassLoader 23 | import java.util.concurrent.TimeUnit 24 | import net.minecraft.launchwrapper.IClassTransformer as LaunchTransformer 25 | 26 | @Suppress("UnstableApiUsage") 27 | class ClassTransformer : LaunchTransformer, Transformer { 28 | private val outputBytecode = System.getProperty("debugBytecode", "false").toBoolean() 29 | 30 | private val transformerCache: Map 31 | 32 | private val loader = Launch.classLoader 33 | 34 | private val logger = LogManager.getLogger("Spice/Forge/Transformer") 35 | private val transformers = mutableListOf() 36 | 37 | init { 38 | logger.info("Initializing Forge transformer") 39 | logger.info("Removing LWJGL exclusion") 40 | 41 | @Suppress("UNCHECKED_CAST") 42 | val exclusions = loader::class.java 43 | .getDeclaredField("classLoaderExceptions") 44 | .also { it.isAccessible = true } 45 | .get(loader) as MutableSet 46 | 47 | exclusions.remove("org.lwjgl.") 48 | 49 | LogWrapper.retarget(LaunchWrapperLogger) 50 | 51 | val stopwatch = Stopwatch.createStarted() 52 | 53 | bootstrapTransformer(this) 54 | transformerCache = loadCache() 55 | 56 | logger.info("Ready in ${stopwatch.elapsed(TimeUnit.MILLISECONDS)}ms") 57 | } 58 | 59 | override fun transform(name: String, transformedName: String, bytes: ByteArray?): ByteArray? { 60 | if (bytes == null) return null 61 | if (name.startsWith("org.lwjgl.")) logger.info("Transforming $name") 62 | 63 | @Suppress("NAME_SHADOWING") 64 | val bytes = transformerCache[name.replace(".", "/")] ?: bytes 65 | val validTransformers = transformers.filter { 66 | val targets = it.targets 67 | 68 | targets == null 69 | || targets.contains(name) 70 | } 71 | 72 | return if (validTransformers.isNotEmpty()) { 73 | val node = ClassNode().also { ClassReader(bytes).accept(it, 0) } 74 | 75 | validTransformers.forEach { it.transform(node) } 76 | 77 | SpiceClassWriter(COMPUTE_FRAMES) 78 | .also { node.accept(it) } 79 | .toByteArray() 80 | } else bytes 81 | } 82 | 83 | override fun addTransformer(transformer: IClassTransformer) { 84 | transformers += transformer 85 | } 86 | 87 | override fun appendToClassPath(url: URL) { 88 | logger.info("Appending $url to the classpath") 89 | loader.addURL(url) 90 | 91 | val parent = loader::class.java.classLoader 92 | if (parent is URLClassLoader) { 93 | URLClassLoader::class.java 94 | .getDeclaredMethod("addURL", URL::class.java) 95 | .also { method -> method.isAccessible = true }(parent, url) 96 | } else { 97 | parent::class.java 98 | .let { clazz -> 99 | try { 100 | clazz.getDeclaredField("ucp") 101 | } catch (_: NoSuchFieldException) { 102 | clazz.superclass.getDeclaredField("ucp") 103 | } 104 | } 105 | .also { field -> field.isAccessible = true } 106 | .get(loader) 107 | .let { classpath -> 108 | classpath::class.java 109 | .getDeclaredMethod("addURL", URL::class.java)(loader, url) 110 | } 111 | } 112 | } 113 | 114 | private fun loadCache(): Map { 115 | val hash = currentHash() 116 | val stopwatch = Stopwatch.createUnstarted() 117 | 118 | return if (!isCached(hash)) { 119 | logger.info("Cache does not contain an entry for $hash, beginning build") 120 | logger.info("Searching for LWJGL classes") 121 | stopwatch.start() 122 | 123 | val resources = 124 | collectResources(loader.urLs) 125 | .filter { 126 | it.endsWith(".class") 127 | && it.startsWith("org/lwjgl/") 128 | } 129 | 130 | stopwatch.stop() 131 | 132 | logger.info("Found ${resources.size} LWJGL classes in ${stopwatch.elapsed(TimeUnit.MILLISECONDS)}ms") 133 | 134 | stopwatch.reset() 135 | stopwatch.start() 136 | 137 | val classes = resources.mapNotNull { resource -> 138 | ClassNode().also { node -> 139 | ClassReader( 140 | loader.getResourceAsStream(resource) 141 | ?.use { it.readBytes() } ?: return@mapNotNull null).accept(node, 0) 142 | } 143 | } 144 | 145 | logger.info("Transforming ${classes.size} classes") 146 | 147 | val transformed = buildCache(hash, classes) 148 | 149 | stopwatch.stop() 150 | 151 | logger.info("Transformed ${transformed.first.size}/${classes.size} classes") 152 | logger.info("Built cache in ${stopwatch.elapsed(TimeUnit.MILLISECONDS)}ms") 153 | 154 | return transformed.second 155 | } else { 156 | logger.info("Loading classes from cache $hash") 157 | stopwatch.start() 158 | 159 | val cache = loadCacheBuffers(hash) 160 | 161 | stopwatch.stop() 162 | logger.info("Loaded ${cache.size} cached classes in ${stopwatch.elapsed(TimeUnit.MILLISECONDS)}ms") 163 | 164 | cache 165 | } 166 | } 167 | } 168 | //#endif 169 | -------------------------------------------------------------------------------- /versions/src/main/kotlin/org/polyfrost/spice/platform/impl/forge/asm/LwjglAccessTracer.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.platform.impl.forge.asm 2 | 3 | //#if FORGE 4 | 5 | import net.minecraft.launchwrapper.IClassTransformer 6 | import org.apache.logging.log4j.LogManager 7 | 8 | class LwjglAccessTracer : IClassTransformer { 9 | private val logger = LogManager.getLogger("Spice/Transformer/Access_Tracer") 10 | 11 | override fun transform(name: String, transformedName: String, basicClass: ByteArray?): ByteArray? { 12 | if (logger.isTraceEnabled && name.startsWith("org.lwjgl.")) { 13 | val currentStack = Thread.currentThread().stackTrace.drop(4).joinToString("\n") { 14 | "\t${it.className}::${it.methodName} at ${it.lineNumber}" 15 | } 16 | 17 | logger.trace("$name from\n${currentStack}") 18 | } 19 | 20 | return basicClass 21 | } 22 | } 23 | 24 | //#endif -------------------------------------------------------------------------------- /versions/src/main/kotlin/org/polyfrost/spice/platform/impl/forge/asm/SpiceTweaker.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.platform.impl.forge.asm 2 | //#if FORGE 3 | 4 | import net.minecraft.launchwrapper.ITweaker 5 | import net.minecraft.launchwrapper.Launch 6 | import net.minecraft.launchwrapper.LaunchClassLoader 7 | import org.spongepowered.asm.launch.MixinBootstrap 8 | import org.spongepowered.asm.mixin.Mixins 9 | import java.io.File 10 | 11 | 12 | class SpiceTweaker : ITweaker { 13 | private val mixinTweaker: String = "org.spongepowered.asm.launch.MixinTweaker" 14 | 15 | init { 16 | injectMixinTweaker() 17 | } 18 | 19 | override fun acceptOptions(args: MutableList, gameDir: File?, assetsDir: File?, profile: String?) { 20 | 21 | } 22 | 23 | override fun injectIntoClassLoader(classLoader: LaunchClassLoader) { 24 | Launch.classLoader.registerTransformer(LwjglAccessTracer::class.java.name) 25 | Launch.classLoader.registerTransformer(ClassTransformer::class.java.name) 26 | 27 | MixinBootstrap.init() 28 | Mixins.addConfiguration("spice.mixins.json") 29 | } 30 | 31 | override fun getLaunchTarget(): String? = null 32 | override fun getLaunchArguments(): Array = arrayOf() 33 | 34 | @Suppress("UNCHECKED_CAST") 35 | @Throws(ClassNotFoundException::class, IllegalAccessException::class, InstantiationException::class) 36 | private fun injectMixinTweaker() { 37 | val tweakClasses = Launch.blackboard["TweakClasses"]!! as List 38 | 39 | if (tweakClasses.contains(mixinTweaker)) { 40 | initMixinTweaker() 41 | return 42 | } 43 | 44 | if (Launch.blackboard["mixin.initialised"] != null) return 45 | 46 | (Launch.blackboard["Tweaks"]!! as MutableList).add(initMixinTweaker()) 47 | } 48 | 49 | @Throws(ClassNotFoundException::class, IllegalAccessException::class, InstantiationException::class) 50 | private fun initMixinTweaker(): ITweaker { 51 | Launch.classLoader.addClassLoaderExclusion(mixinTweaker.substring(0, mixinTweaker.lastIndexOf("."))) 52 | 53 | return Class.forName(mixinTweaker, true, Launch.classLoader).newInstance() as ITweaker 54 | } 55 | } 56 | 57 | //#endif 58 | -------------------------------------------------------------------------------- /versions/src/main/kotlin/org/polyfrost/spice/platform/impl/forge/util/LaunchWrapperLogger.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.platform.impl.forge.util 2 | 3 | import org.apache.logging.log4j.Level 4 | import org.apache.logging.log4j.LogManager 5 | import org.apache.logging.log4j.Logger 6 | 7 | private val base = LogManager.getLogger("LaunchWrapper") 8 | 9 | object LaunchWrapperLogger : Logger by base { 10 | override fun log(level: Level, format: String, vararg params: Any?) { 11 | when (format) { 12 | "The jar file %s is trying to seal already secured path %s" -> return 13 | "The jar file %s has a security seal for path %s, but that path is defined and not secure" -> return 14 | "The URL %s is defining elements for sealed path %s" -> return 15 | } 16 | 17 | base.log(level, format, params) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /versions/src/main/kotlin/org/polyfrost/spice/platform/impl/forge/util/Relaunch.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.platform.impl.forge.util 2 | 3 | import java.lang.management.ManagementFactory 4 | import kotlin.io.path.Path 5 | 6 | fun relaunch() { 7 | val runtimeBean = ManagementFactory.getRuntimeMXBean() 8 | val binary = Path(System.getProperty("sun.boot.library.path")).resolve("java.exe") 9 | 10 | val builder = ProcessBuilder() 11 | .command( 12 | binary.toAbsolutePath().toString(), 13 | *runtimeBean.inputArguments.toTypedArray(), 14 | "-cp", 15 | runtimeBean.classPath, 16 | *System.getProperty("sun.java.command").split(" ").toTypedArray() 17 | ) 18 | 19 | println("Starting process: ${builder.command().joinToString(" ")}") 20 | 21 | val process = 22 | builder 23 | .inheritIO() 24 | .start() 25 | 26 | Runtime.getRuntime().addShutdownHook(Thread(process::destroy)) 27 | Runtime.getRuntime().halt(process.waitFor()) 28 | } -------------------------------------------------------------------------------- /versions/src/main/kotlin/org/polyfrost/spice/util/Classpath.kt: -------------------------------------------------------------------------------- 1 | package org.polyfrost.spice.util 2 | 3 | import java.io.File 4 | import java.net.URL 5 | import java.util.jar.JarInputStream 6 | 7 | //TODO we can probably move this to core 8 | 9 | fun collectResources(urls: Array): List = 10 | urls 11 | .filter { it.protocol != "spice" 12 | //#if FORGE 13 | && it.protocol != "asmgen" 14 | //#endif 15 | } 16 | .map { 17 | runCatching { 18 | val file = File(it.toURI()) 19 | 20 | if (file.isDirectory) return@map emptyList() 21 | } 22 | 23 | JarInputStream(it.openStream()) 24 | .use { stream -> 25 | val entries = mutableListOf() 26 | 27 | while (true) entries += stream.nextEntry?.name ?: break 28 | 29 | entries 30 | } 31 | } 32 | .flatten() 33 | -------------------------------------------------------------------------------- /versions/src/main/resources/mcmod.info: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "modid": "spice", 4 | "name": "Spice", 5 | "description": "optimization & qol mod for minecraft 1.8 and 1.12", 6 | "version": "${version}", 7 | "mcversion": "${mcVersionStr}", 8 | "url": "https://github.com/Polyfrost/Spice", 9 | "updateUrl": "", 10 | "authorList": [ 11 | "Polyfrost", 12 | "zani" 13 | ], 14 | "credits": "", 15 | "logoFile": "", 16 | "screenshots": [], 17 | "dependencies": [] 18 | } 19 | ] -------------------------------------------------------------------------------- /versions/src/main/resources/spice.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": false, 3 | "minVersion": "0.7", 4 | "package": "org.polyfrost.spice.mixin", 5 | "compatibilityLevel": "JAVA_8", 6 | "verbose": true, 7 | "client": [ 8 | "common.MinecraftMixin", 9 | "lwjgl3.compat.EssentialGlobalMouseOverrideMixin", 10 | "lwjgl3.GuiControlsMixin", 11 | "lwjgl3.MinecraftMixin" 12 | ], 13 | "injectors": { 14 | "defaultRequire": 1 15 | } 16 | } 17 | --------------------------------------------------------------------------------