diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 43a60f7..e977170 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -11,11 +11,11 @@ jobs:
- name: Checkout sources
uses: actions/checkout@v7
- - name: Setup JDK 25
+ - name: Setup JDK 21
uses: actions/setup-java@v5
with:
distribution: temurin
- java-version: 25
+ java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
@@ -26,5 +26,5 @@ jobs:
- name: Upload Artifact
uses: actions/upload-artifact@v7
with:
- name: lenis-nightly
+ name: pylon-nightly
path: build/libs
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 42ef88c..e65d6fe 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -16,11 +16,11 @@ jobs:
- name: Checkout sources
uses: actions/checkout@v7
- - name: Setup JDK 25
+ - name: Setup JDK 21
uses: actions/setup-java@v5
with:
distribution: temurin
- java-version: 25
+ java-version: 21
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
diff --git a/.gitignore b/.gitignore
index 0ecd304..28456e5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,7 @@
.gradle
.DS_Store
.idea/
+.kotlin/
build/
+!**/tomgirl/pylon/build
run/
\ No newline at end of file
diff --git a/README.md b/README.md
index f4f36e3..d7cccbc 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,11 @@
-
+
-# Lenis
-Lenis allows legacy Minecraft versions to run with LWJGL 3. It replaces the outdated windowing system with SDL 3, and serves as a library for mods.
-
-Currently only 1.8.9 on Ornithe is supported, with support for more versions coming soon.
+# Pylon
+Pylon allows legacy Minecraft versions to run with LWJGL 3. It replaces the outdated windowing system with SDL 3, and serves as a library for mods.
## Downloads
-* [Latest Release](https://github.com/notdevcody/lenis/releases/latest)
-* [Latest Nightly](https://nightly.link/notdevcody/lenis/workflows/nightly/main/lenis-nightly.zip)
+* [Latest Release](https://github.com/notdevcody/pylon/releases/latest)
+* [Latest Nightly](https://nightly.link/notdevcody/pylon/workflows/nightly/main/pylon-nightly.zip)
## Features
### Fixes
@@ -24,13 +22,13 @@ Currently only 1.8.9 on Ornithe is supported, with support for more versions com
- Better fullscreen and window resizing
## Developers
-Lenis is published to the Clover Client Maven, and can be added as a dependency like so:
+Pylon is published to the Clover Client Maven, and can be added as a dependency like so:
```kts
repositories {
maven("https://maven.cloverclient.com/releases")
}
dependencies {
- modImplementation("pl.tomgirl:lenis:${version}")
+ modImplementation("pl.tomgirl:pylon:${version}")
}
```
diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts
new file mode 100644
index 0000000..3bbb5c1
--- /dev/null
+++ b/build-logic/build.gradle.kts
@@ -0,0 +1,23 @@
+plugins {
+ `kotlin-dsl`
+}
+
+repositories {
+ gradlePluginPortal()
+ mavenCentral()
+}
+
+dependencies {
+ implementation("com.gradleup.shadow:shadow-gradle-plugin:9.3.0")
+ implementation("org.ow2.asm:asm:9.10.1")
+ implementation("org.ow2.asm:asm-commons:9.10.1")
+}
+
+gradlePlugin {
+ plugins {
+ create("pylonBuild") {
+ id = "pylon.build"
+ implementationClass = "pl.tomgirl.pylon.build.PylonBuildPlugin"
+ }
+ }
+}
diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts
new file mode 100644
index 0000000..0a4b371
--- /dev/null
+++ b/build-logic/settings.gradle.kts
@@ -0,0 +1 @@
+rootProject.name = "pylon-build-logic"
diff --git a/build-logic/src/baked-patches/java/pl/tomgirl/pylon/bakery/patch/AL10Patch.java b/build-logic/src/baked-patches/java/pl/tomgirl/pylon/bakery/patch/AL10Patch.java
new file mode 100644
index 0000000..6559d8a
--- /dev/null
+++ b/build-logic/src/baked-patches/java/pl/tomgirl/pylon/bakery/patch/AL10Patch.java
@@ -0,0 +1,16 @@
+package pl.tomgirl.pylon.bakery.patch;
+
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+@SuppressWarnings("unused")
+public abstract class AL10Patch {
+ @CompatStub("alListenerfv")
+ private static void alListener(int paramName, FloatBuffer values) {}
+
+ @CompatStub("alSourcefv")
+ private static void alSource(int source, int param, FloatBuffer values) {}
+
+ @CompatStub("alSourceStopv")
+ private static void alSourceStop(IntBuffer sources) {}
+}
diff --git a/src/main/java/pl/tomgirl/lenis/mixin/ALMixin.java b/build-logic/src/baked-patches/java/pl/tomgirl/pylon/bakery/patch/ALPatch.java
similarity index 79%
rename from src/main/java/pl/tomgirl/lenis/mixin/ALMixin.java
rename to build-logic/src/baked-patches/java/pl/tomgirl/pylon/bakery/patch/ALPatch.java
index a81e9c6..e79b39f 100644
--- a/src/main/java/pl/tomgirl/lenis/mixin/ALMixin.java
+++ b/build-logic/src/baked-patches/java/pl/tomgirl/pylon/bakery/patch/ALPatch.java
@@ -1,6 +1,4 @@
-package pl.tomgirl.lenis.mixin;
-
-import pl.tomgirl.lenis.plugin.CompatStub;
+package pl.tomgirl.pylon.bakery.patch;
import java.nio.IntBuffer;
@@ -10,29 +8,26 @@
import org.lwjgl.openal.ALC10;
import org.lwjgl.openal.ALCCapabilities;
import org.lwjgl.system.MemoryStack;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Unique;
import static org.lwjgl.openal.AL.createCapabilities;
@SuppressWarnings("unused")
-@Mixin(AL.class)
-public abstract class ALMixin {
- @Unique private static long _contextPtr;
- @Unique private static long _devicePtr;
- @Unique private static boolean _created;
+public abstract class ALPatch {
+ private static long _contextPtr;
+ private static long _devicePtr;
+ private static boolean _created;
- @Unique @CompatStub
+ @CompatStub
private static boolean isCreated() {
return _created;
}
- @Unique @CompatStub
+ @CompatStub
private static void create(String deviceArguments, int contextFrequency, int contextRefresh, boolean contextSynchronized) throws LWJGLException {
create(deviceArguments, contextFrequency, contextRefresh, contextSynchronized, true);
}
- @Unique @CompatStub
+ @CompatStub
private static void create(String deviceArguments, int contextFrequency, int contextRefresh, boolean contextSynchronized, boolean openDevice) throws LWJGLException {
if (_created) {
throw new IllegalStateException("Only one OpenAL context may be instantiated");
@@ -42,7 +37,6 @@ private static void create(String deviceArguments, int contextFrequency, int con
}
}
- @Unique
private static void init(String deviceArguments, int contextFrequency, int contextRefresh, boolean contextSynchronized, boolean openDevice) throws LWJGLException {
try {
if (!openDevice) {
@@ -50,7 +44,7 @@ private static void init(String deviceArguments, int contextFrequency, int conte
}
_devicePtr = ALC10.alcOpenDevice(deviceArguments);
- if (_devicePtr == -1L) {
+ if (_devicePtr == 0L) {
throw new LWJGLException("Could not open ALC device");
}
@@ -63,20 +57,21 @@ private static void init(String deviceArguments, int contextFrequency, int conte
}
}
- ALC10.alcMakeContextCurrent(_contextPtr);
+ if (_contextPtr == 0L || !ALC10.alcMakeContextCurrent(_contextPtr)) {
+ throw new LWJGLException("Could not create or make current an OpenAL context");
+ }
createCapabilities(deviceCaps);
- } catch (LWJGLException ex) {
+ } catch (LWJGLException | RuntimeException | Error ex) {
exit();
throw ex;
}
}
- @Unique @CompatStub
+ @CompatStub
private static void create() throws LWJGLException {
create(null, 44100, 60, false);
}
- @Unique
private static IntBuffer createAttributeList(int contextFrequency, int contextRefresh, int contextSynchronized, MemoryStack stack) {
IntBuffer buffer = stack.callocInt(7);
buffer.put(0, 4103);
@@ -89,19 +84,20 @@ private static IntBuffer createAttributeList(int contextFrequency, int contextRe
return buffer;
}
- @Unique @CompatStub
+ @CompatStub
private static void exit() {
- if (_contextPtr != -1L) {
+ if (_contextPtr != 0L) {
ALC10.alcMakeContextCurrent(0L);
ALC10.alcDestroyContext(_contextPtr);
- _contextPtr = -1L;
+ _contextPtr = 0L;
}
- if (_devicePtr != -1L) {
+ if (_devicePtr != 0L) {
ALC10.alcCloseDevice(_devicePtr);
- _devicePtr = -1L;
+ _devicePtr = 0L;
}
+ AL.setCurrentProcess(null);
_created = false;
}
}
diff --git a/build-logic/src/baked-patches/java/pl/tomgirl/pylon/bakery/patch/ARBShaderObjectsPatch.java b/build-logic/src/baked-patches/java/pl/tomgirl/pylon/bakery/patch/ARBShaderObjectsPatch.java
new file mode 100644
index 0000000..af67e7c
--- /dev/null
+++ b/build-logic/src/baked-patches/java/pl/tomgirl/pylon/bakery/patch/ARBShaderObjectsPatch.java
@@ -0,0 +1,16 @@
+package pl.tomgirl.pylon.bakery.patch;
+
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+
+@SuppressWarnings("unused")
+public abstract class ARBShaderObjectsPatch {
+ @CompatStub("glGetObjectParameterivARB")
+ private static void glGetObjectParameterARB(int obj, int pname, IntBuffer params) {}
+
+ @CompatStub("glUniformMatrix4fvARB")
+ private static void glUniformMatrix4ARB(int location, boolean transpose, FloatBuffer value) {}
+
+ @CompatStub("glUniform1ivARB")
+ private static void glUniform1ARB(int location, IntBuffer value) {}
+}
diff --git a/src/main/java/pl/tomgirl/lenis/plugin/CompatStub.java b/build-logic/src/baked-patches/java/pl/tomgirl/pylon/bakery/patch/CompatStub.java
similarity index 88%
rename from src/main/java/pl/tomgirl/lenis/plugin/CompatStub.java
rename to build-logic/src/baked-patches/java/pl/tomgirl/pylon/bakery/patch/CompatStub.java
index 13164c6..5eaf7d3 100644
--- a/src/main/java/pl/tomgirl/lenis/plugin/CompatStub.java
+++ b/build-logic/src/baked-patches/java/pl/tomgirl/pylon/bakery/patch/CompatStub.java
@@ -1,4 +1,4 @@
-package pl.tomgirl.lenis.plugin;
+package pl.tomgirl.pylon.bakery.patch;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
diff --git a/build-logic/src/baked-patches/java/pl/tomgirl/pylon/bakery/patch/GL11Patch.java b/build-logic/src/baked-patches/java/pl/tomgirl/pylon/bakery/patch/GL11Patch.java
new file mode 100644
index 0000000..cba8472
--- /dev/null
+++ b/build-logic/src/baked-patches/java/pl/tomgirl/pylon/bakery/patch/GL11Patch.java
@@ -0,0 +1,53 @@
+package pl.tomgirl.pylon.bakery.patch;
+
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+
+import org.lwjgl.opengl.GL11;
+import org.lwjgl.system.MemoryUtil;
+
+@SuppressWarnings("unused")
+public abstract class GL11Patch {
+ @CompatStub
+ private static void glDrawElements(int mode, int count, int type, ByteBuffer indices) {
+ GL11.nglDrawElements(mode, count, type, MemoryUtil.memAddress(indices));
+ }
+
+ @CompatStub
+ private static void glTexCoordPointer(int i, int stride, FloatBuffer pointer) {
+ GL11.glTexCoordPointer(i, 0x1406, stride, pointer);
+ }
+
+ @CompatStub
+ private static void glTexCoordPointer(int i, int stride, ShortBuffer pointer) {
+ GL11.glTexCoordPointer(i, 0x1402, stride, pointer);
+ }
+
+ @CompatStub
+ private static void glColorPointer(int i, boolean bl, int i2, ByteBuffer pointer) {
+ GL11.glColorPointer(i, 0x1401, i2, pointer);
+ }
+
+ @CompatStub
+ private static void glVertexPointer(int i, int i2, FloatBuffer pointer) {
+ GL11.glVertexPointer(i, 0x1406, i2, pointer);
+ }
+
+ @CompatStub
+ private static void glNormalPointer(int stride, ByteBuffer pointer) {
+ GL11.glNormalPointer(0x1400, stride, pointer);
+ }
+
+ @CompatStub("glGetBooleanv") private static void glGetBoolean(int pname, ByteBuffer params) {}
+ @CompatStub("glGetFloatv") private static void glGetFloat(int pname, FloatBuffer params) {}
+ @CompatStub("glGetIntegerv") private static void glGetInteger(int pname, IntBuffer params) {}
+ @CompatStub("glFogfv") private static void glFog(int pname, FloatBuffer params) {}
+ @CompatStub("glLightfv") private static void glLight(int light, int pname, FloatBuffer params) {}
+ @CompatStub("glLightModelfv") private static void glLightModel(int pname, FloatBuffer params) {}
+ @CompatStub("glMultMatrixf") private static void glMultMatrix(FloatBuffer matrix) {}
+ @CompatStub("glTexEnvfv") private static void glTexEnv(int target, int pname, FloatBuffer params) {}
+ @CompatStub("glTexGenfv") private static void glTexGen(int coord, int pname, FloatBuffer params) {}
+ @CompatStub("glLoadMatrixf") private static void glLoadMatrix(FloatBuffer matrix) {}
+}
diff --git a/build-logic/src/baked-patches/java/pl/tomgirl/pylon/bakery/patch/GL20Patch.java b/build-logic/src/baked-patches/java/pl/tomgirl/pylon/bakery/patch/GL20Patch.java
new file mode 100644
index 0000000..d337aee
--- /dev/null
+++ b/build-logic/src/baked-patches/java/pl/tomgirl/pylon/bakery/patch/GL20Patch.java
@@ -0,0 +1,29 @@
+package pl.tomgirl.pylon.bakery.patch;
+
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import org.lwjgl.opengl.GL20;
+
+@SuppressWarnings("unused")
+public abstract class GL20Patch {
+ @CompatStub
+ private static void glShaderSource(int shader, java.nio.ByteBuffer string) {
+ byte[] data = new byte[string.limit()];
+ string.position(0);
+ string.get(data);
+ string.position(0);
+ GL20.glShaderSource(shader, new String(data));
+ }
+
+ @CompatStub("glUniformMatrix4fv") private static void glUniformMatrix4(int location, boolean transpose, FloatBuffer value) {}
+ @CompatStub("glUniform2fv") private static void glUniform2(int location, FloatBuffer value) {}
+ @CompatStub("glUniform1iv") private static void glUniform1(int location, IntBuffer value) {}
+ @CompatStub("glUniform1fv") private static void glUniform1(int location, FloatBuffer value) {}
+ @CompatStub("glUniform2iv") private static void glUniform2i(int location, IntBuffer value) {}
+ @CompatStub("glUniform3iv") private static void glUniform3(int location, IntBuffer value) {}
+ @CompatStub("glUniform3fv") private static void glUniform3(int location, FloatBuffer value) {}
+ @CompatStub("glUniform4iv") private static void glUniform4(int location, IntBuffer value) {}
+ @CompatStub("glUniform4fv") private static void glUniform4(int location, FloatBuffer value) {}
+ @CompatStub("glUniformMatrix2fv") private static void glUniformMatrix2(int location, boolean transpose, FloatBuffer value) {}
+ @CompatStub("glUniformMatrix3fv") private static void glUniformMatrix3(int location, boolean transpose, FloatBuffer value) {}
+}
diff --git a/build-logic/src/main/kotlin/pl/tomgirl/pylon/build/BakeLwjglTask.kt b/build-logic/src/main/kotlin/pl/tomgirl/pylon/build/BakeLwjglTask.kt
new file mode 100644
index 0000000..25eb1a2
--- /dev/null
+++ b/build-logic/src/main/kotlin/pl/tomgirl/pylon/build/BakeLwjglTask.kt
@@ -0,0 +1,164 @@
+package pl.tomgirl.pylon.build
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.tasks.*
+import org.objectweb.asm.*
+import org.objectweb.asm.commons.ClassRemapper
+import org.objectweb.asm.commons.SimpleRemapper
+import org.objectweb.asm.tree.*
+import java.nio.file.Files
+import java.util.jar.JarEntry
+import java.util.jar.JarFile
+import java.util.jar.JarOutputStream
+
+abstract class BakeLwjglTask : DefaultTask() {
+ @get:InputFile @get:Classpath
+ abstract val inputJar: RegularFileProperty
+
+ @get:Classpath
+ abstract val patchClasses: ConfigurableFileCollection
+
+ @get:OutputFile
+ abstract val outputJar: RegularFileProperty
+
+ @TaskAction
+ fun bake() {
+ val output = outputJar.get().asFile.toPath()
+ val patched = mutableSetOf()
+ Files.createDirectories(output.parent)
+ JarFile(inputJar.get().asFile).use { input ->
+ JarOutputStream(Files.newOutputStream(output)).use { jar ->
+ input.stream().sorted(compareBy(JarEntry::getName)).forEach { entry ->
+ var bytes = input.getInputStream(entry).use { it.readAllBytes() }
+ val name = entry.name.removeSuffix(".class")
+ if (name in patches || name == CAPABILITIES) {
+ val target = ClassNode().also { ClassReader(bytes).accept(it, 0) }
+ patches[name]?.let { merge(target, it) } ?: extendCapabilities(target)
+ bytes = ClassWriter(ClassWriter.COMPUTE_MAXS).also(target::accept).toByteArray()
+ patched += name
+ }
+ jar.putNextEntry(JarEntry(entry.name).apply { time = 0 })
+ jar.write(bytes)
+ jar.closeEntry()
+ }
+ }
+ }
+ val missing = patches.keys + CAPABILITIES - patched
+ if (missing.isNotEmpty()) {
+ Files.delete(output)
+ error("Missing LWJGL patch targets: $missing")
+ }
+ logger.lifecycle("Baked LWJGL compatibility into $output")
+ }
+
+ private fun merge(target: ClassNode, template: String) {
+ val sourceName = "$TEMPLATES$template"
+ val templateFile = patchClasses.files.asSequence()
+ .map { it.toPath().resolve("$sourceName.class") }
+ .firstOrNull(Files::isRegularFile)
+ ?: error("Missing template: $template")
+ val source = ClassNode()
+ Files.newInputStream(templateFile).use {
+ ClassReader(it).accept(
+ ClassRemapper(source, SimpleRemapper(Opcodes.ASM9, mapOf(sourceName to target.name))), 0,
+ )
+ }
+ source.fields.forEach { field ->
+ check(target.fields.none { it.name == field.name }) { "Field collision: ${target.name}.${field.name}" }
+ target.fields.add(field)
+ }
+ source.methods.filterNot { it.name == "" }.forEach { method ->
+ check(method.name != "") { "Template static initializers are not supported: $template" }
+ check(target.find(method.name, method.desc) == null) {
+ "Method collision: ${target.name}.${method.name}${method.desc}"
+ }
+ method.invisibleAnnotations?.filter { it.desc == STUB }?.forEach { annotation ->
+ check(method.access and Opcodes.ACC_STATIC != 0) { "CompatStub must be static: ${method.name}" }
+ method.access = method.access and (Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED).inv() or Opcodes.ACC_PUBLIC
+ annotation.values?.chunked(2)?.firstOrNull { it[0] == "value" }
+ ?.let { delegate(target, method, it[1] as String) }
+ }
+ method.invisibleAnnotations?.removeIf { it.desc == STUB }
+ target.methods.add(method)
+ }
+ // forge workaround
+ if (target.name == "org/lwjgl/opengl/GL11") {
+ val getString = target.find("glGetString", "(I)Ljava/lang/String;") ?: error("Missing GL11.glGetString")
+ getString.instructions.insert(InsnList().apply {
+ add(MethodInsnNode(
+ Opcodes.INVOKESTATIC, "org/lwjgl/opengl/GL", "getCapabilities",
+ "()Lorg/lwjgl/opengl/GLCapabilities;", false,
+ ))
+ add(InsnNode(Opcodes.POP))
+ })
+ }
+ if (target.name == "org/lwjgl/openal/AL") {
+ val destroy = target.find("destroy", "()V") ?: error("Missing AL.destroy")
+ destroy.access = destroy.access and (Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED).inv() or Opcodes.ACC_PUBLIC
+ destroy.instructions.toArray().filter { it.opcode == Opcodes.RETURN }.forEach {
+ destroy.instructions.insertBefore(
+ it, MethodInsnNode(Opcodes.INVOKESTATIC, target.name, "exit", "()V", false),
+ )
+ }
+ }
+ }
+
+ private fun delegate(owner: ClassNode, stub: MethodNode, name: String) {
+ if (name.isEmpty()) return
+ val target = owner.find(name, stub.desc)
+ check(target != null && target.access and Opcodes.ACC_STATIC != 0) {
+ "Missing static delegate: ${owner.name}.$name${stub.desc}"
+ }
+ stub.instructions.clear()
+ stub.tryCatchBlocks.clear()
+ stub.localVariables?.clear()
+ var local = 0
+ Type.getArgumentTypes(stub.desc).forEach {
+ stub.instructions.add(VarInsnNode(it.getOpcode(Opcodes.ILOAD), local))
+ local += it.size
+ }
+ stub.instructions.add(MethodInsnNode(Opcodes.INVOKESTATIC, owner.name, name, stub.desc, false))
+ stub.instructions.add(InsnNode(Type.getReturnType(stub.desc).getOpcode(Opcodes.IRETURN)))
+ }
+
+ private fun extendCapabilities(target: ClassNode) {
+ val constructor = target.find(
+ "", "(Lorg/lwjgl/system/FunctionProvider;Ljava/util/Set;ZLjava/util/function/IntFunction;)V",
+ ) ?: error("Unexpected GLCapabilities constructor")
+ capabilityExtensions.forEach { extension ->
+ check(target.fields.none { it.name == extension }) { "Capability field collision: $extension" }
+ target.fields.add(FieldNode(Opcodes.ACC_PUBLIC or Opcodes.ACC_FINAL, extension, "Z", null, null))
+ constructor.instructions.toArray().filter { it.opcode == Opcodes.RETURN }.forEach {
+ constructor.instructions.insertBefore(it, InsnList().apply {
+ add(VarInsnNode(Opcodes.ALOAD, 0))
+ add(VarInsnNode(Opcodes.ALOAD, 2))
+ add(LdcInsnNode(extension))
+ add(MethodInsnNode(Opcodes.INVOKEINTERFACE, "java/util/Set", "contains", "(Ljava/lang/Object;)Z", true))
+ add(FieldInsnNode(Opcodes.PUTFIELD, target.name, extension, "Z"))
+ })
+ }
+ }
+ }
+
+ private fun ClassNode.find(name: String, descriptor: String) =
+ methods.find { it.name == name && it.desc == descriptor }
+
+ private companion object {
+ const val TEMPLATES = "pl/tomgirl/pylon/bakery/patch/"
+ const val STUB = "L${TEMPLATES}CompatStub;"
+ const val CAPABILITIES = "org/lwjgl/opengl/GLCapabilities"
+ val patches = mapOf(
+ "org/lwjgl/openal/AL" to "ALPatch",
+ "org/lwjgl/openal/AL10" to "AL10Patch",
+ "org/lwjgl/opengl/GL11" to "GL11Patch",
+ "org/lwjgl/opengl/GL20" to "GL20Patch",
+ "org/lwjgl/opengl/ARBShaderObjects" to "ARBShaderObjectsPatch",
+ )
+ val capabilityExtensions = listOf(
+ "GL_EXT_multi_draw_arrays", "GL_EXT_paletted_texture", "GL_EXT_rescale_normal",
+ "GL_EXT_texture_3d", "GL_EXT_texture_lod_bias", "GL_EXT_vertex_shader", "GL_EXT_vertex_weighting",
+ )
+ }
+}
diff --git a/build-logic/src/main/kotlin/pl/tomgirl/pylon/build/PylonBuildPlugin.kt b/build-logic/src/main/kotlin/pl/tomgirl/pylon/build/PylonBuildPlugin.kt
new file mode 100644
index 0000000..3a72992
--- /dev/null
+++ b/build-logic/src/main/kotlin/pl/tomgirl/pylon/build/PylonBuildPlugin.kt
@@ -0,0 +1,176 @@
+package pl.tomgirl.pylon.build
+
+import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.artifacts.VersionCatalogsExtension
+import org.gradle.api.plugins.BasePluginExtension
+import org.gradle.api.plugins.JavaPluginExtension
+import org.gradle.api.publish.PublishingExtension
+import org.gradle.api.publish.maven.MavenPublication
+import org.gradle.api.tasks.SourceSetContainer
+import org.gradle.api.tasks.bundling.Jar
+import org.gradle.api.tasks.compile.JavaCompile
+import org.gradle.kotlin.dsl.*
+import org.gradle.language.jvm.tasks.ProcessResources
+
+class PylonBuildPlugin : Plugin {
+ override fun apply(project: Project) = with(project) {
+ pluginManager.apply("java-library")
+ pluginManager.apply("maven-publish")
+ pluginManager.apply("com.gradleup.shadow")
+
+ repositories {
+ mavenCentral()
+ maven("https://maven.fabricmc.net/")
+ }
+
+ val libs = extensions.getByType().named("libs")
+ val lwjglVersion = libs.findVersion("lwjgl").get().requiredVersion
+ val sourceSets = extensions.getByType()
+ val main = sourceSets.named("main")
+ val fabric = sourceSets.create("fabric") {
+ compileClasspath += main.get().output
+ runtimeClasspath += output + compileClasspath
+ }
+ configurations.named(fabric.implementationConfigurationName) {
+ extendsFrom(configurations.getByName("implementation"))
+ }
+ configurations.named(fabric.compileOnlyConfigurationName) {
+ extendsFrom(configurations.getByName("compileOnly"))
+ }
+ configurations.named(fabric.runtimeOnlyConfigurationName) {
+ extendsFrom(configurations.getByName("runtimeOnly"))
+ }
+ val launchwrapper = sourceSets.create("launchwrapper")
+ val forge = sourceSets.create("forge") {
+ compileClasspath += main.get().output + launchwrapper.output +
+ configurations.getByName("compileClasspath")
+ runtimeClasspath += output + compileClasspath
+ }
+ val bakedPatches = sourceSets.create("bakedPatches") {
+ java.srcDir(rootProject.layout.projectDirectory.dir("build-logic/src/baked-patches/java"))
+ compileClasspath += main.get().output + configurations.getByName("compileClasspath")
+ runtimeClasspath += output + compileClasspath
+ }
+
+ extensions.configure {
+ withSourcesJar()
+ }
+
+ tasks.withType().configureEach {
+ options.release.set(8)
+ options.compilerArgs.add("-Xlint:-options")
+ }
+
+ group = providers.gradleProperty("maven_group").get()
+ val base = extensions.getByType()
+ base.archivesName.set(providers.gradleProperty("base_name"))
+
+ dependencies {
+ add("implementation", libs.findLibrary("lwjgl").get())
+ add("implementation", libs.findLibrary("lwjgl-openal").get())
+ add("implementation", libs.findLibrary("lwjgl-opengl").get())
+ add("implementation", libs.findLibrary("lwjgl-sdl").get())
+ add("implementation", libs.findLibrary("asm").get())
+ add("compileOnly", "org.jetbrains:annotations:26.0.2")
+ add(fabric.compileOnlyConfigurationName, libs.findLibrary("fabric-loader").get())
+ add(fabric.compileOnlyConfigurationName, libs.findLibrary("fabric-mixin").get())
+
+ for (module in listOf("lwjgl", "lwjgl-openal", "lwjgl-opengl", "lwjgl-sdl")) {
+ for (platform in listOf("windows", "linux", "macos", "macos-arm64")) {
+ add("runtimeOnly", "org.lwjgl:$module:$lwjglVersion:natives-$platform")
+ }
+ }
+ }
+
+ tasks.named("processResources") {
+ inputs.property("version", version)
+ filesMatching(listOf("fabric.mod.json", "mcmod.info")) {
+ expand("version" to project.version)
+ }
+ }
+
+ tasks.named("jar") {
+ dependsOn(fabric.classesTaskName, forge.classesTaskName)
+ from(fabric.output, forge.output)
+ archiveClassifier.set("slim")
+ destinationDirectory.set(layout.buildDirectory.dir("intermediates"))
+ manifest.attributes(
+ "Premain-Class" to "pl.tomgirl.pylon.Agent",
+ "Implementation-Version" to project.version.toString(),
+ "TweakClass" to "pl.tomgirl.pylon.forge.PylonTweaker",
+ )
+ }
+
+ val shadowJar = tasks.named("shadowJar") {
+ archiveClassifier.set("relocated")
+ destinationDirectory.set(layout.buildDirectory.dir("intermediates"))
+ relocate("org.objectweb.asm", "pl.tomgirl.pylon.internal.asm")
+ exclude("module-info.class", "META-INF/versions/**/module-info.class")
+ exclude("META-INF/*.SF", "META-INF/*.RSA", "META-INF/*.DSA")
+ mergeServiceFiles()
+ }
+
+ val unpatchedJar = tasks.register("unpatchedJar") {
+ dependsOn(shadowJar, fabric.classesTaskName, forge.classesTaskName)
+ from(shadowJar.flatMap { it.archiveFile }.map { zipTree(it.asFile) }) {
+ exclude("META-INF/MANIFEST.MF")
+ }
+ from(fabric.output, forge.output)
+ archiveClassifier.set("unpatched")
+ destinationDirectory.set(layout.buildDirectory.dir("intermediates"))
+ manifest.attributes(
+ "Premain-Class" to "pl.tomgirl.pylon.Agent",
+ "Implementation-Version" to project.version.toString(),
+ "TweakClass" to "pl.tomgirl.pylon.forge.PylonTweaker",
+ "Multi-Release" to "true",
+ )
+ }
+
+ val distributionFile = layout.buildDirectory.file(
+ providers.zip(base.archivesName, provider { version.toString() }) { name, release ->
+ "libs/$name-$release.jar"
+ },
+ )
+ val bakeLwjgl = tasks.register("bakeLwjgl") {
+ group = "build"
+ description = "Bakes the legacy LWJGL compatibility patches into the distribution jar."
+ dependsOn(unpatchedJar, bakedPatches.classesTaskName)
+ inputJar.set(unpatchedJar.flatMap { it.archiveFile })
+ patchClasses.from(bakedPatches.output.classesDirs)
+ outputJar.set(distributionFile)
+ }
+
+ val distribution = configurations.create("distribution") {
+ isCanBeConsumed = true
+ isCanBeResolved = false
+ }
+ artifacts.add(distribution.name, distributionFile) { builtBy(bakeLwjgl) }
+ tasks.named("assemble") { dependsOn(bakeLwjgl) }
+ tasks.named("sourcesJar") {
+ from(fabric.allSource, forge.allSource, launchwrapper.allSource)
+ }
+
+ extensions.configure {
+ publications {
+ create("mavenJava") {
+ artifactId = providers.gradleProperty("base_name").get()
+ artifact(distributionFile) { builtBy(bakeLwjgl) }
+ artifact(tasks.named("sourcesJar"))
+ }
+ }
+
+ providers.environmentVariable("MAVEN_URL").orNull?.let { repositoryUrl ->
+ repositories.maven {
+ name = "release"
+ url = uri(repositoryUrl)
+ credentials {
+ username = providers.environmentVariable("MAVEN_USER").orNull
+ password = providers.environmentVariable("MAVEN_PASS").orNull
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index 1602435..1681231 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,83 +1,3 @@
plugins {
- id("java")
- id("maven-publish")
- alias(libs.plugins.loom)
- alias(libs.plugins.ploceus)
-}
-
-loom {}
-ploceus.setIntermediaryGeneration(2)
-
-java {
- sourceCompatibility = JavaVersion.VERSION_21
- targetCompatibility = JavaVersion.VERSION_21
- withSourcesJar()
-}
-
-group = providers.gradleProperty("maven_group").get()
-
-base {
- archivesName = providers.gradleProperty("base_name").get()
-}
-
-dependencies {
- minecraft(libs.minecraft)
- modImplementation(libs.loader)
- mappings(ploceus.featherMappings(libs.versions.feather.build.get()))
-
- bundled(libs.lwjgl.asProvider())
- bundled(libs.lwjgl.openal)
- bundled(libs.lwjgl.opengl)
- bundled(libs.lwjgl.sdl)
-
- lwjglDesktopNatives("lwjgl")
- lwjglDesktopNatives("lwjgl-openal")
- lwjglDesktopNatives("lwjgl-opengl")
- lwjglDesktopNatives("lwjgl-sdl")
-}
-
-tasks.processResources {
- filesMatching("fabric.mod.json") {
- expand(Pair("version", project.version))
- }
-}
-
-publishing {
- publications {
- create("mavenJava") {
- artifactId = providers.gradleProperty("base_name").get()
- from(components["java"])
- }
- }
-
- repositories {
- providers.environmentVariable("MAVEN_URL").orNull?.let { repositoryUrl ->
- maven {
- name = "release"
- url = uri(repositoryUrl)
- credentials {
- username = providers.environmentVariable("MAVEN_USER").orNull
- password = providers.environmentVariable("MAVEN_PASS").orNull
- }
- }
- }
- }
-}
-
-fun DependencyHandlerScope.bundled(dependency: Provider) {
- add("api", dependency)
- add("include", dependency)
-}
-
-fun DependencyHandlerScope.lwjglNative(module: String, classifier: String) {
- val notation = "org.lwjgl:$module:${libs.versions.lwjgl.get()}:$classifier"
- add("runtimeOnly", notation)
- add("include", notation)
-}
-
-fun DependencyHandlerScope.lwjglDesktopNatives(module: String) {
- lwjglNative(module, "natives-windows")
- lwjglNative(module, "natives-linux")
- lwjglNative(module, "natives-macos")
- lwjglNative(module, "natives-macos-arm64")
+ id("pylon.build")
}
diff --git a/gradle.properties b/gradle.properties
index 21acf40..00a7f8c 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,5 +2,5 @@ org.gradle.jvmargs = -Xmx512M
org.gradle.parallel = true
maven_group = pl.tomgirl
-base_name = lenis
-version = 0.1.4
+base_name = pylon
+version = 0.1.6
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 052c3fc..049e06a 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,19 +1,19 @@
[versions]
-minecraft = "1.8.9"
-feather_build = "1"
-loader = "0.19.3"
+fabric-loader = "0.19.3"
+fabric-mixin = "0.17.3+mixin.0.8.7"
loom = "1.17.19"
-ploceus = "1.17.7"
lwjgl = "3.4.3"
+asm = "9.10.1"
[libraries]
-minecraft = { module = "com.mojang:minecraft", version.ref = "minecraft" }
-loader = { module = "net.fabricmc:fabric-loader", version.ref = "loader" }
+fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "fabric-loader" }
+fabric-mixin = { module = "net.fabricmc:sponge-mixin", version.ref = "fabric-mixin" }
lwjgl = { module = "org.lwjgl:lwjgl", version.ref = "lwjgl" }
lwjgl-opengl = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl" }
lwjgl-openal = { module = "org.lwjgl:lwjgl-openal", version.ref = "lwjgl" }
lwjgl-sdl = { module = "org.lwjgl:lwjgl-sdl", version.ref = "lwjgl" }
+asm = { module = "org.ow2.asm:asm", version.ref = "asm" }
+asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" }
[plugins]
-ploceus = { id = "ploceus", version.ref = "ploceus" }
-loom = { id = "net.fabricmc.fabric-loom-remap", version.ref = "loom" }
+loom = { id = "net.fabricmc.fabric-loom", version.ref = "loom" }
diff --git a/runs/fabric/build.gradle.kts b/runs/fabric/build.gradle.kts
new file mode 100644
index 0000000..9a6af14
--- /dev/null
+++ b/runs/fabric/build.gradle.kts
@@ -0,0 +1,31 @@
+plugins {
+ alias(libs.plugins.loom)
+}
+
+loom {
+ clientOnlyMinecraftJar()
+ runs {
+ remove(getByName("server"))
+ named("client") {
+ displayName = "Fabric 1.8.9"
+ generateRunConfig = true
+ preferGradleTask = true
+ programArguments.addAll("--username", "Player", "--accessToken", "0")
+ jvmArguments.add("-Djava.util.logging.config.file=${rootProject.file("runs/logging.properties")}")
+ jvmArguments.add("--enable-native-access=ALL-UNNAMED")
+ }
+ }
+}
+
+dependencies {
+ minecraft("com.mojang:minecraft:1.8.9")
+ implementation(libs.fabric.loader)
+ runtimeOnly("org.apache.logging.log4j:log4j-api:2.26.0")
+ runtimeOnly("org.apache.logging.log4j:log4j-core:2.26.0")
+ runtimeOnly(project(path = ":", configuration = "distribution"))
+}
+
+configurations.configureEach {
+ exclude(group = "org.lwjgl.lwjgl")
+ exclude(group = "org.lwjgl")
+}
diff --git a/runs/logging.properties b/runs/logging.properties
new file mode 100644
index 0000000..d772d33
--- /dev/null
+++ b/runs/logging.properties
@@ -0,0 +1,6 @@
+handlers=java.util.logging.ConsoleHandler
+.level=INFO
+
+java.util.logging.ConsoleHandler.level=ALL
+Pylon.level=FINE
+LWJGL.level=FINE
diff --git a/runs/vanilla/build.gradle.kts b/runs/vanilla/build.gradle.kts
new file mode 100644
index 0000000..77f97a5
--- /dev/null
+++ b/runs/vanilla/build.gradle.kts
@@ -0,0 +1,44 @@
+plugins {
+ alias(libs.plugins.loom)
+}
+
+val pylon = configurations.create("pylon")
+
+dependencies {
+ minecraft("com.mojang:minecraft:1.6.4")
+ pylon(project(path = ":", configuration = "distribution"))
+ runtimeOnly(project(path = ":", configuration = "distribution"))
+}
+
+configurations.configureEach {
+ exclude(group = "org.lwjgl.lwjgl")
+ exclude(group = "org.lwjgl")
+}
+
+loom {
+ clientOnlyMinecraftJar()
+ runs {
+ remove(getByName("server"))
+ remove(getByName("client"))
+ create("base") {
+ client()
+ mainClass = "net.minecraft.client.main.Main"
+ displayName = "Vanilla 1.6.4 (base)"
+ runDirectory = layout.projectDirectory.dir("run/base")
+ }
+ create("agent") {
+ client()
+ mainClass = "net.minecraft.client.main.Main"
+ displayName = "Vanilla 1.6.4 (agent)"
+ runDirectory = layout.projectDirectory.dir("run/agent")
+ jvmArguments.add(provider { "-javaagent:${pylon.singleFile.absolutePath}" })
+ }
+ configureEach {
+ generateRunConfig = true
+ preferGradleTask = true
+ programArguments.addAll("--username", "Player", "--session", "0", "--version", "1.6.4")
+ jvmArguments.add("-Djava.util.logging.config.file=${rootProject.file("runs/logging.properties")}")
+ jvmArguments.add("--enable-native-access=ALL-UNNAMED")
+ }
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 15bd70d..0ea1d53 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,11 +1,12 @@
pluginManagement {
+ includeBuild("build-logic")
repositories {
maven("https://maven.fabricmc.net/")
- maven("https://maven.ornithemc.net/releases")
- maven("https://maven.ornithemc.net/snapshots")
mavenLocal()
gradlePluginPortal()
}
}
-rootProject.name = "lenis"
+rootProject.name = "pylon"
+
+include(":runs:fabric", ":runs:vanilla")
diff --git a/src/fabric/java/pl/tomgirl/pylon/fabric/MixinPlugin.java b/src/fabric/java/pl/tomgirl/pylon/fabric/MixinPlugin.java
new file mode 100644
index 0000000..9c119e2
--- /dev/null
+++ b/src/fabric/java/pl/tomgirl/pylon/fabric/MixinPlugin.java
@@ -0,0 +1,58 @@
+package pl.tomgirl.pylon.fabric;
+
+import org.objectweb.asm.tree.ClassNode;
+import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
+import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
+import org.spongepowered.asm.transformers.TreeTransformer;
+
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Set;
+
+@SuppressWarnings("unused")
+public class MixinPlugin implements IMixinConfigPlugin {
+ static {
+ try {
+ ClassLoader knotClassLoader = MixinPlugin.class.getClassLoader(); // net.fabricmc.loader.impl.launch.knot.KnotClassLoader
+ Field delegateField = knotClassLoader.getClass().getDeclaredField("delegate");
+ delegateField.setAccessible(true);
+ Object delegate = delegateField.get(knotClassLoader); // net.fabricmc.loader.impl.launch.knot.KnotClassDelegate
+ Field mixinTransformerField = delegate.getClass().getDeclaredField("mixinTransformer");
+ mixinTransformerField.setAccessible(true);
+ mixinTransformerField.set(delegate, new MixinTransformer((TreeTransformer) mixinTransformerField.get(delegate)));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean shouldApplyMixin(String s, String s1) {
+ return true;
+ }
+
+ @Override
+ public String getRefMapperConfig() {
+ return null;
+ }
+
+ @Override
+ public List getMixins() {
+ return null;
+ }
+
+ @Override
+ public void onLoad(String s) {
+ }
+
+ @Override
+ public void acceptTargets(Set set, Set set1) {
+ }
+
+ @Override
+ public void preApply(String s, ClassNode classNode, String s1, IMixinInfo iMixinInfo) {
+ }
+
+ @Override
+ public void postApply(String s, ClassNode classNode, String s1, IMixinInfo iMixinInfo) {
+ }
+}
\ No newline at end of file
diff --git a/src/fabric/java/pl/tomgirl/pylon/fabric/MixinTransformer.java b/src/fabric/java/pl/tomgirl/pylon/fabric/MixinTransformer.java
new file mode 100644
index 0000000..18b86b3
--- /dev/null
+++ b/src/fabric/java/pl/tomgirl/pylon/fabric/MixinTransformer.java
@@ -0,0 +1,29 @@
+package pl.tomgirl.pylon.fabric;
+
+import org.spongepowered.asm.mixin.transformer.IMixinTransformer;
+import org.spongepowered.asm.transformers.TreeTransformer;
+import pl.tomgirl.pylon.game.GameTransformer;
+
+public class MixinTransformer extends MixinTransformerDelegate {
+ MixinTransformer(T delegate) throws Exception {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public byte[] transformClassBytes(String name, String transformedName, byte[] bytecode) {
+ if (bytecode == null) {
+ return super.transformClassBytes(name, transformedName, null);
+ }
+
+ if (name.startsWith("pl.tomgirl.pylon")) {
+ return super.transformClassBytes(name, transformedName, bytecode);
+ }
+
+ byte[] transformed = GameTransformer.transform(bytecode);
+ if (transformed != null) {
+ bytecode = transformed;
+ }
+
+ return super.transformClassBytes(name, transformedName, bytecode);
+ }
+}
\ No newline at end of file
diff --git a/src/fabric/java/pl/tomgirl/pylon/fabric/MixinTransformerDelegate.java b/src/fabric/java/pl/tomgirl/pylon/fabric/MixinTransformerDelegate.java
new file mode 100644
index 0000000..6855712
--- /dev/null
+++ b/src/fabric/java/pl/tomgirl/pylon/fabric/MixinTransformerDelegate.java
@@ -0,0 +1,73 @@
+package pl.tomgirl.pylon.fabric;
+
+import org.objectweb.asm.tree.ClassNode;
+import org.spongepowered.asm.mixin.MixinEnvironment;
+import org.spongepowered.asm.mixin.transformer.IMixinTransformer;
+import org.spongepowered.asm.mixin.transformer.ext.IExtensionRegistry;
+import org.spongepowered.asm.transformers.TreeTransformer;
+
+import java.util.List;
+
+public class MixinTransformerDelegate extends TreeTransformer implements IMixinTransformer {
+ protected T delegate;
+
+ @Override
+ public void audit(MixinEnvironment mixinEnvironment) {
+ delegate.audit(mixinEnvironment);
+ }
+
+ @Override
+ public List reload(String s, ClassNode classNode) {
+ return delegate.reload(s, classNode);
+ }
+
+ @Override
+ public boolean computeFramesForClass(MixinEnvironment mixinEnvironment, String s, ClassNode classNode) {
+ return delegate.computeFramesForClass(mixinEnvironment, s, classNode);
+ }
+
+ @Override
+ public byte[] transformClass(MixinEnvironment mixinEnvironment, String s, byte[] bytes) {
+ return delegate.transformClass(mixinEnvironment, s, bytes);
+ }
+
+ @Override
+ public boolean transformClass(MixinEnvironment mixinEnvironment, String s, ClassNode classNode) {
+ return delegate.transformClass(mixinEnvironment, s, classNode);
+ }
+
+ @Override
+ public boolean couldTransformClass(MixinEnvironment mixinEnvironment, String s) {
+ return delegate.couldTransformClass(mixinEnvironment, s);
+ }
+
+ @Override
+ public byte[] generateClass(MixinEnvironment mixinEnvironment, String s) {
+ return delegate.generateClass(mixinEnvironment, s);
+ }
+
+ @Override
+ public boolean generateClass(MixinEnvironment mixinEnvironment, String s, ClassNode classNode) {
+ return delegate.generateClass(mixinEnvironment, s, classNode);
+ }
+
+ @Override
+ public IExtensionRegistry getExtensions() {
+ return delegate.getExtensions();
+ }
+
+ @Override
+ public byte[] transformClassBytes(String s, String s1, byte[] bytes) {
+ return delegate.transformClassBytes(s, s1, bytes);
+ }
+
+ @Override
+ public String getName() {
+ return delegate.getName();
+ }
+
+ @Override
+ public boolean isDelegationExcluded() {
+ return delegate.isDelegationExcluded();
+ }
+}
diff --git a/src/main/java/pl/tomgirl/lenis/PreLaunch.java b/src/fabric/java/pl/tomgirl/pylon/fabric/PreLaunch.java
similarity index 87%
rename from src/main/java/pl/tomgirl/lenis/PreLaunch.java
rename to src/fabric/java/pl/tomgirl/pylon/fabric/PreLaunch.java
index 0e27b3f..df11e48 100644
--- a/src/main/java/pl/tomgirl/lenis/PreLaunch.java
+++ b/src/fabric/java/pl/tomgirl/pylon/fabric/PreLaunch.java
@@ -1,4 +1,4 @@
-package pl.tomgirl.lenis;
+package pl.tomgirl.pylon.fabric;
import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint;
@@ -7,4 +7,4 @@ public class PreLaunch implements PreLaunchEntrypoint {
public void onPreLaunch() {
System.setProperty("java.awt.headless", "true");
}
-}
+}
\ No newline at end of file
diff --git a/src/forge/java/pl/tomgirl/pylon/forge/OldFmlCompat.java b/src/forge/java/pl/tomgirl/pylon/forge/OldFmlCompat.java
new file mode 100644
index 0000000..221af85
--- /dev/null
+++ b/src/forge/java/pl/tomgirl/pylon/forge/OldFmlCompat.java
@@ -0,0 +1,64 @@
+package pl.tomgirl.pylon.forge;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.Map;
+import java.util.jar.JarFile;
+import java.util.stream.Stream;
+
+public class OldFmlCompat {
+ private OldFmlCompat() {}
+
+ public static void prepare(ClassLoader loader) {
+ String remapperName = "cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper";
+ if (loader.getResource(remapperName.replace('.', '/') + ".class") == null) {
+ return;
+ }
+
+ try {
+ try {
+ Class.forName("org.objectweb.asm.Opcodes", false, loader).getField("ASM5");
+ return;
+ } catch (NoSuchFieldException ignored) {}
+
+ Class> type = Class.forName(remapperName, true, loader);
+ Object remapper = type.getField("INSTANCE").get(null);
+ Map> methods = mappings(type, remapper, "methodNameMaps");
+ Map> fields = mappings(type, remapper, "fieldNameMaps");
+ File source = new File(OldFmlCompat.class.getProtectionDomain().getCodeSource().getLocation().toURI());
+ if (source.isDirectory()) {
+ Path root = source.toPath();
+ try (Stream paths = Files.walk(root)) {
+ paths.filter(Files::isRegularFile).forEach(path ->
+ exclude(root.relativize(path).toString().replace(File.separatorChar, '/'), methods, fields));
+ }
+ } else {
+ try (JarFile jar = new JarFile(source)) {
+ jar.stream().forEach(entry -> exclude(entry.getName(), methods, fields));
+ }
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Could not configure legacy FML remapping", e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Map> mappings(Class> type, Object remapper, String name)
+ throws ReflectiveOperationException {
+ Field field = type.getDeclaredField(name);
+ field.setAccessible(true);
+ return (Map>) field.get(remapper);
+ }
+
+ private static void exclude(String entry, Map> methods, Map> fields) {
+ if (!entry.endsWith(".class") || !(entry.startsWith("org/lwjgl/") || entry.startsWith("pl/tomgirl/pylon/"))) {
+ return;
+ }
+ String name = entry.substring(0, entry.length() - ".class".length());
+ methods.put(name, Collections.emptyMap());
+ fields.put(name, Collections.emptyMap());
+ }
+}
diff --git a/src/forge/java/pl/tomgirl/pylon/forge/PylonTransformer.java b/src/forge/java/pl/tomgirl/pylon/forge/PylonTransformer.java
new file mode 100644
index 0000000..dc8b8e9
--- /dev/null
+++ b/src/forge/java/pl/tomgirl/pylon/forge/PylonTransformer.java
@@ -0,0 +1,14 @@
+package pl.tomgirl.pylon.forge;
+
+import net.minecraft.launchwrapper.IClassTransformer;
+import pl.tomgirl.pylon.game.GameTransformer;
+
+public final class PylonTransformer implements IClassTransformer {
+ @Override
+ public byte[] transform(String name, String transformedName, byte[] bytecode) {
+ if (bytecode == null || name.startsWith("pl.tomgirl.pylon.") || name.startsWith("org.lwjgl.")) {
+ return bytecode;
+ }
+ return GameTransformer.transform(bytecode);
+ }
+}
diff --git a/src/forge/java/pl/tomgirl/pylon/forge/PylonTweaker.java b/src/forge/java/pl/tomgirl/pylon/forge/PylonTweaker.java
new file mode 100644
index 0000000..9aa4b31
--- /dev/null
+++ b/src/forge/java/pl/tomgirl/pylon/forge/PylonTweaker.java
@@ -0,0 +1,84 @@
+package pl.tomgirl.pylon.forge;
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.lang.reflect.Field;
+import java.util.List;
+
+import net.minecraft.launchwrapper.ITweaker;
+import net.minecraft.launchwrapper.LaunchClassLoader;
+
+@SuppressWarnings("unused")
+public final class PylonTweaker implements ITweaker {
+ private LaunchClassLoader classLoader;
+
+ @Override
+ public void acceptOptions(List arguments, File gameDirectory, File assetsDirectory, String profile) {
+ System.setProperty("java.awt.headless", "true");
+ }
+
+ @Override
+ public void injectIntoClassLoader(LaunchClassLoader classLoader) {
+ this.classLoader = classLoader;
+ try {
+ Field parent = classLoader.getClass().getDeclaredField("parent");
+ parent.setAccessible(true);
+ ClassLoader launcherParent = (ClassLoader) parent.get(classLoader);
+ URL runtime = PylonTweaker.class.getProtectionDomain().getCodeSource().getLocation();
+ RuntimeClassLoader runtimeLoader = new RuntimeClassLoader(runtime, launcherParent);
+ parent.set(classLoader, runtimeLoader);
+ if (System.getProperty("os.name", "").startsWith("Mac")) {
+ Class> display = runtimeLoader.loadClass("pl.tomgirl.pylon.window.DisplaySdl");
+ Object instance = display.getMethod("instance").invoke(null);
+ display.getMethod("initializeVideo").invoke(instance);
+ }
+ } catch (Exception e) {
+ throw new IllegalStateException("Failed to install the runtime class loader", e);
+ }
+
+ classLoader.addClassLoaderExclusion("pl.tomgirl.pylon.");
+ classLoader.addTransformerExclusion("pl.tomgirl.pylonfml.");
+ classLoader.registerTransformer(PylonTransformer.class.getName());
+ }
+
+ private static class RuntimeClassLoader extends URLClassLoader {
+ RuntimeClassLoader(URL runtime, ClassLoader parent) {
+ super(new URL[]{runtime}, parent);
+ }
+
+ @Override
+ protected synchronized Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ if (name.startsWith("org.lwjgl.") || name.startsWith("pl.tomgirl.pylon.")) {
+ Class> loaded = findLoadedClass(name);
+ if (loaded == null) {
+ try {
+ loaded = findClass(name);
+ } catch (ClassNotFoundException ignored) {
+ loaded = super.loadClass(name, false);
+ }
+ }
+
+ if (resolve) {
+ resolveClass(loaded);
+ }
+
+ return loaded;
+ }
+
+ return super.loadClass(name, resolve);
+ }
+ }
+
+ @Override
+ public String getLaunchTarget() {
+ return "";
+ }
+
+ @Override
+ public String[] getLaunchArguments() {
+ classLoader.addClassLoaderExclusion("org.lwjgl.");
+ OldFmlCompat.prepare(classLoader);
+ return new String[0];
+ }
+}
diff --git a/src/forge/java/pl/tomgirl/pylonfml/CpwModContainer.java b/src/forge/java/pl/tomgirl/pylonfml/CpwModContainer.java
new file mode 100644
index 0000000..cdd68fa
--- /dev/null
+++ b/src/forge/java/pl/tomgirl/pylonfml/CpwModContainer.java
@@ -0,0 +1,30 @@
+package pl.tomgirl.pylonfml;
+
+import com.google.common.eventbus.EventBus;
+import cpw.mods.fml.common.DummyModContainer;
+import cpw.mods.fml.common.LoadController;
+import cpw.mods.fml.common.ModMetadata;
+
+@SuppressWarnings("unused")
+public class CpwModContainer extends DummyModContainer {
+ public CpwModContainer() {
+ this(new ModMetadata());
+ }
+
+ public CpwModContainer(ModMetadata md) {
+ super(md);
+ md.modId = "pylon";
+ md.name = "Pylon";
+ md.description = "LWJGL3 compatibility layer for legacy Minecraft.";
+ md.url = "https://github.com/notdevcody/pylon";
+ String version = getClass().getPackage().getImplementationVersion();
+ md.version = version == null ? "dev" : version;
+ md.authorList.add("notdevcody");
+ md.logoFile = "/assets/pylon/icon-256x.png";
+ }
+
+ @Override
+ public boolean registerBus(EventBus bus, LoadController controller) {
+ return true;
+ }
+}
diff --git a/src/forge/java/pl/tomgirl/pylonfml/FmlModContainer.java b/src/forge/java/pl/tomgirl/pylonfml/FmlModContainer.java
new file mode 100644
index 0000000..dcd6f54
--- /dev/null
+++ b/src/forge/java/pl/tomgirl/pylonfml/FmlModContainer.java
@@ -0,0 +1,30 @@
+package pl.tomgirl.pylonfml;
+
+import com.google.common.eventbus.EventBus;
+import net.minecraftforge.fml.common.DummyModContainer;
+import net.minecraftforge.fml.common.LoadController;
+import net.minecraftforge.fml.common.ModMetadata;
+
+@SuppressWarnings("unused")
+public class FmlModContainer extends DummyModContainer {
+ public FmlModContainer() {
+ this(new ModMetadata());
+ }
+
+ public FmlModContainer(ModMetadata md) {
+ super(md);
+ md.modId = "pylon";
+ md.name = "Pylon";
+ md.description = "LWJGL3 compatibility layer for legacy Minecraft.";
+ md.url = "https://github.com/notdevcody/pylon";
+ String version = getClass().getPackage().getImplementationVersion();
+ md.version = version == null ? "dev" : version;
+ md.authorList.add("notdevcody");
+ md.logoFile = "/assets/pylon/icon-256x.png";
+ }
+
+ @Override
+ public boolean registerBus(EventBus bus, LoadController controller) {
+ return true;
+ }
+}
diff --git a/src/launchwrapper/java/com/google/common/eventbus/EventBus.java b/src/launchwrapper/java/com/google/common/eventbus/EventBus.java
new file mode 100644
index 0000000..3cfd1ca
--- /dev/null
+++ b/src/launchwrapper/java/com/google/common/eventbus/EventBus.java
@@ -0,0 +1,4 @@
+package com.google.common.eventbus;
+
+public class EventBus {
+}
diff --git a/src/launchwrapper/java/cpw/mods/fml/common/DummyModContainer.java b/src/launchwrapper/java/cpw/mods/fml/common/DummyModContainer.java
new file mode 100644
index 0000000..2bd8cfa
--- /dev/null
+++ b/src/launchwrapper/java/cpw/mods/fml/common/DummyModContainer.java
@@ -0,0 +1,12 @@
+package cpw.mods.fml.common;
+
+import com.google.common.eventbus.EventBus;
+
+public class DummyModContainer {
+ public DummyModContainer(ModMetadata md) {
+ }
+
+ public boolean registerBus(EventBus bus, LoadController controller) {
+ return false;
+ }
+}
diff --git a/src/launchwrapper/java/cpw/mods/fml/common/LoadController.java b/src/launchwrapper/java/cpw/mods/fml/common/LoadController.java
new file mode 100644
index 0000000..4053b60
--- /dev/null
+++ b/src/launchwrapper/java/cpw/mods/fml/common/LoadController.java
@@ -0,0 +1,4 @@
+package cpw.mods.fml.common;
+
+public class LoadController {
+}
diff --git a/src/launchwrapper/java/cpw/mods/fml/common/ModMetadata.java b/src/launchwrapper/java/cpw/mods/fml/common/ModMetadata.java
new file mode 100644
index 0000000..8309930
--- /dev/null
+++ b/src/launchwrapper/java/cpw/mods/fml/common/ModMetadata.java
@@ -0,0 +1,14 @@
+package cpw.mods.fml.common;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ModMetadata {
+ public String modId;
+ public String name;
+ public String url = "";
+ public String description = "";
+ public String logoFile = "";
+ public String version = "";
+ public List authorList = new ArrayList<>();
+}
diff --git a/src/launchwrapper/java/net/minecraft/launchwrapper/IClassTransformer.java b/src/launchwrapper/java/net/minecraft/launchwrapper/IClassTransformer.java
new file mode 100644
index 0000000..b90c75a
--- /dev/null
+++ b/src/launchwrapper/java/net/minecraft/launchwrapper/IClassTransformer.java
@@ -0,0 +1,5 @@
+package net.minecraft.launchwrapper;
+
+public interface IClassTransformer {
+ byte[] transform(String name, String transformedName, byte[] bytecode);
+}
diff --git a/src/launchwrapper/java/net/minecraft/launchwrapper/ITweaker.java b/src/launchwrapper/java/net/minecraft/launchwrapper/ITweaker.java
new file mode 100644
index 0000000..28cd90b
--- /dev/null
+++ b/src/launchwrapper/java/net/minecraft/launchwrapper/ITweaker.java
@@ -0,0 +1,11 @@
+package net.minecraft.launchwrapper;
+
+import java.io.File;
+import java.util.List;
+
+public interface ITweaker {
+ void acceptOptions(List arguments, File gameDirectory, File assetsDirectory, String profile);
+ void injectIntoClassLoader(LaunchClassLoader classLoader);
+ String getLaunchTarget();
+ String[] getLaunchArguments();
+}
diff --git a/src/launchwrapper/java/net/minecraft/launchwrapper/LaunchClassLoader.java b/src/launchwrapper/java/net/minecraft/launchwrapper/LaunchClassLoader.java
new file mode 100644
index 0000000..42228bf
--- /dev/null
+++ b/src/launchwrapper/java/net/minecraft/launchwrapper/LaunchClassLoader.java
@@ -0,0 +1,8 @@
+package net.minecraft.launchwrapper;
+
+public abstract class LaunchClassLoader extends ClassLoader {
+ private LaunchClassLoader() {}
+ public native void registerTransformer(String name);
+ public native void addClassLoaderExclusion(String prefix);
+ public native void addTransformerExclusion(String prefix);
+}
diff --git a/src/launchwrapper/java/net/minecraftforge/fml/common/DummyModContainer.java b/src/launchwrapper/java/net/minecraftforge/fml/common/DummyModContainer.java
new file mode 100644
index 0000000..bc54ed9
--- /dev/null
+++ b/src/launchwrapper/java/net/minecraftforge/fml/common/DummyModContainer.java
@@ -0,0 +1,12 @@
+package net.minecraftforge.fml.common;
+
+import com.google.common.eventbus.EventBus;
+
+public class DummyModContainer {
+ public DummyModContainer(ModMetadata md) {
+ }
+
+ public boolean registerBus(EventBus bus, LoadController controller) {
+ return false;
+ }
+}
diff --git a/src/launchwrapper/java/net/minecraftforge/fml/common/LoadController.java b/src/launchwrapper/java/net/minecraftforge/fml/common/LoadController.java
new file mode 100644
index 0000000..a8411e9
--- /dev/null
+++ b/src/launchwrapper/java/net/minecraftforge/fml/common/LoadController.java
@@ -0,0 +1,4 @@
+package net.minecraftforge.fml.common;
+
+public class LoadController {
+}
diff --git a/src/launchwrapper/java/net/minecraftforge/fml/common/ModMetadata.java b/src/launchwrapper/java/net/minecraftforge/fml/common/ModMetadata.java
new file mode 100644
index 0000000..9793f2d
--- /dev/null
+++ b/src/launchwrapper/java/net/minecraftforge/fml/common/ModMetadata.java
@@ -0,0 +1,14 @@
+package net.minecraftforge.fml.common;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ModMetadata {
+ public String modId;
+ public String name;
+ public String url = "";
+ public String description = "";
+ public String logoFile = "";
+ public String version = "";
+ public List authorList = new ArrayList<>();
+}
diff --git a/src/main/java/org/lwjgl/LWJGLException.java b/src/main/java/org/lwjgl/LWJGLException.java
index 0804134..450b3de 100644
--- a/src/main/java/org/lwjgl/LWJGLException.java
+++ b/src/main/java/org/lwjgl/LWJGLException.java
@@ -1,10 +1,7 @@
package org.lwjgl;
-import java.io.Serial;
-
@SuppressWarnings("unused")
public class LWJGLException extends Exception {
- @Serial
private static final long serialVersionUID = 1L;
public LWJGLException() {
diff --git a/src/main/java/org/lwjgl/LWJGLUtil.java b/src/main/java/org/lwjgl/LWJGLUtil.java
index 0eb3cc6..bff05fe 100644
--- a/src/main/java/org/lwjgl/LWJGLUtil.java
+++ b/src/main/java/org/lwjgl/LWJGLUtil.java
@@ -1,18 +1,37 @@
package org.lwjgl;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
+import pl.tomgirl.pylon.Platform;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
@SuppressWarnings("unused")
public class LWJGLUtil {
public static final boolean DEBUG = Boolean.getBoolean("org.lwjgl.util.Debug");
- private static final Logger LOG = LogManager.getLogger("LWJGL");
+ private static final Logger LOG = Logger.getLogger("LWJGL");
private LWJGLUtil() {}
public static void log(CharSequence msg) {
if (DEBUG) {
- LOG.debug(msg);
+ LOG.log(Level.FINE, msg.toString());
+ }
+ }
+
+ public static int getPlatform() {
+ return Platform.CURRENT.ordinal() + 1;
+ }
+
+ public static String getPlatformName() {
+ switch (Platform.CURRENT) {
+ case UNIX:
+ return "linux";
+ case MACOS:
+ return "macosx";
+ case WINDOWS:
+ return "windows";
+ default:
+ return "unknown";
}
}
}
diff --git a/src/main/java/org/lwjgl/Sys.java b/src/main/java/org/lwjgl/Sys.java
index 5ffa56e..38e7457 100644
--- a/src/main/java/org/lwjgl/Sys.java
+++ b/src/main/java/org/lwjgl/Sys.java
@@ -1,6 +1,6 @@
package org.lwjgl;
-import pl.tomgirl.lenis.Platform;
+import pl.tomgirl.pylon.Platform;
@SuppressWarnings("unused")
public class Sys {
diff --git a/src/main/java/org/lwjgl/input/Controllers.java b/src/main/java/org/lwjgl/input/Controllers.java
new file mode 100644
index 0000000..8479de7
--- /dev/null
+++ b/src/main/java/org/lwjgl/input/Controllers.java
@@ -0,0 +1,18 @@
+package org.lwjgl.input;
+
+import org.lwjgl.LWJGLException;
+
+@SuppressWarnings("unused")
+public final class Controllers {
+ private Controllers() {}
+
+ public static void create() throws LWJGLException {
+ }
+
+ public static void destroy() {
+ }
+
+ public static boolean isCreated() {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/lwjgl/input/Keyboard.java b/src/main/java/org/lwjgl/input/Keyboard.java
index 7d5ceb0..abc5fb6 100644
--- a/src/main/java/org/lwjgl/input/Keyboard.java
+++ b/src/main/java/org/lwjgl/input/Keyboard.java
@@ -10,7 +10,7 @@
import org.lwjgl.Sys;
import org.lwjgl.opengl.Display;
import org.lwjgl.sdl.SDLKeyboard;
-import pl.tomgirl.lenis.window.KeyboardSdl;
+import pl.tomgirl.pylon.window.KeyboardSdl;
@SuppressWarnings("unused")
public class Keyboard {
diff --git a/src/main/java/org/lwjgl/input/Mouse.java b/src/main/java/org/lwjgl/input/Mouse.java
index 6e8d235..e62094a 100644
--- a/src/main/java/org/lwjgl/input/Mouse.java
+++ b/src/main/java/org/lwjgl/input/Mouse.java
@@ -3,7 +3,7 @@
import java.util.HashMap;
import java.util.Map;
-import pl.tomgirl.lenis.window.MouseSdl;
+import pl.tomgirl.pylon.window.MouseSdl;
import org.lwjgl.LWJGLException;
import org.lwjgl.Sys;
import org.lwjgl.opengl.Display;
@@ -41,6 +41,13 @@ public class Mouse {
private static double lastRawEventY;
private static long eventNanos;
+ // compatibility fields
+ // shout out to a mod which sets these w/ reflection
+ private static int x;
+ private static int y;
+ private static int event_x;
+ private static int event_y;
+
private Mouse() {}
private static void checkCreated() {
@@ -50,7 +57,7 @@ private static void checkCreated() {
}
private static double clamp(double coordinate, int displaySize) {
- return Math.clamp(coordinate, 0, displaySize - 1);
+ return Math.max(0, Math.min(coordinate, displaySize - 1));
}
private static void initialize() {
diff --git a/src/main/java/org/lwjgl/opengl/Display.java b/src/main/java/org/lwjgl/opengl/Display.java
index ccfd61b..f7a6564 100644
--- a/src/main/java/org/lwjgl/opengl/Display.java
+++ b/src/main/java/org/lwjgl/opengl/Display.java
@@ -1,12 +1,14 @@
package org.lwjgl.opengl;
import java.nio.ByteBuffer;
+import java.util.logging.Level;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.LWJGLException;
-import pl.tomgirl.lenis.window.GlSurface;
-import pl.tomgirl.lenis.window.DisplaySdl;
+import pl.tomgirl.pylon.Pylon;
+import pl.tomgirl.pylon.window.GlSurface;
+import pl.tomgirl.pylon.window.DisplaySdl;
@SuppressWarnings("unused")
public class Display {
@@ -60,7 +62,16 @@ public static void create() throws LWJGLException {
}
public static void create(@NotNull PixelFormat pixelFormat) throws LWJGLException {
- SDL.create(new GlSurface(pixelFormat));
+ try {
+ SDL.create(new GlSurface(pixelFormat));
+ } catch (RuntimeException e) {
+ Pylon.LOG.log(Level.SEVERE, "Failed to create display", e);
+ throw new LWJGLException(e);
+ }
+ }
+
+ public static boolean isFullscreen() {
+ return SDL.isFullscreen();
}
public static void setFullscreen(boolean fullscreen) {
@@ -80,6 +91,15 @@ public static boolean isCreated() {
return SDL.isCreated();
}
+ public static boolean isCurrent() {
+ try {
+ return SDL.getDrawable().isCurrent();
+ } catch (LWJGLException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
public static boolean isActive() {
return SDL.isActive();
}
@@ -119,4 +139,8 @@ public static boolean isCloseRequested() {
public static void swapBuffers() {
SDL.swapBuffers();
}
+
+ public static void setParent(java.awt.Canvas o) {}
+
+ public static void setDisplayConfiguration(float gamma, float brightness, float contrast) {}
}
diff --git a/src/main/java/org/lwjgl/opengl/DisplayMode.java b/src/main/java/org/lwjgl/opengl/DisplayMode.java
index 9a55eb7..363b11e 100644
--- a/src/main/java/org/lwjgl/opengl/DisplayMode.java
+++ b/src/main/java/org/lwjgl/opengl/DisplayMode.java
@@ -42,10 +42,11 @@ public int getFrequency() {
}
public boolean equals(Object obj) {
- if (!(obj instanceof DisplayMode dm)) {
+ if (!(obj instanceof DisplayMode)) {
return false;
}
+ DisplayMode dm = (DisplayMode) obj;
return dm.width == width && dm.height == height && dm.bpp == bpp && dm.freq == freq;
}
diff --git a/src/main/java/org/lwjgl/opengl/Drawable.java b/src/main/java/org/lwjgl/opengl/Drawable.java
index f9a7101..10ea4c2 100644
--- a/src/main/java/org/lwjgl/opengl/Drawable.java
+++ b/src/main/java/org/lwjgl/opengl/Drawable.java
@@ -4,6 +4,7 @@
@SuppressWarnings("unused")
public interface Drawable {
+ boolean isCurrent() throws LWJGLException;
void makeCurrent() throws LWJGLException;
void releaseContext() throws LWJGLException;
void destroy();
diff --git a/src/main/java/org/lwjgl/opengl/OpenGLException.java b/src/main/java/org/lwjgl/opengl/OpenGLException.java
index d998b86..016bc84 100644
--- a/src/main/java/org/lwjgl/opengl/OpenGLException.java
+++ b/src/main/java/org/lwjgl/opengl/OpenGLException.java
@@ -1,10 +1,7 @@
package org.lwjgl.opengl;
-import java.io.Serial;
-
@SuppressWarnings("unused")
public class OpenGLException extends RuntimeException {
- @Serial
private static final long serialVersionUID = 1L;
public OpenGLException(int gl_error_code) {
diff --git a/src/main/java/org/lwjgl/opengl/SharedDrawable.java b/src/main/java/org/lwjgl/opengl/SharedDrawable.java
index b3d921a..fb3d8c6 100644
--- a/src/main/java/org/lwjgl/opengl/SharedDrawable.java
+++ b/src/main/java/org/lwjgl/opengl/SharedDrawable.java
@@ -2,26 +2,81 @@
import org.lwjgl.LWJGLException;
+import static org.lwjgl.sdl.SDLError.SDL_GetError;
+import static org.lwjgl.sdl.SDLVideo.*;
+import static org.lwjgl.system.MemoryUtil.memFree;
+
@SuppressWarnings("unused")
public class SharedDrawable implements Drawable {
- private final Drawable drawable;
+ private final long window;
+ private long context;
+ private GLCapabilities capabilities;
+
+ public SharedDrawable(final Drawable drawable) throws LWJGLException {
+ long previousWindow = SDL_GL_GetCurrentWindow();
+ long previousContext = SDL_GL_GetCurrentContext();
+ GLCapabilities previousCapabilities = previousContext == 0 ? null : GL.getCapabilities();
+ try {
+ drawable.makeCurrent();
+ window = SDL_GL_GetCurrentWindow();
+ check(SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1));
+ try {
+ context = SDL_GL_CreateContext(window);
+ check(context != 0);
+ } finally {
+ SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 0);
+ }
+ capabilities = GL.createCapabilities(org.lwjgl.system.MemoryUtil::memCallocPointer);
+ } catch (LWJGLException | RuntimeException | Error e) {
+ destroy();
+ throw e;
+ } finally {
+ check(SDL_GL_MakeCurrent(previousWindow, previousContext));
+ GL.setCapabilities(previousCapabilities);
+ }
+ }
- public SharedDrawable(final Drawable drawable) {
- this.drawable = drawable;
+ @Override
+ public boolean isCurrent() throws LWJGLException {
+ return context != 0 && SDL_GL_GetCurrentContext() == context;
}
@Override
public void makeCurrent() throws LWJGLException {
- drawable.makeCurrent();
+ if (context == 0) {
+ throw new LWJGLException("Shared context has been destroyed");
+ }
+ check(SDL_GL_MakeCurrent(window, context));
+ GL.setCapabilities(capabilities);
}
@Override
public void releaseContext() throws LWJGLException {
- drawable.releaseContext();
+ if (isCurrent()) {
+ check(SDL_GL_MakeCurrent(window, 0));
+ GL.setCapabilities(null);
+ }
}
@Override
public void destroy() {
- drawable.destroy();
+ if (context != 0) {
+ if (SDL_GL_GetCurrentContext() == context) {
+ SDL_GL_MakeCurrent(window, 0);
+ GL.setCapabilities(null);
+ }
+ SDL_GL_DestroyContext(context);
+ context = 0;
+ }
+ if (capabilities != null) {
+ memFree(capabilities.getAddressBuffer());
+ capabilities = null;
+ }
+ }
+
+ private static void check(boolean success) throws LWJGLException {
+ if (!success) {
+ throw new LWJGLException("SDL error encountered: " + SDL_GetError());
+ }
}
}
diff --git a/src/main/java/org/lwjgl/opengl/Util.java b/src/main/java/org/lwjgl/opengl/Util.java
index a2c2d33..018bd1d 100644
--- a/src/main/java/org/lwjgl/opengl/Util.java
+++ b/src/main/java/org/lwjgl/opengl/Util.java
@@ -21,17 +21,27 @@ public static void checkGLError() throws OpenGLException {
}
public static String translateGLErrorString(int error_code) {
- return switch (error_code) {
- case GL_NO_ERROR -> "No error";
- case GL_INVALID_ENUM -> "Invalid enum";
- case GL_INVALID_VALUE -> "Invalid value";
- case GL_INVALID_OPERATION -> "Invalid operation";
- case GL_STACK_OVERFLOW -> "Stack overflow";
- case GL_STACK_UNDERFLOW -> "Stack underflow";
- case GL_OUT_OF_MEMORY -> "Out of memory";
- case GL_TABLE_TOO_LARGE -> "Table too large";
- case GL_INVALID_FRAMEBUFFER_OPERATION -> "Invalid framebuffer operation";
- default -> null;
- };
+ switch (error_code) {
+ case GL_NO_ERROR:
+ return "No error";
+ case GL_INVALID_ENUM:
+ return "Invalid enum";
+ case GL_INVALID_VALUE:
+ return "Invalid value";
+ case GL_INVALID_OPERATION:
+ return "Invalid operation";
+ case GL_STACK_OVERFLOW:
+ return "Stack overflow";
+ case GL_STACK_UNDERFLOW:
+ return "Stack underflow";
+ case GL_OUT_OF_MEMORY:
+ return "Out of memory";
+ case GL_TABLE_TOO_LARGE:
+ return "Table too large";
+ case GL_INVALID_FRAMEBUFFER_OPERATION:
+ return "Invalid framebuffer operation";
+ default:
+ return null;
+ }
}
}
diff --git a/src/main/java/org/lwjgl/util/glu/GLU.java b/src/main/java/org/lwjgl/util/glu/GLU.java
index 7199506..72f1039 100644
--- a/src/main/java/org/lwjgl/util/glu/GLU.java
+++ b/src/main/java/org/lwjgl/util/glu/GLU.java
@@ -2,6 +2,7 @@
import org.lwjgl.opengl.Util;
+import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
@@ -156,12 +157,24 @@ public static boolean gluCheckExtension(String extName, String extString) {
return Registry.gluCheckExtension(extName, extString);
}
+ public static int gluBuild2DMipmaps(int target, int components, int width, int height, int format, int type, ByteBuffer data) {
+ return MipMap.gluBuild2DMipmaps(target, components, width, height, format, type, data);
+ }
+
+ public static int gluScaleImage(int format, int widthIn, int heightIn, int typeIn, ByteBuffer dataIn, int widthOut, int heightOut, int typeOut, ByteBuffer dataOut) {
+ return MipMap.gluScaleImage(format, widthIn, heightIn, typeIn, dataIn, widthOut, heightOut, typeOut, dataOut);
+ }
+
public static String gluErrorString(int error_code) {
- return switch (error_code) {
- case GLU_INVALID_ENUM -> "Invalid enum (glu)";
- case GLU_INVALID_VALUE -> "Invalid value (glu)";
- case GLU_OUT_OF_MEMORY -> "Out of memory (glu)";
- default -> Util.translateGLErrorString(error_code);
- };
+ switch (error_code) {
+ case GLU_INVALID_ENUM:
+ return "Invalid enum (glu)";
+ case GLU_INVALID_VALUE:
+ return "Invalid value (glu)";
+ case GLU_OUT_OF_MEMORY:
+ return "Out of memory (glu)";
+ default:
+ return Util.translateGLErrorString(error_code);
+ }
}
}
diff --git a/src/main/java/org/lwjgl/util/glu/MipMap.java b/src/main/java/org/lwjgl/util/glu/MipMap.java
new file mode 100644
index 0000000..61d203a
--- /dev/null
+++ b/src/main/java/org/lwjgl/util/glu/MipMap.java
@@ -0,0 +1,237 @@
+package org.lwjgl.util.glu;
+
+import org.lwjgl.BufferUtils;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import static org.lwjgl.opengl.GL11.*;
+import static org.lwjgl.util.glu.GLU.*;
+
+public class MipMap extends Util {
+ public static int gluBuild2DMipmaps(int target, int components, int width, int height, int format, int type, ByteBuffer data) {
+ if (width < 1 || height < 1) {
+ return GLU_INVALID_VALUE;
+ }
+
+ int bpp = bytesPerPixel(format, type);
+ if (bpp == 0) {
+ return GLU_INVALID_ENUM;
+ }
+
+ int maxSize = glGetIntegerv(GL_MAX_TEXTURE_SIZE);
+ int w = nearestPower(width);
+ if (w > maxSize) {
+ w = maxSize;
+ }
+
+ int h = nearestPower(height);
+ if (h > maxSize) {
+ h = maxSize;
+ }
+
+ PixelStoreState pss = new PixelStoreState();
+ glPixelStorei(GL_PACK_ROW_LENGTH, 0);
+ glPixelStorei(GL_PACK_ALIGNMENT, 1);
+ glPixelStorei(GL_PACK_SKIP_ROWS, 0);
+ glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
+
+ int err = 0;
+ boolean done = false;
+ ByteBuffer image;
+ if (w == width && h == height) {
+ image = data;
+ } else {
+ image = BufferUtils.createByteBuffer((w + 4) * h * bpp);
+ int error = gluScaleImage(format, width, height, type, data, w, h, type, image);
+ if (error != 0) {
+ err = error;
+ done = true;
+ }
+ }
+
+ ByteBuffer scratch = image != data ? image : null;
+
+ for (int level = 0; !done; level++) {
+ if (image != data) {
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
+ }
+
+ glTexImage2D(target, level, components, w, h, 0, format, type, image);
+ if (w == 1 && h == 1) {
+ break;
+ }
+
+ int newW = Math.max(1, w >> 1);
+ int newH = Math.max(1, h >> 1);
+ if (scratch == null) {
+ scratch = BufferUtils.createByteBuffer((newW + 4) * newH * bpp);
+ }
+
+ int error = gluScaleImage(format, w, h, type, image, newW, newH, type, scratch);
+ if (error != 0) {
+ err = error;
+ break;
+ }
+
+ image = scratch;
+ w = newW;
+ h = newH;
+ }
+
+ pss.save();
+ return err;
+ }
+
+ public static int gluScaleImage(int format, int widthIn, int heightIn, int typein, ByteBuffer dataIn, int widthOut, int heightOut, int typeOut, ByteBuffer dataOut) {
+ int components = compPerPix(format);
+ if (components == -1) {
+ return GLU_INVALID_ENUM;
+ }
+
+ float[] tempIn = new float[widthIn * heightIn * components];
+ float[] tempOut = new float[widthOut * heightOut * components];
+
+ int sizein;
+ switch (typein) {
+ case GL_UNSIGNED_BYTE:
+ sizein = 1;
+ break;
+ case GL_FLOAT:
+ sizein = 4;
+ break;
+ default:
+ return GL_INVALID_ENUM;
+ }
+
+ int sizeout;
+ switch (typeOut) {
+ case GL_UNSIGNED_BYTE:
+ sizeout = 1;
+ break;
+ case GL_FLOAT:
+ sizeout = 4;
+ break;
+ default:
+ return GL_INVALID_ENUM;
+ }
+
+ PixelStoreState pss = new PixelStoreState();
+ int rowlen;
+ if (pss.unpackRowLength > 0) {
+ rowlen = pss.unpackRowLength;
+ } else {
+ rowlen = widthIn;
+ }
+
+ int rowstride;
+ if (sizein >= pss.unpackAlignment) {
+ rowstride = components * rowlen;
+ } else {
+ rowstride = pss.unpackAlignment / sizein * ceil(components * rowlen * sizein, pss.unpackAlignment);
+ }
+
+ int temp = 0;
+ switch (typein) {
+ case GL_UNSIGNED_BYTE:
+ dataIn.rewind();
+ for (int i = 0; i < heightIn; i++ ) {
+ int ubptr = (i + pss.unpackSkipRows) * rowstride + pss.unpackSkipPixels * components;
+ for (int j = 0; j < widthIn * components; j++ ) {
+ tempIn[temp++] = dataIn.get(ubptr++) & 0xff;
+ }
+ }
+ break;
+ case GL_FLOAT:
+ dataIn.rewind();
+ for (int i = 0; i < heightIn; i++ ) {
+ int fptr = 4 * ((i + pss.unpackSkipRows) * rowstride + pss.unpackSkipPixels * components);
+ for (int j = 0; j < widthIn * components; j++ ) {
+ tempIn[temp++] = dataIn.getFloat(fptr);
+ fptr += 4;
+ }
+ }
+ break;
+ }
+
+ float sx = (float)widthIn / widthOut;
+ float sy = (float)heightIn / heightOut;
+ float[] c = new float[components];
+
+ for (int iy = 0; iy < heightOut; iy++) {
+ for (int ix = 0; ix < widthOut; ix++) {
+ int x0 = (int)(ix * sx);
+ int x1 = (int)((ix + 1) * sx);
+ int y0 = (int)(iy * sy);
+ int y1 = (int)((iy + 1) * sy);
+ int readPix = 0;
+
+ Arrays.fill(c, 0.0F);
+
+ for (int ix0 = x0; ix0 < x1; ix0++) {
+ for (int iy0 = y0; iy0 < y1; iy0++) {
+ int src = (iy0 * widthIn + ix0) * components;
+
+ for (int ic = 0; ic < components; ic++) {
+ c[ic] += tempIn[src + ic];
+ }
+
+ readPix++;
+ }
+ }
+
+ int dst = (iy * widthOut + ix) * components;
+ if (readPix == 0) {
+ int src = (y0 * widthIn + x0) * components;
+
+ for (int ic = 0; ic < components; ic++) {
+ tempOut[dst++] = tempIn[src + ic];
+ }
+ } else {
+ for (int i = 0; i < components; i++) {
+ tempOut[dst++] = c[i] / readPix;
+ }
+ }
+ }
+ }
+
+ if (pss.packRowLength > 0) {
+ rowlen = pss.packRowLength;
+ } else {
+ rowlen = widthOut;
+ }
+
+ if (sizeout >= pss.packAlignment) {
+ rowstride = components * rowlen;
+ } else {
+ rowstride = pss.packAlignment / sizeout * ceil(components * rowlen * sizeout, pss.packAlignment);
+ }
+
+ temp = 0;
+ switch (typeOut) {
+ case GL_UNSIGNED_BYTE:
+ for (int i = 0; i < heightOut; i++) {
+ int ubptr = (i + pss.packSkipRows) * rowstride + pss.packSkipPixels * components;
+
+ for (int j = 0; j < widthOut * components; j++) {
+ dataOut.put(ubptr++, (byte)tempOut[temp++]);
+ }
+ }
+ break;
+ case GL_FLOAT:
+ for (int i = 0; i < heightOut; i++) {
+ int fptr = 4 * ((i + pss.unpackSkipRows) * rowstride + pss.unpackSkipPixels * components);
+
+ for (int j = 0; j < widthOut * components; j++) {
+ dataOut.putFloat(fptr, tempOut[temp++]);
+ fptr += 4;
+ }
+ }
+ break;
+ }
+
+ return 0;
+ }
+}
diff --git a/src/main/java/org/lwjgl/util/glu/PixelStoreState.java b/src/main/java/org/lwjgl/util/glu/PixelStoreState.java
new file mode 100644
index 0000000..a521a27
--- /dev/null
+++ b/src/main/java/org/lwjgl/util/glu/PixelStoreState.java
@@ -0,0 +1,40 @@
+package org.lwjgl.util.glu;
+
+import static org.lwjgl.opengl.GL11.*;
+
+class PixelStoreState extends Util {
+ public int unpackRowLength;
+ public int unpackAlignment;
+ public int unpackSkipRows;
+ public int unpackSkipPixels;
+ public int packRowLength;
+ public int packAlignment;
+ public int packSkipRows;
+ public int packSkipPixels;
+
+ PixelStoreState() {
+ this.load();
+ }
+
+ public void load() {
+ this.unpackRowLength = glGetIntegerv(GL_UNPACK_ROW_LENGTH);
+ this.unpackAlignment = glGetIntegerv(GL_UNPACK_ALIGNMENT);
+ this.unpackSkipRows = glGetIntegerv(GL_UNPACK_SKIP_ROWS);
+ this.unpackSkipPixels = glGetIntegerv(GL_UNPACK_SKIP_PIXELS);
+ this.packRowLength = glGetIntegerv(GL_PACK_ROW_LENGTH);
+ this.packAlignment = glGetIntegerv(GL_PACK_ALIGNMENT);
+ this.packSkipRows = glGetIntegerv(GL_PACK_SKIP_ROWS);
+ this.packSkipPixels = glGetIntegerv(GL_PACK_SKIP_PIXELS);
+ }
+
+ public void save() {
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, this.unpackRowLength);
+ glPixelStorei(GL_UNPACK_ALIGNMENT, this.unpackAlignment);
+ glPixelStorei(GL_UNPACK_SKIP_ROWS, this.unpackSkipRows);
+ glPixelStorei(GL_UNPACK_SKIP_PIXELS, this.unpackSkipPixels);
+ glPixelStorei(GL_PACK_ROW_LENGTH, this.packRowLength);
+ glPixelStorei(GL_PACK_ALIGNMENT, this.packAlignment);
+ glPixelStorei(GL_PACK_SKIP_ROWS, this.packSkipRows);
+ glPixelStorei(GL_PACK_SKIP_PIXELS, this.packSkipPixels);
+ }
+}
diff --git a/src/main/java/org/lwjgl/util/glu/Util.java b/src/main/java/org/lwjgl/util/glu/Util.java
index ec1777d..020e8fe 100644
--- a/src/main/java/org/lwjgl/util/glu/Util.java
+++ b/src/main/java/org/lwjgl/util/glu/Util.java
@@ -31,13 +31,27 @@ protected static void cross(float[] v1, float[] v2, float[] result) {
}
protected static int compPerPix(int format) {
- return switch (format) {
- case GL_COLOR_INDEX, GL_STENCIL_INDEX, GL_DEPTH_COMPONENT, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_LUMINANCE -> 1;
- case GL_LUMINANCE_ALPHA -> 2;
- case GL_RGB, GL_BGR -> 3;
- case GL_RGBA, GL_BGRA -> 4;
- default -> -1;
- };
+ switch (format) {
+ case GL_COLOR_INDEX:
+ case GL_STENCIL_INDEX:
+ case GL_DEPTH_COMPONENT:
+ case GL_RED:
+ case GL_GREEN:
+ case GL_BLUE:
+ case GL_ALPHA:
+ case GL_LUMINANCE:
+ return 1;
+ case GL_LUMINANCE_ALPHA:
+ return 2;
+ case GL_RGB:
+ case GL_BGR:
+ return 3;
+ case GL_RGBA:
+ case GL_BGRA:
+ return 4;
+ default:
+ return -1;
+ }
}
protected static int nearestPower(int value) {
@@ -58,20 +72,27 @@ protected static int nearestPower(int value) {
}
protected static int bytesPerPixel(int format, int type) {
- int n = switch (format) {
- case GL_COLOR_INDEX, GL_STENCIL_INDEX, GL_DEPTH_COMPONENT, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_LUMINANCE -> 1;
- case GL_LUMINANCE_ALPHA -> 2;
- case GL_RGB, GL_BGR -> 3;
- case GL_RGBA, GL_BGRA -> 4;
- default -> 0;
- };
+ int n = Math.max(compPerPix(format), 0);
- int m = switch (type) {
- case GL_UNSIGNED_BYTE, GL_BITMAP, GL_BYTE -> 1;
- case GL_UNSIGNED_SHORT, GL_SHORT -> 2;
- case GL_UNSIGNED_INT, GL_FLOAT, GL_INT -> 4;
- default -> 0;
- };
+ int m;
+ switch (type) {
+ case GL_UNSIGNED_BYTE:
+ case GL_BITMAP:
+ case GL_BYTE:
+ m = 1;
+ break;
+ case GL_UNSIGNED_SHORT:
+ case GL_SHORT:
+ m = 2;
+ break;
+ case GL_UNSIGNED_INT:
+ case GL_FLOAT:
+ case GL_INT:
+ m = 4;
+ break;
+ default:
+ m = 0;
+ }
return n * m;
}
diff --git a/src/main/java/pl/tomgirl/lenis/Lenis.java b/src/main/java/pl/tomgirl/lenis/Lenis.java
deleted file mode 100644
index 8e7c61a..0000000
--- a/src/main/java/pl/tomgirl/lenis/Lenis.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package pl.tomgirl.lenis;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-public class Lenis {
- public static final Logger LOG = LogManager.getLogger("Lenis");
-}
diff --git a/src/main/java/pl/tomgirl/lenis/mixin/AL10Mixin.java b/src/main/java/pl/tomgirl/lenis/mixin/AL10Mixin.java
deleted file mode 100644
index b68be0f..0000000
--- a/src/main/java/pl/tomgirl/lenis/mixin/AL10Mixin.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package pl.tomgirl.lenis.mixin;
-
-import pl.tomgirl.lenis.plugin.CompatStub;
-
-import java.nio.FloatBuffer;
-import java.nio.IntBuffer;
-import org.lwjgl.openal.AL10;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Unique;
-
-@SuppressWarnings("unused")
-@Mixin(value = AL10.class, remap = false)
-public abstract class AL10Mixin {
- @Unique @CompatStub("alListenerfv")
- private static void alListener(int paramName, FloatBuffer values) {}
-
- @Unique @CompatStub("alSourcefv")
- private static void alSource(int source, int param, FloatBuffer values) {}
-
- @Unique @CompatStub("alSourceStopv")
- private static void alSourceStop(IntBuffer sources) {}
-}
diff --git a/src/main/java/pl/tomgirl/lenis/mixin/ARBShaderObjectsMixin.java b/src/main/java/pl/tomgirl/lenis/mixin/ARBShaderObjectsMixin.java
deleted file mode 100644
index 70c7f48..0000000
--- a/src/main/java/pl/tomgirl/lenis/mixin/ARBShaderObjectsMixin.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package pl.tomgirl.lenis.mixin;
-
-import pl.tomgirl.lenis.plugin.CompatStub;
-
-import java.nio.FloatBuffer;
-import java.nio.IntBuffer;
-import org.lwjgl.opengl.ARBShaderObjects;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Unique;
-
-@SuppressWarnings("unused")
-@Mixin(value = ARBShaderObjects.class, remap = false)
-public abstract class ARBShaderObjectsMixin {
- @Unique @CompatStub("glGetObjectParameterivARB")
- private static void glGetObjectParameterARB(int obj, int pname, IntBuffer params) {}
-
- @Unique @CompatStub("glUniformMatrix4fvARB")
- private static void glUniformMatrix4ARB(int location, boolean transpose, FloatBuffer value) {}
-
- @Unique @CompatStub("glUniform1ivARB")
- private static void glUniform1ARB(int location, IntBuffer value) {}
-}
diff --git a/src/main/java/pl/tomgirl/lenis/mixin/GL11Mixin.java b/src/main/java/pl/tomgirl/lenis/mixin/GL11Mixin.java
deleted file mode 100644
index 64ae8dd..0000000
--- a/src/main/java/pl/tomgirl/lenis/mixin/GL11Mixin.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package pl.tomgirl.lenis.mixin;
-
-import pl.tomgirl.lenis.plugin.CompatStub;
-
-import java.nio.ByteBuffer;
-import java.nio.FloatBuffer;
-import java.nio.IntBuffer;
-import java.nio.ShortBuffer;
-
-import org.lwjgl.opengl.GL11;
-import org.lwjgl.system.MemoryUtil;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Unique;
-
-@SuppressWarnings("unused")
-@Mixin(value = GL11.class, remap = false)
-public abstract class GL11Mixin {
- @Unique @CompatStub
- private static void glDrawElements(int mode, int count, int type, ByteBuffer indices) {
- GL11.nglDrawElements(mode, count, type, MemoryUtil.memAddress(indices));
- }
-
- @Unique @CompatStub
- private static void glTexCoordPointer(int i, int stride, FloatBuffer pointer) {
- GL11.glTexCoordPointer(i, 0x1406, stride, pointer);
- }
-
- @Unique @CompatStub
- private static void glTexCoordPointer(int i, int stride, ShortBuffer pointer) {
- GL11.glTexCoordPointer(i, 0x1402, stride, pointer);
- }
-
- @Unique @CompatStub
- private static void glColorPointer(int i, boolean bl, int i2, ByteBuffer pointer) {
- GL11.glColorPointer(i, 0x1401, i2, pointer);
- }
-
- @Unique @CompatStub
- private static void glVertexPointer(int i, int i2, FloatBuffer pointer) {
- GL11.glVertexPointer(i, 0x1406, i2, pointer);
- }
-
- @Unique @CompatStub
- private static void glNormalPointer(int stride, ByteBuffer pointer) {
- GL11.glNormalPointer(0x1400, stride, pointer);
- }
-
- @Unique @CompatStub("glGetFloatv") private static void glGetFloat(int pname, FloatBuffer params) {}
- @Unique @CompatStub("glGetIntegerv") private static void glGetInteger(int pname, IntBuffer params) {}
- @Unique @CompatStub("glFogfv") private static void glFog(int pname, FloatBuffer params) {}
- @Unique @CompatStub("glLightfv") private static void glLight(int light, int pname, FloatBuffer params) {}
- @Unique @CompatStub("glLightModelfv") private static void glLightModel(int pname, FloatBuffer params) {}
- @Unique @CompatStub("glMultMatrixf") private static void glMultMatrix(FloatBuffer matrix) {}
- @Unique @CompatStub("glTexEnvfv") private static void glTexEnv(int target, int pname, FloatBuffer params) {}
- @Unique @CompatStub("glTexGenfv") private static void glTexGen(int coord, int pname, FloatBuffer params) {}
- @Unique @CompatStub("glLoadMatrixf") private static void glLoadMatrix(FloatBuffer matrix) {}
-}
diff --git a/src/main/java/pl/tomgirl/lenis/mixin/GL20Mixin.java b/src/main/java/pl/tomgirl/lenis/mixin/GL20Mixin.java
deleted file mode 100644
index cdc471a..0000000
--- a/src/main/java/pl/tomgirl/lenis/mixin/GL20Mixin.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package pl.tomgirl.lenis.mixin;
-
-import pl.tomgirl.lenis.plugin.CompatStub;
-
-import java.nio.FloatBuffer;
-import java.nio.IntBuffer;
-import org.lwjgl.opengl.GL20;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Unique;
-
-@SuppressWarnings("unused")
-@Mixin(value = GL20.class, remap = false)
-public abstract class GL20Mixin {
- @Unique @CompatStub
- private static void glShaderSource(int shader, java.nio.ByteBuffer string) {
- byte[] data = new byte[string.limit()];
- string.position(0);
- string.get(data);
- string.position(0);
- GL20.glShaderSource(shader, new String(data));
- }
-
- @Unique @CompatStub("glUniformMatrix4fv") private static void glUniformMatrix4(int location, boolean transpose, FloatBuffer value) {}
- @Unique @CompatStub("glUniform2fv") private static void glUniform2(int location, FloatBuffer value) {}
- @Unique @CompatStub("glUniform1iv") private static void glUniform1(int location, IntBuffer value) {}
- @Unique @CompatStub("glUniform1fv") private static void glUniform1(int location, FloatBuffer value) {}
- @Unique @CompatStub("glUniform2iv") private static void glUniform2i(int location, IntBuffer value) {}
- @Unique @CompatStub("glUniform3iv") private static void glUniform3(int location, IntBuffer value) {}
- @Unique @CompatStub("glUniform3fv") private static void glUniform3(int location, FloatBuffer value) {}
- @Unique @CompatStub("glUniform4iv") private static void glUniform4(int location, IntBuffer value) {}
- @Unique @CompatStub("glUniform4fv") private static void glUniform4(int location, FloatBuffer value) {}
- @Unique @CompatStub("glUniformMatrix2fv") private static void glUniformMatrix2(int location, boolean transpose, FloatBuffer value) {}
- @Unique @CompatStub("glUniformMatrix3fv") private static void glUniformMatrix3(int location, boolean transpose, FloatBuffer value) {}
-}
diff --git a/src/main/java/pl/tomgirl/lenis/mixin/GLCapabilitiesMixin.java b/src/main/java/pl/tomgirl/lenis/mixin/GLCapabilitiesMixin.java
deleted file mode 100644
index b2c3e22..0000000
--- a/src/main/java/pl/tomgirl/lenis/mixin/GLCapabilitiesMixin.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package pl.tomgirl.lenis.mixin;
-
-import java.util.Set;
-import java.util.function.IntFunction;
-import org.lwjgl.PointerBuffer;
-import org.lwjgl.opengl.GLCapabilities;
-import org.lwjgl.system.FunctionProvider;
-import org.spongepowered.asm.mixin.Final;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Mutable;
-import org.spongepowered.asm.mixin.Unique;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-
-@Mixin(value = GLCapabilities.class, remap = false)
-public abstract class GLCapabilitiesMixin {
- @Mutable @Unique @Final public boolean GL_EXT_multi_draw_arrays;
- @Mutable @Unique @Final public boolean GL_EXT_paletted_texture;
- @Mutable @Unique @Final public boolean GL_EXT_rescale_normal;
- @Mutable @Unique @Final public boolean GL_EXT_texture_3d;
- @Mutable @Unique @Final public boolean GL_EXT_texture_lod_bias;
- @Mutable @Unique @Final public boolean GL_EXT_vertex_shader;
- @Mutable @Unique @Final public boolean GL_EXT_vertex_weighting;
-
- @Inject(method = "", at = @At("TAIL"))
- private void initExtendedFields(FunctionProvider provider, Set ext, boolean fc, IntFunction bufferFactory, CallbackInfo ci) {
- GL_EXT_multi_draw_arrays = ext.contains("GL_EXT_multi_draw_arrays");
- GL_EXT_paletted_texture = ext.contains("GL_EXT_paletted_texture");
- GL_EXT_rescale_normal = ext.contains("GL_EXT_rescale_normal");
- GL_EXT_texture_3d = ext.contains("GL_EXT_texture_3d");
- GL_EXT_texture_lod_bias = ext.contains("GL_EXT_texture_lod_bias");
- GL_EXT_vertex_shader = ext.contains("GL_EXT_vertex_shader");
- GL_EXT_vertex_weighting = ext.contains("GL_EXT_vertex_weighting");
- }
-}
diff --git a/src/main/java/pl/tomgirl/lenis/mixin/LibraryLWJGLOpenALMixin.java b/src/main/java/pl/tomgirl/lenis/mixin/LibraryLWJGLOpenALMixin.java
deleted file mode 100644
index 48a8bfc..0000000
--- a/src/main/java/pl/tomgirl/lenis/mixin/LibraryLWJGLOpenALMixin.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package pl.tomgirl.lenis.mixin;
-
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
-
-import org.lwjgl.openal.AL;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Unique;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Redirect;
-import paulscode.sound.libraries.LibraryLWJGLOpenAL;
-
-@Mixin(value = LibraryLWJGLOpenAL.class, remap = false)
-public abstract class LibraryLWJGLOpenALMixin {
- @Unique private static MethodHandle alExit = null;
-
- static {
- try {
- //noinspection JavaLangInvokeHandleSignature
- alExit = MethodHandles.lookup().findStatic(AL.class, "exit", MethodType.methodType(void.class));
- } catch (Exception ignored) {}
- }
-
- @Redirect(method = "libraryCompatible", at = @At(value = "INVOKE", target = "Lorg/lwjgl/openal/AL;destroy()V"))
- private static void libraryCompatible() {
- exitAL();
- }
-
- @Redirect(method = "cleanup", at = @At(value = "INVOKE", target = "Lorg/lwjgl/openal/AL;destroy()V"))
- private void cleanup() {
- exitAL();
- }
-
- @Unique
- private static void exitAL() {
- try {
- alExit.invoke();
- } catch (Throwable ignored) {}
- }
-}
diff --git a/src/main/java/pl/tomgirl/lenis/mixin/game/MixinMinecraft_Icon.java b/src/main/java/pl/tomgirl/lenis/mixin/game/MixinMinecraft_Icon.java
deleted file mode 100644
index db5851d..0000000
--- a/src/main/java/pl/tomgirl/lenis/mixin/game/MixinMinecraft_Icon.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package pl.tomgirl.lenis.mixin.game;
-
-import net.minecraft.client.Minecraft;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-import pl.tomgirl.lenis.Lenis;
-import pl.tomgirl.lenis.window.DisplaySdl;
-
-import javax.imageio.ImageIO;
-import java.awt.image.BufferedImage;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-
-@Mixin(Minecraft.class)
-public class MixinMinecraft_Icon {
- @Inject(method = "initIcon", at = @At("HEAD"), cancellable = true)
- private void initIcon(CallbackInfo ci) {
- try (InputStream stream = Lenis.class.getResourceAsStream("/icon_256x.png")) {
- if (stream == null) {
- throw new IllegalStateException("Missing default icon");
- }
-
- BufferedImage image = ImageIO.read(stream);
- if (image == null) {
- throw new IllegalStateException("Unable to decode default icon");
- }
-
- ByteBuffer pixels = ByteBuffer.allocate(image.getWidth() * image.getHeight() * 4);
- for (int y = 0; y < image.getHeight(); y++) {
- for (int x = 0; x < image.getWidth(); x++) {
- int argb = image.getRGB(x, y);
- pixels.put((byte) (argb >> 16));
- pixels.put((byte) (argb >> 8));
- pixels.put((byte) argb);
- pixels.put((byte) (argb >> 24));
- }
- }
- pixels.flip();
- DisplaySdl.instance().setIcon(new ByteBuffer[] { pixels });
- } catch (IOException ex) {
- throw new IllegalStateException("Unable to load default icon", ex);
- }
- ci.cancel();
- }
-}
diff --git a/src/main/java/pl/tomgirl/lenis/mixin/game/MixinMinecraft_Resize.java b/src/main/java/pl/tomgirl/lenis/mixin/game/MixinMinecraft_Resize.java
deleted file mode 100644
index 9a01a07..0000000
--- a/src/main/java/pl/tomgirl/lenis/mixin/game/MixinMinecraft_Resize.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package pl.tomgirl.lenis.mixin.game;
-
-import net.minecraft.client.Minecraft;
-import org.lwjgl.opengl.Display;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Shadow;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-
-@Mixin(Minecraft.class)
-public abstract class MixinMinecraft_Resize {
- @Shadow public int width;
- @Shadow public int height;
- @Shadow protected abstract void resize(int width, int height);
-
- @Inject(method = "init", at = @At("TAIL"))
- private void init(CallbackInfo ci) {
- this.width = Math.max(1, Display.getWidth());
- this.height = Math.max(1, Display.getHeight());
- this.resize(this.width, this.height);
- }
-}
diff --git a/src/main/java/pl/tomgirl/lenis/mixin/game/MixinMinecraft_TextInput.java b/src/main/java/pl/tomgirl/lenis/mixin/game/MixinMinecraft_TextInput.java
deleted file mode 100644
index ad6f401..0000000
--- a/src/main/java/pl/tomgirl/lenis/mixin/game/MixinMinecraft_TextInput.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package pl.tomgirl.lenis.mixin.game;
-
-import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.screen.Screen;
-import pl.tomgirl.lenis.window.DisplaySdl;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-
-@Mixin(Minecraft.class)
-public abstract class MixinMinecraft_TextInput {
- @Inject(method = "openScreen", at = @At("TAIL"))
- private void openScreen(Screen screen, CallbackInfo ci) {
- DisplaySdl.instance().setTextInputActive(screen != null);
- }
-}
diff --git a/src/main/java/pl/tomgirl/lenis/mixin/game/MixinScreen_Clipboard.java b/src/main/java/pl/tomgirl/lenis/mixin/game/MixinScreen_Clipboard.java
deleted file mode 100644
index 79d9b89..0000000
--- a/src/main/java/pl/tomgirl/lenis/mixin/game/MixinScreen_Clipboard.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package pl.tomgirl.lenis.mixin.game;
-
-import net.minecraft.client.gui.screen.Screen;
-import org.lwjgl.sdl.SDLClipboard;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
-
-import java.util.Objects;
-
-@SuppressWarnings({"unused"})
-@Mixin(Screen.class)
-public abstract class MixinScreen_Clipboard {
- @Inject(at = @At("HEAD"), method = "getClipboard", cancellable = true)
- private static void getClipboard(CallbackInfoReturnable cir) {
- cir.setReturnValue(Objects.requireNonNullElse(SDLClipboard.SDL_GetClipboardText(), ""));
- }
-
- @Inject(at = @At("HEAD"), method = "setClipboard", cancellable = true)
- private static void setClipboard(String text, CallbackInfo ci) {
- SDLClipboard.SDL_SetClipboardText(text);
- ci.cancel();
- }
-}
diff --git a/src/main/java/pl/tomgirl/lenis/mixin/game/MixinTextFieldWidget_Ime.java b/src/main/java/pl/tomgirl/lenis/mixin/game/MixinTextFieldWidget_Ime.java
deleted file mode 100644
index 388bf39..0000000
--- a/src/main/java/pl/tomgirl/lenis/mixin/game/MixinTextFieldWidget_Ime.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package pl.tomgirl.lenis.mixin.game;
-
-import net.minecraft.client.Minecraft;
-import net.minecraft.client.gui.widget.TextFieldWidget;
-import net.minecraft.client.render.TextRenderer;
-import org.spongepowered.asm.mixin.Final;
-import org.spongepowered.asm.mixin.Mixin;
-import org.spongepowered.asm.mixin.Shadow;
-import org.spongepowered.asm.mixin.injection.At;
-import org.spongepowered.asm.mixin.injection.Inject;
-import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
-import pl.tomgirl.lenis.window.DisplaySdl;
-
-@Mixin(TextFieldWidget.class)
-public abstract class MixinTextFieldWidget_Ime {
- @Shadow public int x;
- @Shadow public int y;
- @Shadow @Final private int height;
- @Shadow @Final private TextRenderer textRenderer;
- @Shadow private String text;
- @Shadow private boolean hasBorder;
- @Shadow private boolean focused;
- @Shadow private boolean editable;
- @Shadow private boolean visible;
- @Shadow private int firstCharacterIndex;
- @Shadow private int selectionStart;
- @Shadow public abstract int getInnerWidth();
-
- @Inject(method = "render", at = @At("HEAD"))
- private void updateTextInputArea(CallbackInfo ci) {
- Minecraft minecraft = Minecraft.getInstance();
- if (!focused || !editable || !visible || minecraft.screen == null) {
- return;
- }
-
- int lineX = hasBorder ? x + 4 : x;
- int lineY = hasBorder ? y + (height - 8) / 2 : y;
- int cursor = textRenderer.getWidth(text.substring(firstCharacterIndex, Math.max(firstCharacterIndex, selectionStart)));
- DisplaySdl.instance().setTextInputArea(
- lineX, lineY, getInnerWidth(), textRenderer.fontHeight,
- Math.min(cursor, getInnerWidth()), minecraft.screen.width, minecraft.screen.height
- );
- }
-}
diff --git a/src/main/java/pl/tomgirl/lenis/plugin/CompatibilityStubBuilder.java b/src/main/java/pl/tomgirl/lenis/plugin/CompatibilityStubBuilder.java
deleted file mode 100644
index 952e174..0000000
--- a/src/main/java/pl/tomgirl/lenis/plugin/CompatibilityStubBuilder.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package pl.tomgirl.lenis.plugin;
-
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-import org.objectweb.asm.tree.AnnotationNode;
-import org.objectweb.asm.tree.ClassNode;
-import org.objectweb.asm.tree.InsnNode;
-import org.objectweb.asm.tree.MethodInsnNode;
-import org.objectweb.asm.tree.MethodNode;
-import org.objectweb.asm.tree.VarInsnNode;
-import org.spongepowered.asm.util.Annotations;
-
-final class CompatibilityStubBuilder {
- private CompatibilityStubBuilder() {
- }
-
- static void build(ClassNode targetClass) {
- for (MethodNode method : targetClass.methods) {
- AnnotationNode annotation = Annotations.getInvisible(method, CompatStub.class);
- if (annotation == null) {
- continue;
- }
- if ((method.access & Opcodes.ACC_STATIC) == 0) {
- throw new IllegalStateException("CompatStub must be static: " + targetClass.name + "::" + method.name + method.desc);
- }
- method.access &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
- method.access |= Opcodes.ACC_PUBLIC;
-
- String delegate = Annotations.getValue(annotation, "value", "");
- if (!delegate.isEmpty()) {
- buildDelegate(targetClass, method, delegate);
- }
- }
- }
-
- private static void buildDelegate(ClassNode targetClass, MethodNode stub, String delegate) {
- MethodNode target = targetClass.methods.stream()
- .filter(method -> method.name.equals(delegate) && method.desc.equals(stub.desc))
- .findFirst()
- .orElseThrow(() -> new IllegalStateException(
- "CompatStub target does not exist: " + targetClass.name + "::" + delegate + stub.desc
- ));
- if ((target.access & Opcodes.ACC_STATIC) == 0) {
- throw new IllegalStateException("CompatStub target must be static: " + targetClass.name + "::" + delegate + stub.desc);
- }
-
- stub.instructions.clear();
- stub.tryCatchBlocks.clear();
- if (stub.localVariables != null) {
- stub.localVariables.clear();
- }
-
- int local = 0;
- int stack = 0;
- for (Type argument : Type.getArgumentTypes(stub.desc)) {
- stub.instructions.add(new VarInsnNode(argument.getOpcode(Opcodes.ILOAD), local));
- local += argument.getSize();
- stack += argument.getSize();
- }
- stub.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, targetClass.name, delegate, stub.desc, false));
- stub.instructions.add(new InsnNode(Type.getReturnType(stub.desc).getOpcode(Opcodes.IRETURN)));
- stub.maxLocals = local;
- stub.maxStack = Math.max(stack, Type.getReturnType(stub.desc).getSize());
- }
-}
diff --git a/src/main/java/pl/tomgirl/lenis/plugin/PaulscodeClasspathPlugin.java b/src/main/java/pl/tomgirl/lenis/plugin/PaulscodeClasspathPlugin.java
deleted file mode 100644
index 50471ae..0000000
--- a/src/main/java/pl/tomgirl/lenis/plugin/PaulscodeClasspathPlugin.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package pl.tomgirl.lenis.plugin;
-
-import java.nio.file.Path;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-
-import net.fabricmc.loader.impl.launch.FabricLauncherBase;
-import org.objectweb.asm.tree.ClassNode;
-import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
-import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
-
-public final class PaulscodeClasspathPlugin implements IMixinConfigPlugin {
- @Override
- public void onLoad(String mixinPackage) {
- findPaulscodePath().ifPresent(FabricLauncherBase.getLauncher()::addToClassPath);
- }
-
- private static Optional findPaulscodePath() {
- return FabricLauncherBase.getLauncher().getClassPath().stream()
- .filter(path -> path.toString().contains("librarylwjglopenal"))
- .findFirst();
- }
-
- @Override
- public String getRefMapperConfig() {
- return null;
- }
-
- @Override
- public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
- return true;
- }
-
- @Override
- public void acceptTargets(Set myTargets, Set otherTargets) {
- }
-
- @Override
- public List getMixins() {
- return List.of();
- }
-
- @Override
- public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
- }
-
- @Override
- public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) {
- CompatibilityStubBuilder.build(targetClass);
- }
-}
diff --git a/src/main/java/pl/tomgirl/pylon/Agent.java b/src/main/java/pl/tomgirl/pylon/Agent.java
new file mode 100644
index 0000000..effa260
--- /dev/null
+++ b/src/main/java/pl/tomgirl/pylon/Agent.java
@@ -0,0 +1,40 @@
+package pl.tomgirl.pylon;
+
+import java.io.File;
+import java.lang.instrument.Instrumentation;
+import java.util.logging.Level;
+
+public final class Agent {
+ private Agent() {}
+
+ public static void premain(String arguments, Instrumentation instrumentation) {
+ if (injectFabric()) {
+ Pylon.LOG.log(Level.INFO, "Injected Fabric mod");
+ return;
+ }
+
+ instrumentation.addTransformer(new pl.tomgirl.pylon.game.GameTransformer());
+ }
+
+ public static boolean injectFabric() {
+ try {
+ Class.forName("net.fabricmc.loader.api.FabricLoader");
+
+ File agentFile = new File(Agent.class.getProtectionDomain().getCodeSource().getLocation().toURI());
+
+ String addMods = System.getProperty("fabric.addMods", "");
+ if (!"".equals(addMods)) {
+ addMods += File.pathSeparator;
+ }
+ addMods += agentFile.getAbsolutePath();
+ System.setProperty("fabric.addMods", addMods);
+
+ return true;
+ } catch (Exception e) {
+ if (!(e instanceof ClassNotFoundException)) {
+ Pylon.LOG.log(Level.SEVERE, "Error injecting into Fabric");
+ }
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/pl/tomgirl/lenis/Platform.java b/src/main/java/pl/tomgirl/pylon/Platform.java
similarity index 89%
rename from src/main/java/pl/tomgirl/lenis/Platform.java
rename to src/main/java/pl/tomgirl/pylon/Platform.java
index cef1220..8bbcce9 100644
--- a/src/main/java/pl/tomgirl/lenis/Platform.java
+++ b/src/main/java/pl/tomgirl/pylon/Platform.java
@@ -1,8 +1,9 @@
-package pl.tomgirl.lenis;
+package pl.tomgirl.pylon;
import java.io.IOException;
import java.net.URI;
import java.util.Locale;
+import java.util.logging.Level;
public enum Platform {
UNIX(),
@@ -29,13 +30,13 @@ public void open(String uri) { // TODO: Validate URI
process.getErrorStream().close();
process.getOutputStream().close();
} catch (IOException e) {
- Lenis.LOG.error("Couldn't open uri '{}'", uri, e);
+ Pylon.LOG.log(Level.SEVERE, "Could not open uri " + uri, e);
}
}
protected String[] getProcessArguments(String uri) {
try {
- var parsed = new URI(uri);
+ URI parsed = new URI(uri);
if ("file".equals(parsed.getScheme())) {
uri = uri.replace("file:", "file://");
}
diff --git a/src/main/java/pl/tomgirl/pylon/Pylon.java b/src/main/java/pl/tomgirl/pylon/Pylon.java
new file mode 100644
index 0000000..72396bf
--- /dev/null
+++ b/src/main/java/pl/tomgirl/pylon/Pylon.java
@@ -0,0 +1,9 @@
+package pl.tomgirl.pylon;
+
+import java.util.logging.Logger;
+
+public final class Pylon {
+ public static final Logger LOG = Logger.getLogger("Pylon");
+
+ private Pylon() {}
+}
diff --git a/src/main/java/pl/tomgirl/pylon/game/GameHooks.java b/src/main/java/pl/tomgirl/pylon/game/GameHooks.java
new file mode 100644
index 0000000..c038772
--- /dev/null
+++ b/src/main/java/pl/tomgirl/pylon/game/GameHooks.java
@@ -0,0 +1,54 @@
+package pl.tomgirl.pylon.game;
+
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import pl.tomgirl.pylon.window.MainThread;
+import java.nio.ByteBuffer;
+
+import javax.imageio.ImageIO;
+
+import org.lwjgl.sdl.SDLClipboard;
+
+import pl.tomgirl.pylon.Pylon;
+import pl.tomgirl.pylon.window.DisplaySdl;
+
+@SuppressWarnings("unused")
+public final class GameHooks {
+ public static volatile boolean screenPatchAvailable;
+
+ private GameHooks() {}
+
+ public static String getClipboard() {
+ String text = MainThread.call(SDLClipboard::SDL_GetClipboardText);
+ return text == null ? "" : text;
+ }
+
+ public static void setClipboard(String text) {
+ MainThread.run(() -> SDLClipboard.SDL_SetClipboardText(text));
+ }
+
+ public static void setIcon() {
+ try (InputStream stream = Pylon.class.getResourceAsStream("/icon_256x.png")) {
+ if (stream == null) throw new IllegalStateException("Missing default icon");
+ BufferedImage image = ImageIO.read(stream);
+ if (image == null) throw new IllegalStateException("Unable to decode default icon");
+ ByteBuffer pixels = ByteBuffer.allocate(image.getWidth() * image.getHeight() * 4);
+ for (int y = 0; y < image.getHeight(); y++) {
+ for (int x = 0; x < image.getWidth(); x++) {
+ int argb = image.getRGB(x, y);
+ pixels.put((byte) (argb >> 16)).put((byte) (argb >> 8));
+ pixels.put((byte) argb).put((byte) (argb >> 24));
+ }
+ }
+ pixels.flip();
+ DisplaySdl.instance().setIcon(new ByteBuffer[] { pixels });
+ } catch (IOException ex) {
+ throw new IllegalStateException("Unable to load default icon", ex);
+ }
+ }
+
+ public static void screenOpened(Object screen) {
+ DisplaySdl.instance().setTextInputActive(screen != null);
+ }
+}
diff --git a/src/main/java/pl/tomgirl/pylon/game/GameTransformer.java b/src/main/java/pl/tomgirl/pylon/game/GameTransformer.java
new file mode 100644
index 0000000..51bd13f
--- /dev/null
+++ b/src/main/java/pl/tomgirl/pylon/game/GameTransformer.java
@@ -0,0 +1,43 @@
+package pl.tomgirl.pylon.game;
+
+import java.lang.instrument.ClassFileTransformer;
+import java.security.ProtectionDomain;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.logging.Level;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import pl.tomgirl.pylon.Pylon;
+import pl.tomgirl.pylon.game.patch.Patch;
+import pl.tomgirl.pylon.game.patch.impl.*;
+
+public final class GameTransformer implements ClassFileTransformer {
+ @Override
+ public byte[] transform(ClassLoader loader, String name, Class> type, ProtectionDomain domain, byte[] bytecode) {
+ return transform(bytecode);
+ }
+
+ public static byte[] transform(byte[] bytecode) {
+ FmlModContainerPatch fml = new FmlModContainerPatch(null);
+ ScreenPatch screen = new ScreenPatch(fml);
+ MinecraftPatch minecraft = new MinecraftPatch(screen);
+ ClassReader reader = new ClassReader(bytecode);
+ reader.accept(minecraft, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+
+ List matches = Stream.of(minecraft, screen, fml).filter(Patch::matches).collect(Collectors.toList());
+ if (matches.isEmpty()) return bytecode;
+
+ ClassWriter writer = new ClassWriter(reader, 0);
+ ClassVisitor patcher = writer;
+ for (Patch patch : matches) {
+ patch.applied();
+ patcher = patch.apply(patcher);
+ }
+ reader.accept(patcher, 0);
+ Pylon.LOG.log(Level.FINE, "Applied {0} to {1}", new Object[]{matches, reader.getClassName()});
+ return writer.toByteArray();
+ }
+}
diff --git a/src/main/java/pl/tomgirl/pylon/game/patch/Patch.java b/src/main/java/pl/tomgirl/pylon/game/patch/Patch.java
new file mode 100644
index 0000000..3c8d310
--- /dev/null
+++ b/src/main/java/pl/tomgirl/pylon/game/patch/Patch.java
@@ -0,0 +1,45 @@
+package pl.tomgirl.pylon.game.patch;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import pl.tomgirl.pylon.game.GameHooks;
+
+public abstract class Patch extends ClassVisitor {
+ public static final String HOOKS = Type.getInternalName(GameHooks.class);
+
+ public Patch(ClassVisitor next) { super(Opcodes.ASM9, next); }
+
+ public abstract boolean matches();
+ public abstract ClassVisitor apply(ClassVisitor writer);
+ public void applied() {}
+
+ @Override
+ public final String toString() { return getClass().getSimpleName(); }
+
+ public static final class Method {
+ private final String name;
+ private final String descriptor;
+
+ public Method(String name, String descriptor) {
+ this.name = name;
+ this.descriptor = descriptor;
+ }
+
+ public String name() { return name; }
+ public String descriptor() { return descriptor; }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof Method)) return false;
+ Method method = (Method) other;
+ return name.equals(method.name) && descriptor.equals(method.descriptor);
+ }
+
+ @Override
+ public int hashCode() { return name.hashCode() * 31 + descriptor.hashCode(); }
+
+ @Override
+ public String toString() { return name + descriptor; }
+ }
+}
diff --git a/src/main/java/pl/tomgirl/pylon/game/patch/impl/FmlModContainerPatch.java b/src/main/java/pl/tomgirl/pylon/game/patch/impl/FmlModContainerPatch.java
new file mode 100644
index 0000000..56f79e0
--- /dev/null
+++ b/src/main/java/pl/tomgirl/pylon/game/patch/impl/FmlModContainerPatch.java
@@ -0,0 +1,71 @@
+package pl.tomgirl.pylon.game.patch.impl;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import pl.tomgirl.pylon.game.patch.Patch;
+
+public class FmlModContainerPatch extends Patch {
+ private String owner;
+ private String container;
+ private Method identifyMods;
+
+ public FmlModContainerPatch(ClassVisitor next) {
+ super(next);
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String parent, String[] interfaces) {
+ owner = name;
+ if (name.equals("net/minecraftforge/fml/common/Loader")) {
+ container = "pl.tomgirl.pylonfml.FmlModContainer";
+ } else if (name.equals("cpw/mods/fml/common/Loader")) {
+ container = "pl.tomgirl.pylonfml.CpwModContainer";
+ }
+ super.visit(version, access, name, signature, parent, interfaces);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
+ if (container != null && name.equals("identifyMods") && (access & Opcodes.ACC_STATIC) == 0) {
+ String discoverer = "L" + owner.substring(0, owner.lastIndexOf('/') + 1) + "discovery/ModDiscoverer;";
+ if (descriptor.equals("()" + discoverer) || descriptor.equals("(Ljava/util/List;)" + discoverer)) {
+ identifyMods = new Method(name, descriptor);
+ }
+ }
+ return super.visitMethod(access, name, descriptor, signature, exceptions);
+ }
+
+ @Override
+ public boolean matches() {
+ return identifyMods != null;
+ }
+
+ @Override
+ public ClassVisitor apply(ClassVisitor writer) {
+ return new ClassVisitor(Opcodes.ASM9, writer) {
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
+ MethodVisitor method = super.visitMethod(access, name, descriptor, signature, exceptions);
+ if (!new Method(name, descriptor).equals(identifyMods)) {
+ return method;
+ }
+ return new MethodVisitor(Opcodes.ASM9, method) {
+ @Override
+ public void visitCode() {
+ super.visitCode();
+ super.visitFieldInsn(Opcodes.GETSTATIC, owner, "injectedContainers", "Ljava/util/List;");
+ super.visitLdcInsn(container);
+ super.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true);
+ super.visitInsn(Opcodes.POP);
+ }
+
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ super.visitMaxs(Math.max(maxStack, 2), maxLocals);
+ }
+ };
+ }
+ };
+ }
+}
diff --git a/src/main/java/pl/tomgirl/pylon/game/patch/impl/MinecraftPatch.java b/src/main/java/pl/tomgirl/pylon/game/patch/impl/MinecraftPatch.java
new file mode 100644
index 0000000..f13a894
--- /dev/null
+++ b/src/main/java/pl/tomgirl/pylon/game/patch/impl/MinecraftPatch.java
@@ -0,0 +1,123 @@
+package pl.tomgirl.pylon.game.patch.impl;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import pl.tomgirl.pylon.game.GameHooks;
+import pl.tomgirl.pylon.game.patch.Patch;
+
+public final class MinecraftPatch extends Patch {
+ private static final String DISPLAY = "org/lwjgl/opengl/Display";
+ private final List screenCandidates = new ArrayList<>();
+ private String owner;
+ private Method openScreen;
+ private boolean displayCreate;
+ private boolean displayTitle;
+
+ public MinecraftPatch(ClassVisitor next) { super(next); }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String parent, String[] interfaces) {
+ owner = name;
+ super.visit(version, access, name, signature, parent, interfaces);
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
+ MethodVisitor next = super.visitMethod(access, name, descriptor, signature, exceptions);
+ Type[] arguments = Type.getArgumentTypes(descriptor);
+ boolean candidate = (access & Opcodes.ACC_STATIC) == 0
+ && Type.getReturnType(descriptor).equals(Type.VOID_TYPE)
+ && arguments.length == 1 && arguments[0].getSort() == Type.OBJECT;
+
+ return new MethodVisitor(Opcodes.ASM9, next) {
+ private final Set reads = new HashSet<>();
+ private final Set writes = new HashSet<>();
+
+ @Override
+ public void visitFieldInsn(int opcode, String fieldOwner, String field, String fieldDescriptor) {
+ if (candidate && fieldOwner.equals(owner) && fieldDescriptor.equals(arguments[0].getDescriptor())) {
+ if (opcode == Opcodes.GETFIELD) reads.add(field);
+ if (opcode == Opcodes.PUTFIELD) writes.add(field);
+ }
+ super.visitFieldInsn(opcode, fieldOwner, field, fieldDescriptor);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String calledOwner, String called, String calledDescriptor, boolean itf) {
+ if (calledOwner.equals(DISPLAY)) {
+ displayCreate |= called.equals("create") && calledDescriptor.endsWith(")V");
+ displayTitle |= called.equals("setTitle") && calledDescriptor.equals("(Ljava/lang/String;)V");
+ }
+ super.visitMethodInsn(opcode, calledOwner, called, calledDescriptor, itf);
+ }
+
+ @Override
+ public void visitEnd() {
+ if (writes.stream().anyMatch(reads::contains)) {
+ screenCandidates.add(new Method(name, descriptor));
+ }
+ super.visitEnd();
+ }
+ };
+ }
+
+ @Override
+ public boolean matches() {
+ if (!displayCreate || !displayTitle) {
+ return false;
+ }
+
+ if (screenCandidates.size() == 1) {
+ openScreen = screenCandidates.get(0);
+ }
+
+ return true;
+ }
+
+ @Override
+ public void applied() {
+ if (openScreen != null) {
+ GameHooks.screenPatchAvailable = true;
+ }
+ }
+
+ @Override
+ public ClassVisitor apply(ClassVisitor writer) {
+ return new ClassVisitor(Opcodes.ASM9, writer) {
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
+ MethodVisitor method = super.visitMethod(access, name, descriptor, signature, exceptions);
+ boolean patchesScreen = new Method(name, descriptor).equals(openScreen);
+ return new MethodVisitor(Opcodes.ASM9, method) {
+ @Override
+ public void visitMethodInsn(int opcode, String calledOwner, String called, String calledDescriptor, boolean itf) {
+ super.visitMethodInsn(opcode, calledOwner, called, calledDescriptor, itf);
+ if (calledOwner.equals(DISPLAY) && called.equals("create") && calledDescriptor.endsWith(")V")) {
+ super.visitMethodInsn(Opcodes.INVOKESTATIC, HOOKS, "setIcon", "()V", false);
+ }
+ }
+
+ @Override
+ public void visitInsn(int opcode) {
+ if (patchesScreen && opcode == Opcodes.RETURN) {
+ super.visitVarInsn(Opcodes.ALOAD, 1);
+ super.visitMethodInsn(Opcodes.INVOKESTATIC, HOOKS, "screenOpened", "(Ljava/lang/Object;)V", false);
+ }
+ super.visitInsn(opcode);
+ }
+
+ @Override
+ public void visitMaxs(int maxStack, int maxLocals) {
+ super.visitMaxs(Math.max(maxStack, 1), maxLocals);
+ }
+ };
+ }
+ };
+ }
+}
diff --git a/src/main/java/pl/tomgirl/pylon/game/patch/impl/ScreenPatch.java b/src/main/java/pl/tomgirl/pylon/game/patch/impl/ScreenPatch.java
new file mode 100644
index 0000000..a997655
--- /dev/null
+++ b/src/main/java/pl/tomgirl/pylon/game/patch/impl/ScreenPatch.java
@@ -0,0 +1,72 @@
+package pl.tomgirl.pylon.game.patch.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import pl.tomgirl.pylon.game.patch.Patch;
+
+public final class ScreenPatch extends Patch {
+ private static final String CLIPBOARD = "java/awt/datatransfer/Clipboard";
+ private final Map methods = new HashMap<>();
+
+ public ScreenPatch(ClassVisitor next) { super(next); }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
+ MethodVisitor next = super.visitMethod(access, name, descriptor, signature, exceptions);
+ return new MethodVisitor(Opcodes.ASM9, next) {
+ private boolean gets;
+ private boolean sets;
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String called, String calledDescriptor, boolean itf) {
+ if (owner.equals(CLIPBOARD)) {
+ gets |= called.equals("getContents");
+ sets |= called.equals("setContents");
+ }
+ super.visitMethodInsn(opcode, owner, called, calledDescriptor, itf);
+ }
+
+ @Override
+ public void visitEnd() {
+ if (gets && descriptor.equals("()Ljava/lang/String;")) methods.put(new Method(name, descriptor), ClipboardMethod.GET);
+ if (sets && descriptor.equals("(Ljava/lang/String;)V")) methods.put(new Method(name, descriptor), ClipboardMethod.SET);
+ super.visitEnd();
+ }
+ };
+ }
+
+ @Override
+ public boolean matches() {
+ return !methods.isEmpty();
+ }
+
+ @Override
+ public ClassVisitor apply(ClassVisitor writer) {
+ return new ClassVisitor(Opcodes.ASM9, writer) {
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
+ ClipboardMethod patch = methods.get(new Method(name, descriptor));
+ if (patch == null) return super.visitMethod(access, name, descriptor, signature, exceptions);
+ MethodVisitor method = super.visitMethod(access, name, descriptor, signature, exceptions);
+ int argumentSlot = (access & Opcodes.ACC_STATIC) != 0 ? 0 : 1;
+ method.visitCode();
+ if (patch == ClipboardMethod.GET) {
+ method.visitMethodInsn(Opcodes.INVOKESTATIC, HOOKS, "getClipboard", "()Ljava/lang/String;", false);
+ method.visitInsn(Opcodes.ARETURN);
+ } else {
+ method.visitVarInsn(Opcodes.ALOAD, argumentSlot);
+ method.visitMethodInsn(Opcodes.INVOKESTATIC, HOOKS, "setClipboard", "(Ljava/lang/String;)V", false);
+ method.visitInsn(Opcodes.RETURN);
+ }
+ method.visitMaxs(1, argumentSlot + (patch == ClipboardMethod.GET ? 0 : 1));
+ method.visitEnd();
+ return null;
+ }
+ };
+ }
+
+ private enum ClipboardMethod { GET, SET }
+}
diff --git a/src/main/java/pl/tomgirl/lenis/window/BoundedQueue.java b/src/main/java/pl/tomgirl/pylon/window/BoundedQueue.java
similarity index 91%
rename from src/main/java/pl/tomgirl/lenis/window/BoundedQueue.java
rename to src/main/java/pl/tomgirl/pylon/window/BoundedQueue.java
index d5f44f2..b2b47d5 100644
--- a/src/main/java/pl/tomgirl/lenis/window/BoundedQueue.java
+++ b/src/main/java/pl/tomgirl/pylon/window/BoundedQueue.java
@@ -1,4 +1,4 @@
-package pl.tomgirl.lenis.window;
+package pl.tomgirl.pylon.window;
import java.util.function.Supplier;
@@ -41,7 +41,7 @@ E peekLast() {
E get(int index) {
if (index < 0 || index >= size) {
- throw new IndexOutOfBoundsException(index);
+ throw new IndexOutOfBoundsException(String.valueOf(index));
}
return entryAt(index);
}
diff --git a/src/main/java/pl/tomgirl/lenis/window/DisplaySdl.java b/src/main/java/pl/tomgirl/pylon/window/DisplaySdl.java
similarity index 74%
rename from src/main/java/pl/tomgirl/lenis/window/DisplaySdl.java
rename to src/main/java/pl/tomgirl/pylon/window/DisplaySdl.java
index f0757b7..376cc87 100644
--- a/src/main/java/pl/tomgirl/lenis/window/DisplaySdl.java
+++ b/src/main/java/pl/tomgirl/pylon/window/DisplaySdl.java
@@ -1,15 +1,15 @@
-package pl.tomgirl.lenis.window;
+package pl.tomgirl.pylon.window;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import java.util.logging.Level;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.Drawable;
-import pl.tomgirl.lenis.Lenis;
-import net.fabricmc.loader.api.FabricLoader;
+import pl.tomgirl.pylon.Pylon;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.LWJGLException;
import org.lwjgl.PointerBuffer;
@@ -33,6 +33,7 @@
public class DisplaySdl {
private static final DisplaySdl INSTANCE = new DisplaySdl();
+ private boolean videoInitialized;
private SDL_Event event;
private SDL_WindowEvent windowEvent;
private final Drawable drawable = new SurfaceDrawable();
@@ -60,7 +61,7 @@ public class DisplaySdl {
private boolean focused;
private boolean closeRequested;
- private boolean textInputRequested;
+ private boolean textInputRequested = true;
private boolean textInputActive;
private int textInputX = -1;
private int textInputY;
@@ -102,7 +103,7 @@ public Drawable getDrawable() {
public void setTextInputActive(boolean active) {
if (handle > 0 && textInputActive) {
- checkSdlError(SDLKeyboard.SDL_ClearComposition(handle));
+ MainThread.run(() -> checkSdlError(SDLKeyboard.SDL_ClearComposition(handle)));
}
textInputX = -1;
textInputRequested = active;
@@ -124,11 +125,13 @@ public void setTextInputArea(int x, int y, int width, int height, int cursor, in
) {
return;
}
- try (MemoryStack stack = stackPush()) {
- SDL_Rect.Buffer area = SDL_Rect.calloc(1, stack)
- .x(areaX).y(areaY).w(areaWidth).h(areaHeight);
- checkSdlError(SDLKeyboard.SDL_SetTextInputArea(handle, area, areaCursor));
- }
+ MainThread.run(() -> {
+ try (MemoryStack stack = stackPush()) {
+ SDL_Rect.Buffer area = SDL_Rect.calloc(1, stack)
+ .x(areaX).y(areaY).w(areaWidth).h(areaHeight);
+ checkSdlError(SDLKeyboard.SDL_SetTextInputArea(handle, area, areaCursor));
+ }
+ });
textInputX = areaX;
textInputY = areaY;
textInputWidth = areaWidth;
@@ -141,11 +144,13 @@ private void updateTextInputState() {
return;
}
- if (textInputRequested) {
- checkSdlError(SDLKeyboard.SDL_StartTextInput(handle));
- } else {
- SDLKeyboard.SDL_StopTextInput(handle);
- }
+ MainThread.run(() -> {
+ if (textInputRequested) {
+ checkSdlError(SDLKeyboard.SDL_StartTextInput(handle));
+ } else {
+ SDLKeyboard.SDL_StopTextInput(handle);
+ }
+ });
textInputActive = textInputRequested;
textInputX = -1;
}
@@ -158,7 +163,7 @@ public String getTitle() {
public void setTitle(@NotNull String title) {
this.title = title;
if (isCreated()) {
- SDL_SetWindowTitle(handle, title);
+ MainThread.run(() -> SDL_SetWindowTitle(handle, title));
}
}
@@ -215,7 +220,7 @@ public void setWindowHint(String hint, String value) {
@SuppressWarnings("resource")
public DisplayMode getDesktopDisplayMode() {
- var mode = SDL_GetDesktopDisplayMode(SDL_GetPrimaryDisplay());
+ SDL_DisplayMode mode = SDL_GetDesktopDisplayMode(SDL_GetPrimaryDisplay());
if (mode == null) {
DisplayMode best = null;
for (DisplayMode displayMode : getAvailableDisplayModes()) {
@@ -259,14 +264,17 @@ public int setIcon(@NotNull ByteBuffer[] icons) {
ByteBuffer pixels = MemoryUtil.memAlloc(icon.remaining());
try {
pixels.put(icon.duplicate()).flip();
- try (var surface = SDLSurface.SDL_CreateSurfaceFrom(
- size, size, SDLPixels.SDL_PIXELFORMAT_RGBA32, pixels, size * 4
- )) {
- checkSdlError(surface != null);
- checkSdlError(SDL_SetWindowIcon(handle, surface));
- } catch (Exception e) {
- Lenis.LOG.error("Failed to set window icon", e);
- }
+ int iconSize = size;
+ MainThread.run(() -> {
+ try (SDL_Surface surface = SDLSurface.SDL_CreateSurfaceFrom(
+ iconSize, iconSize, SDLPixels.SDL_PIXELFORMAT_RGBA32, pixels, iconSize * 4
+ )) {
+ checkSdlError(surface != null);
+ checkSdlError(SDL_SetWindowIcon(handle, surface));
+ } catch (Exception e) {
+ Pylon.LOG.log(Level.SEVERE, "Failed to set window icon", e);
+ }
+ });
} finally {
MemoryUtil.memFree(pixels);
}
@@ -288,12 +296,27 @@ public void update() {
}
public void processMessages() {
+ if (!MainThread.DISPATCH && !SDL_IsMainThread()) {
+ return;
+ }
+
windowResized = false;
+ MainThread.run(this::pollEvents);
+ Keyboard.poll();
+ Mouse.poll();
+ }
+
+ private void pollEvents() {
while (SDL_PollEvent(event)) {
switch (event.type()) {
- case SDL_EVENT_QUIT, SDL_EVENT_WINDOW_CLOSE_REQUESTED -> closeRequested = true;
- case SDL_EVENT_WINDOW_FOCUS_GAINED -> focused = true;
- case SDL_EVENT_WINDOW_FOCUS_LOST -> {
+ case SDL_EVENT_QUIT:
+ case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
+ closeRequested = true;
+ break;
+ case SDL_EVENT_WINDOW_FOCUS_GAINED:
+ focused = true;
+ break;
+ case SDL_EVENT_WINDOW_FOCUS_LOST: {
focused = false;
long nanos = windowEvent.timestamp();
if (Keyboard.isCreated()) {
@@ -302,36 +325,52 @@ public void processMessages() {
if (Mouse.isCreated()) {
MouseSdl.instance().releaseAll(nanos);
}
+ break;
}
- case SDL_EVENT_WINDOW_SHOWN, SDL_EVENT_WINDOW_RESTORED, SDL_EVENT_WINDOW_MAXIMIZED -> minimized = false;
- case SDL_EVENT_WINDOW_HIDDEN, SDL_EVENT_WINDOW_MINIMIZED -> minimized = true;
- case SDL_EVENT_WINDOW_RESIZED -> resizeCallback(handle, windowEvent.data1(), windowEvent.data2());
- case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED ->
- onFramebufferResize(handle, windowEvent.data1(), windowEvent.data2());
- case SDL_EVENT_WINDOW_ENTER_FULLSCREEN -> {
+ case SDL_EVENT_WINDOW_SHOWN:
+ case SDL_EVENT_WINDOW_RESTORED:
+ case SDL_EVENT_WINDOW_MAXIMIZED:
+ minimized = false;
+ break;
+ case SDL_EVENT_WINDOW_HIDDEN:
+ case SDL_EVENT_WINDOW_MINIMIZED:
+ minimized = true;
+ break;
+ case SDL_EVENT_WINDOW_RESIZED:
+ resizeCallback(handle, windowEvent.data1(), windowEvent.data2());
+ break;
+ case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
+ onFramebufferResize(handle, windowEvent.data1(), windowEvent.data2());
+ break;
+ case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
fullscreen = true;
windowResized = true;
- }
- case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN -> {
+ break;
+ case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
fullscreen = false;
windowResized = true;
- }
- case SDL_EVENT_KEY_DOWN, SDL_EVENT_KEY_UP, SDL_EVENT_TEXT_INPUT, SDL_EVENT_TEXT_EDITING,
- SDL_EVENT_TEXT_EDITING_CANDIDATES -> {
+ break;
+ case SDL_EVENT_KEY_DOWN:
+ case SDL_EVENT_KEY_UP:
+ case SDL_EVENT_TEXT_INPUT:
+ case SDL_EVENT_TEXT_EDITING:
+ case SDL_EVENT_TEXT_EDITING_CANDIDATES:
if (Keyboard.isCreated()) {
KeyboardSdl.instance().processKeyboardEvent(event);
}
- }
- case SDL_EVENT_MOUSE_BUTTON_DOWN, SDL_EVENT_MOUSE_BUTTON_UP, SDL_EVENT_MOUSE_MOTION,
- SDL_EVENT_MOUSE_WHEEL, SDL_EVENT_WINDOW_MOUSE_ENTER, SDL_EVENT_WINDOW_MOUSE_LEAVE -> {
+ break;
+ case SDL_EVENT_MOUSE_BUTTON_DOWN:
+ case SDL_EVENT_MOUSE_BUTTON_UP:
+ case SDL_EVENT_MOUSE_MOTION:
+ case SDL_EVENT_MOUSE_WHEEL:
+ case SDL_EVENT_WINDOW_MOUSE_ENTER:
+ case SDL_EVENT_WINDOW_MOUSE_LEAVE:
if (Mouse.isCreated()) {
MouseSdl.instance().processMouseEvent(event);
}
- }
+ break;
}
}
- Keyboard.poll();
- Mouse.poll();
}
private static void checkSdlError(boolean success) {
@@ -340,11 +379,16 @@ private static void checkSdlError(boolean success) {
}
}
- public void create(@NotNull GpuSurface fallbackSurface) throws LWJGLException {
- if (isCreated()) {
- throw new IllegalStateException("Display has already been created");
+ public synchronized void initializeVideo() {
+ if (videoInitialized) {
+ return;
}
+ MainThread.run(this::initSdlVideo);
+ videoInitialized = true;
+ }
+
+ private void initSdlVideo() {
SDL_SetMemoryFunctions(
MemoryUtil::nmemAllocChecked,
MemoryUtil::nmemCallocChecked,
@@ -356,20 +400,23 @@ public void create(@NotNull GpuSurface fallbackSurface) throws LWJGLException {
checkSdlError(SDLHints.SDL_SetHint(SDLHints.SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"));
hints.forEach((k, v) -> checkSdlError(SDLHints.SDL_SetHint(k, v)));
- checkSdlError(SDL_SetAppMetadata(
- "Minecraft",
- FabricLoader.getInstance().getModContainer("minecraft").orElseThrow(IllegalStateException::new)
- .getMetadata().getVersion().getFriendlyString(),
- "com.mojang.minecraft"
- ));
+ checkSdlError(SDL_SetAppMetadata("Minecraft", null, "com.mojang.minecraft"));
checkSdlError(SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_URL_STRING, "https://minecraft.net"));
checkSdlError(SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_CREATOR_STRING, "Mojang AB"));
checkSdlError(SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_COPYRIGHT_STRING, "Minecraft EULA: https://minecraft.net/eula"));
checkSdlError(SDL_SetAppMetadataProperty(SDL_PROP_APP_METADATA_TYPE_STRING, "game"));
if (!SDL_Init(SDL_INIT_VIDEO)) {
- throw new IllegalStateException("Unable to initialize SDL" + SDL_GetError());
+ throw new IllegalStateException("Unable to initialize SDL: " + SDL_GetError());
}
+ }
+
+ public void create(@NotNull GpuSurface fallbackSurface) throws LWJGLException {
+ if (isCreated()) {
+ throw new IllegalStateException("Display has already been created");
+ }
+
+ initializeVideo();
GpuSurface surface = this.surface != null ? this.surface : fallbackSurface;
this.surface = surface;
@@ -384,21 +431,27 @@ public void create(@NotNull GpuSurface fallbackSurface) throws LWJGLException {
throw throwable;
}
- try (MemoryStack ms = stackPush()) {
- setFullscreen(fullscreenDeferred);
-
- IntBuffer width = ms.mallocInt(1);
- IntBuffer height = ms.mallocInt(1);
- checkSdlError(SDL_GetWindowSizeInPixels(handle, width, height));
- framebufferWidth = Math.max(1, width.get(0));
- framebufferHeight = Math.max(1, height.get(0));
- }
+ setFullscreen(fullscreenDeferred);
+ MainThread.run(() -> {
+ try (MemoryStack ms = stackPush()) {
+ IntBuffer width = ms.mallocInt(1);
+ IntBuffer height = ms.mallocInt(1);
+ checkSdlError(SDL_GetWindowSizeInPixels(handle, width, height));
+ framebufferWidth = Math.max(1, width.get(0));
+ framebufferHeight = Math.max(1, height.get(0));
+ }
+ });
Mouse.create();
Keyboard.create();
- checkSdlError(SDL_ShowWindow(handle));
- checkSdlError(SDL_RaiseWindow(handle));
- focused = (SDL_GetWindowFlags(handle) & SDL_WINDOW_INPUT_FOCUS) != 0;
+ MainThread.run(() -> {
+ checkSdlError(SDL_ShowWindow(handle));
+ checkSdlError(SDL_RaiseWindow(handle));
+ if (SDL_IsMainThread()) {
+ SDL_PumpEvents();
+ }
+ focused = (SDL_GetWindowFlags(handle) & SDL_WINDOW_INPUT_FOCUS) != 0;
+ });
updateTextInputState();
if (cachedIcons != null) {
setIcon(cachedIcons);
@@ -423,6 +476,10 @@ private void onFramebufferResize(long window, int framebufferWidth, int framebuf
}
}
+ public boolean isFullscreen() {
+ return this.fullscreen;
+ }
+
public void setFullscreen(boolean fullscreen) {
if (!isCreated()) {
fullscreenDeferred = fullscreen;
@@ -433,7 +490,7 @@ public void setFullscreen(boolean fullscreen) {
if (fullscreen) {
int display = SDL_GetPrimaryDisplay();
if (display == 0) {
- Lenis.LOG.warn("Failed to find display");
+ Pylon.LOG.log(Level.WARNING, "Failed to find display");
return;
}
if (!this.fullscreen) {
@@ -444,11 +501,13 @@ public void setFullscreen(boolean fullscreen) {
width = windowedWidth;
height = windowedHeight;
}
- SDL_SetWindowFullscreen(handle, fullscreen);
- SDL_SetWindowSize(handle, windowedWidth, windowedHeight);
+ MainThread.run(() -> {
+ SDL_SetWindowFullscreen(handle, fullscreen);
+ SDL_SetWindowSize(handle, windowedWidth, windowedHeight);
+ });
windowResized = true;
} catch (Throwable t) {
- Lenis.LOG.warn("Failed to set fullscreen: ", t);
+ Pylon.LOG.log(Level.WARNING, "Failed to set fullscreen: ", t);
}
}
@@ -462,7 +521,7 @@ public DisplayMode[] getAvailableDisplayModes() {
return new DisplayMode[0];
}
- var buf = SDL_GetFullscreenDisplayModes(currDisplay);
+ PointerBuffer buf = SDL_GetFullscreenDisplayModes(currDisplay);
if (buf == null) {
throw new IllegalStateException("No display modes found");
}
@@ -491,15 +550,18 @@ public void destroy() {
}
handle = -1L;
textInputX = -1;
- if (SDL_WasInit(SDL_INIT_VIDEO) != 0) {
- SDL_QuitSubSystem(SDL_INIT_VIDEO);
- }
+ MainThread.run(() -> {
+ if (SDL_WasInit(SDL_INIT_VIDEO) != 0) {
+ SDL_QuitSubSystem(SDL_INIT_VIDEO);
+ }
+ SDL_Quit();
+ });
if (event != null) {
event.free();
event = null;
windowEvent = null;
}
- SDL_Quit();
+ videoInitialized = false;
try (MemoryStack stack = stackPush()) {
PointerBuffer funcs = stack.mallocPointer(4);
@@ -527,7 +589,7 @@ public boolean isActive() {
public void setResizable(boolean isResizable) {
resizable = isResizable;
if (isCreated()) {
- SDL_SetWindowResizable(handle, resizable);
+ MainThread.run(() -> SDL_SetWindowResizable(handle, resizable));
}
}
@@ -569,6 +631,11 @@ private GpuSurface requireSurface() {
}
private final class SurfaceDrawable implements Drawable {
+ @Override
+ public boolean isCurrent() {
+ return false;
+ }
+
@Override
public void makeCurrent() {
DisplaySdl.this.makeCurrent();
diff --git a/src/main/java/pl/tomgirl/lenis/window/GlSurface.java b/src/main/java/pl/tomgirl/pylon/window/GlSurface.java
similarity index 82%
rename from src/main/java/pl/tomgirl/lenis/window/GlSurface.java
rename to src/main/java/pl/tomgirl/pylon/window/GlSurface.java
index 3f094d0..1153e0b 100644
--- a/src/main/java/pl/tomgirl/lenis/window/GlSurface.java
+++ b/src/main/java/pl/tomgirl/pylon/window/GlSurface.java
@@ -1,8 +1,10 @@
-package pl.tomgirl.lenis.window;
+package pl.tomgirl.pylon.window;
import java.nio.ByteBuffer;
import org.lwjgl.opengl.GL;
+import org.lwjgl.opengl.GL11;
+import org.lwjgl.opengl.GLCapabilities;
import org.lwjgl.opengl.PixelFormat;
import org.lwjgl.sdl.SDLPlatform;
import org.lwjgl.sdl.SDLVideo;
@@ -19,7 +21,7 @@ public class GlSurface implements GpuSurface {
private long window;
private long context;
private boolean glInitialized;
- private boolean capabilitiesCreated;
+ private GLCapabilities capabilities;
public GlSurface(PixelFormat pixelFormat) {
this.pixelFormat = pixelFormat;
@@ -31,6 +33,23 @@ public long createWindow(String title, int width, int height, boolean resizable)
throw new IllegalStateException("Surface has already been created");
}
+ MainThread.run(() -> createSdlWindow(title, width, height, resizable));
+
+ try {
+ context = check(SDL_GL_CreateContext(window));
+ check(SDL_GL_LoadLibrary((ByteBuffer) null));
+ Configuration.OPENGL_EXPLICIT_INIT.set(true);
+ GL.create(SDLVideo::SDL_GL_GetProcAddress);
+ glInitialized = true;
+ capabilities = GL.createCapabilities(MemoryUtil::memCallocPointer);
+ return window;
+ } catch (RuntimeException | Error throwable) {
+ destroy();
+ throw throwable;
+ }
+ }
+
+ private void createSdlWindow(String title, int width, int height, boolean resizable) {
int properties = (int) check(SDL_CreateProperties());
try {
check(SDL_SetNumberProperty(properties, SDL_PROP_WINDOW_CREATE_X_NUMBER, SDL_WINDOWPOS_CENTERED));
@@ -48,20 +67,6 @@ public long createWindow(String title, int width, int height, boolean resizable)
} finally {
SDL_DestroyProperties(properties);
}
-
- try {
- context = check(SDL_GL_CreateContext(window));
- check(SDL_GL_LoadLibrary((ByteBuffer) null));
- Configuration.OPENGL_EXPLICIT_INIT.set(true);
- GL.create(SDLVideo::SDL_GL_GetProcAddress);
- glInitialized = true;
- GL.createCapabilities(MemoryUtil::memCallocPointer);
- capabilitiesCreated = true;
- return window;
- } catch (RuntimeException | Error throwable) {
- destroy();
- throw throwable;
- }
}
private void configureContext() {
@@ -79,12 +84,21 @@ private void configureContext() {
@Override
public void makeCurrent() {
+ if ("macOS".equals(SDLPlatform.SDL_GetPlatform())
+ && SDL_GL_GetCurrentContext() == context
+ && SDL_GL_GetCurrentWindow() == window
+ && GL11.glGetString(GL11.GL_VERSION) == null
+ ) {
+ check(SDL_GL_MakeCurrent(window, 0));
+ }
check(SDL_GL_MakeCurrent(window, context));
+ GL.setCapabilities(capabilities);
}
@Override
public void releaseCurrent() {
check(SDL_GL_MakeCurrent(window, 0));
+ GL.setCapabilities(null);
}
@Override
@@ -101,10 +115,10 @@ public void setVSyncEnabled(boolean enabled) {
@Override
public void destroy() {
- if (capabilitiesCreated) {
- memFree(GL.getCapabilities().getAddressBuffer());
+ if (capabilities != null) {
+ memFree(capabilities.getAddressBuffer());
GL.setCapabilities(null);
- capabilitiesCreated = false;
+ capabilities = null;
}
if (glInitialized) {
GL.destroy();
@@ -115,7 +129,7 @@ public void destroy() {
context = 0;
}
if (window != 0) {
- SDL_DestroyWindow(window);
+ MainThread.run(() -> SDL_DestroyWindow(window));
window = 0;
}
}
diff --git a/src/main/java/pl/tomgirl/lenis/window/GpuSurface.java b/src/main/java/pl/tomgirl/pylon/window/GpuSurface.java
similarity index 88%
rename from src/main/java/pl/tomgirl/lenis/window/GpuSurface.java
rename to src/main/java/pl/tomgirl/pylon/window/GpuSurface.java
index 8e61350..2fd0437 100644
--- a/src/main/java/pl/tomgirl/lenis/window/GpuSurface.java
+++ b/src/main/java/pl/tomgirl/pylon/window/GpuSurface.java
@@ -1,4 +1,4 @@
-package pl.tomgirl.lenis.window;
+package pl.tomgirl.pylon.window;
public interface GpuSurface {
long createWindow(String title, int width, int height, boolean resizable);
diff --git a/src/main/java/pl/tomgirl/lenis/window/KeyboardSdl.java b/src/main/java/pl/tomgirl/pylon/window/KeyboardSdl.java
similarity index 84%
rename from src/main/java/pl/tomgirl/lenis/window/KeyboardSdl.java
rename to src/main/java/pl/tomgirl/pylon/window/KeyboardSdl.java
index d55f866..6a2b81d 100644
--- a/src/main/java/pl/tomgirl/lenis/window/KeyboardSdl.java
+++ b/src/main/java/pl/tomgirl/pylon/window/KeyboardSdl.java
@@ -1,10 +1,10 @@
-package pl.tomgirl.lenis.window;
+package pl.tomgirl.pylon.window;
import java.nio.ByteBuffer;
import java.util.Arrays;
-import java.util.Objects;
+import java.util.logging.Level;
-import pl.tomgirl.lenis.Lenis;
+import pl.tomgirl.pylon.Pylon;
import org.lwjgl.input.Keyboard;
import org.lwjgl.sdl.*;
@@ -85,20 +85,24 @@ public int getNumKeyboardEvents(boolean includeRepeats) {
public void processKeyboardEvent(SDL_Event event) {
switch (event.type()) {
- case SDL_EVENT_KEY_DOWN, SDL_EVENT_KEY_UP -> {
+ case SDL_EVENT_KEY_DOWN:
+ case SDL_EVENT_KEY_UP: {
int key = translateKeyFromSDL(keyboardEvent.scancode());
byte state = keyboardEvent.down() ? (byte) 1 : 0;
if (key != Keyboard.KEY_NONE) {
this.keyDownBuffer[key] = state;
}
putKeyboardEvent(key, state, 0, keyboardEvent.timestamp(), keyboardEvent.repeat());
+ break;
+ }
+ case SDL_EVENT_TEXT_INPUT: {
+ String text = textInputEvent.textString();
+ if (text == null) text = "";
+ text.chars().forEach(character ->
+ putKeyboardEvent(TEXT_EVENT_KEY, (byte) -1, character, textInputEvent.timestamp(), false)
+ );
+ break;
}
- case SDL_EVENT_TEXT_INPUT ->
- Objects.requireNonNullElse(textInputEvent.textString(), "")
- .chars()
- .forEach(character ->
- putKeyboardEvent(TEXT_EVENT_KEY, (byte) -1, character, textInputEvent.timestamp(), false)
- );
}
}
@@ -108,7 +112,7 @@ private int translateKeyFromSDL(int key) {
if (translated != Keymap.UNMAPPED) {
return translated;
}
- Lenis.LOG.warn("Untranslated key: {} ({})", key, SDLKeyboard.SDL_GetScancodeName(key));
+ Pylon.LOG.log(Level.WARNING, "Untranslated key: {0} ({1})", new Object[]{key, SDLKeyboard.SDL_GetScancodeName(key)});
return Keyboard.KEY_NONE;
}
diff --git a/src/main/java/pl/tomgirl/lenis/window/Keymap.java b/src/main/java/pl/tomgirl/pylon/window/Keymap.java
similarity index 99%
rename from src/main/java/pl/tomgirl/lenis/window/Keymap.java
rename to src/main/java/pl/tomgirl/pylon/window/Keymap.java
index 17424d3..bc6bfd9 100644
--- a/src/main/java/pl/tomgirl/lenis/window/Keymap.java
+++ b/src/main/java/pl/tomgirl/pylon/window/Keymap.java
@@ -1,4 +1,4 @@
-package pl.tomgirl.lenis.window;
+package pl.tomgirl.pylon.window;
import java.lang.reflect.Field;
import java.util.Arrays;
diff --git a/src/main/java/pl/tomgirl/pylon/window/MainThread.java b/src/main/java/pl/tomgirl/pylon/window/MainThread.java
new file mode 100644
index 0000000..5da48ab
--- /dev/null
+++ b/src/main/java/pl/tomgirl/pylon/window/MainThread.java
@@ -0,0 +1,82 @@
+package pl.tomgirl.pylon.window;
+
+import java.util.function.Supplier;
+
+import org.lwjgl.sdl.SDL_MainThreadCallback;
+import org.lwjgl.system.JNI;
+import org.lwjgl.system.macosx.LibC;
+import pl.tomgirl.pylon.Platform;
+
+import static org.lwjgl.system.MemoryUtil.NULL;
+import static org.lwjgl.system.macosx.DynamicLinkLoader.RTLD_DEFAULT;
+import static org.lwjgl.system.macosx.DynamicLinkLoader.dlsym;
+
+public class MainThread {
+ public static final boolean DISPATCH = Platform.CURRENT == Platform.MACOS && System.getenv("JAVA_STARTED_ON_FIRST_THREAD_" + LibC.getpid()) == null;
+
+ private static long dispatchSyncF;
+ private static long mainQueue;
+ private static SDL_MainThreadCallback trampoline;
+ private static volatile Thread thread;
+ private static volatile Supplier> task;
+ private static volatile Object result;
+ private static volatile Throwable failure;
+
+ private MainThread() {}
+
+ public static void run(Runnable task) {
+ call(() -> {
+ task.run();
+ return null;
+ });
+ }
+
+ @SuppressWarnings("unchecked")
+ public static synchronized T call(Supplier task) {
+ if (!DISPATCH || Thread.currentThread() == thread) {
+ return task.get();
+ }
+
+ if (trampoline == null) {
+ dispatchSyncF = check(dlsym(RTLD_DEFAULT, "dispatch_sync_f"));
+ mainQueue = check(dlsym(RTLD_DEFAULT, "_dispatch_main_q"));
+ trampoline = SDL_MainThreadCallback.create(userdata -> execute());
+ }
+
+ MainThread.task = task;
+ try {
+ JNI.invokePPPV(mainQueue, NULL, trampoline.address(), dispatchSyncF);
+ Throwable t = failure;
+ if (t instanceof RuntimeException) {
+ throw (RuntimeException) t;
+ }
+ if (t instanceof Error) {
+ throw (Error) t;
+ }
+ if (t != null) {
+ throw new IllegalStateException(t);
+ }
+ return (T) result;
+ } finally {
+ MainThread.task = null;
+ result = null;
+ failure = null;
+ }
+ }
+
+ private static void execute() {
+ thread = Thread.currentThread();
+ try {
+ result = task.get();
+ } catch (Throwable t) {
+ failure = t;
+ }
+ }
+
+ private static long check(long address) {
+ if (address == NULL) {
+ throw new IllegalStateException("libdispatch symbol not found");
+ }
+ return address;
+ }
+}
diff --git a/src/main/java/pl/tomgirl/lenis/window/MouseSdl.java b/src/main/java/pl/tomgirl/pylon/window/MouseSdl.java
similarity index 85%
rename from src/main/java/pl/tomgirl/lenis/window/MouseSdl.java
rename to src/main/java/pl/tomgirl/pylon/window/MouseSdl.java
index 1f38291..0a8355b 100644
--- a/src/main/java/pl/tomgirl/lenis/window/MouseSdl.java
+++ b/src/main/java/pl/tomgirl/pylon/window/MouseSdl.java
@@ -1,4 +1,4 @@
-package pl.tomgirl.lenis.window;
+package pl.tomgirl.pylon.window;
import java.util.Arrays;
@@ -6,6 +6,7 @@
import org.lwjgl.sdl.SDL_MouseButtonEvent;
import org.lwjgl.sdl.SDL_MouseMotionEvent;
import org.lwjgl.sdl.SDL_MouseWheelEvent;
+import pl.tomgirl.pylon.game.GameHooks;
import static org.lwjgl.sdl.SDLEvents.*;
import static org.lwjgl.sdl.SDLMouse.*;
@@ -122,16 +123,21 @@ public void setCursorPosition(double x, double y) {
lastX = x;
lastY = y;
accumDx = accumDy = 0;
- SDL_WarpMouseInWindow(handle, toSdlX(x), toSdlY(y));
+ MainThread.run(() -> SDL_WarpMouseInWindow(handle, toSdlX(x), toSdlY(y)));
}
public void grabMouse(boolean grab) {
- if (!grab) {
- SDL_WarpMouseInWindow(handle, toSdlX(lastX), toSdlY(lastY));
- }
- SDL_SetWindowRelativeMouseMode(handle, grab);
+ MainThread.run(() -> {
+ if (!grab) {
+ SDL_WarpMouseInWindow(handle, toSdlX(lastX), toSdlY(lastY));
+ }
+ SDL_SetWindowRelativeMouseMode(handle, grab);
+ });
grabbed = grab;
reset();
+ if (!GameHooks.screenPatchAvailable) {
+ DISPLAY.setTextInputActive(!grab);
+ }
}
public boolean hasWheel() {
@@ -148,14 +154,21 @@ public boolean isInsideWindow() {
public void processMouseEvent(SDL_Event event) {
switch (event.type()) {
- case SDL_EVENT_MOUSE_BUTTON_UP, SDL_EVENT_MOUSE_BUTTON_DOWN -> {
+ case SDL_EVENT_MOUSE_BUTTON_UP:
+ case SDL_EVENT_MOUSE_BUTTON_DOWN: {
boolean down = buttonEvent.down();
long timestamp = buttonEvent.timestamp();
- byte button = switch (buttonEvent.button()) {
- case SDL_BUTTON_RIGHT -> 1;
- case SDL_BUTTON_MIDDLE -> 2;
- default -> (byte) (buttonEvent.button() - 1);
- };
+ byte button;
+ switch (buttonEvent.button()) {
+ case SDL_BUTTON_RIGHT:
+ button = 1;
+ break;
+ case SDL_BUTTON_MIDDLE:
+ button = 2;
+ break;
+ default:
+ button = (byte) (buttonEvent.button() - 1);
+ }
if (grabbed) {
putMouseEvent(button, down, 0, timestamp);
@@ -172,8 +185,9 @@ public void processMouseEvent(SDL_Event event) {
if (button >= 0 && button < buttonStates.length) {
buttonStates[button] = down;
}
+ break;
}
- case SDL_EVENT_MOUSE_WHEEL -> {
+ case SDL_EVENT_MOUSE_WHEEL: {
int yOffset = wheelEvent.integer_y();
if (yOffset == 0) {
break;
@@ -191,8 +205,9 @@ public void processMouseEvent(SDL_Event event) {
wheelEvent.timestamp()
);
}
+ break;
}
- case SDL_EVENT_MOUSE_MOTION -> {
+ case SDL_EVENT_MOUSE_MOTION: {
double x = toLwjglX(motionEvent.x());
double y = toLwjglY(motionEvent.y());
double dx = motionEvent.xrel() * scaleX();
@@ -209,9 +224,14 @@ public void processMouseEvent(SDL_Event event) {
putMouseMotionEvent(x, y, nanos);
}
}
+ break;
}
- case SDL_EVENT_WINDOW_MOUSE_ENTER -> insideWindow = true;
- case SDL_EVENT_WINDOW_MOUSE_LEAVE -> insideWindow = false;
+ case SDL_EVENT_WINDOW_MOUSE_ENTER:
+ insideWindow = true;
+ break;
+ case SDL_EVENT_WINDOW_MOUSE_LEAVE:
+ insideWindow = false;
+ break;
}
}
diff --git a/src/main/resources/assets/lenis/icon.png b/src/main/resources/assets/lenis/icon.png
deleted file mode 100644
index 06d486e..0000000
Binary files a/src/main/resources/assets/lenis/icon.png and /dev/null differ
diff --git a/src/main/resources/assets/pylon/icon-256x.png b/src/main/resources/assets/pylon/icon-256x.png
new file mode 100644
index 0000000..243f3f1
Binary files /dev/null and b/src/main/resources/assets/pylon/icon-256x.png differ
diff --git a/src/main/resources/assets/pylon/icon.png b/src/main/resources/assets/pylon/icon.png
new file mode 100644
index 0000000..3f6354e
Binary files /dev/null and b/src/main/resources/assets/pylon/icon.png differ
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index 120840c..3686084 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -1,22 +1,23 @@
{
"schemaVersion": 1,
- "id": "lenis",
+ "id": "pylon",
"version": "${version}",
- "name": "Lenis",
- "icon": "assets/lenis/icon.png",
+ "name": "Pylon",
+ "icon": "assets/pylon/icon.png",
"authors": [ "notdevcody" ],
"license": "LGPL-3.0-only",
"environment": "client",
"entrypoints": {
"preLaunch": [
- "pl.tomgirl.lenis.PreLaunch"
+ "pl.tomgirl.pylon.fabric.PreLaunch"
]
},
"mixins": [
- "lenis.mixins.json"
+ "pylon.mixins.json"
],
"depends": {
"fabricloader": ">=0.17.3",
- "minecraft": "<1.13.0-alpha.17.43.a"
+ "minecraft": "<1.13.0-alpha.17.43.a",
+ "java": ">=8"
}
}
diff --git a/src/main/resources/lenis.mixins.json b/src/main/resources/lenis.mixins.json
deleted file mode 100644
index e2a7a8a..0000000
--- a/src/main/resources/lenis.mixins.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "required": true,
- "minVersion": "0.8",
- "package": "pl.tomgirl.lenis.mixin",
- "plugin": "pl.tomgirl.lenis.plugin.PaulscodeClasspathPlugin",
- "compatibilityLevel": "JAVA_17",
- "client": [
- "AL10Mixin",
- "ALMixin",
- "ARBShaderObjectsMixin",
- "GL11Mixin",
- "GL20Mixin",
- "GLCapabilitiesMixin",
- "LibraryLWJGLOpenALMixin",
- "game.MixinMinecraft_Resize",
- "game.MixinMinecraft_TextInput",
- "game.MixinMinecraft_Icon",
- "game.MixinScreen_Clipboard",
- "game.MixinTextFieldWidget_Ime"
- ],
- "injectors": {
- "defaultRequire": 1
- }
-}
diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info
new file mode 100644
index 0000000..fd1a1a9
--- /dev/null
+++ b/src/main/resources/mcmod.info
@@ -0,0 +1,14 @@
+[{
+ "modid": "pylon",
+ "name": "Pylon",
+ "description": "LWJGL3 compatibility layer for legacy Minecraft.",
+ "version": "${version}",
+ "mcversion": "*",
+ "url": "https://github.com/notdevcody/pylon",
+ "authors": [ "notdevcody" ],
+ "credits": "",
+ "logoFile": "/assets/pylon/icon-256x.png",
+ "parent": "",
+ "screenshots": [],
+ "dependencies": []
+}]
diff --git a/src/main/resources/pylon.mixins.json b/src/main/resources/pylon.mixins.json
new file mode 100644
index 0000000..ddb342b
--- /dev/null
+++ b/src/main/resources/pylon.mixins.json
@@ -0,0 +1,5 @@
+{
+ "required": true,
+ "compatibilityLevel": "JAVA_8",
+ "plugin": "pl.tomgirl.pylon.fabric.MixinPlugin"
+}
\ No newline at end of file