Skip to content

Commit

Permalink
Merge pull request #212 from 3arthqu4ke/211-1122-xvfb-does-not-work
Browse files Browse the repository at this point in the history
Fixed xvfb on older versions with lwjgl 2.9.4, like 1.12.2
  • Loading branch information
3arthqu4ke authored Nov 13, 2024
2 parents 9518393 + 7acb022 commit 37cb912
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 4 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ HeadlessMc can run inside Termux.
* Download the headlessmc-launcher-wrapper.jar into Termux.
* Disable JLine, as we could not get it to work on Termux for now,
by adding `hmc.jline.enabled=false` to the HeadlessMC/config.properties.
* Now you can use HeadlessMc like you do on Desktop or Docker.
* Now you can use HeadlessMc like you would on Desktop or Docker.

### Web

Expand All @@ -87,7 +87,7 @@ but it does not support all features we need to launch the game.
The CheerpJ instance can be tried out [here](https://3arthqu4ke.github.io/headlessmc/).
Secondly, there is [container2wasm](https://github.com/headlesshq/hmc-container2wasm),
which can translate the HeadlessMc Docker container
to WebAssembly and the run it inside the browser, but this is extremly slow.
to WebAssembly and the run it inside the browser, but this is extremely slow.

### Optimizations

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public interface LauncherProperties extends HmcProperties {
Property<Boolean> GAME_DIR_FOR_EACH_VERSION = bool("hmc.game.dir.for.each.version");

Property<Boolean> INSTALL_LOGGING = bool("hmc.install.mc.logging");

Property<Boolean> CHECK_XVFB = bool("hmc.check.xvfb");

}
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
package me.earth.headlessmc.launcher.instrumentation;

import lombok.CustomLog;
import lombok.experimental.UtilityClass;
import lombok.val;
import me.earth.headlessmc.launcher.instrumentation.log4j.Patchers;
import me.earth.headlessmc.launcher.instrumentation.lwjgl.HmcLwjglTransformer;
import me.earth.headlessmc.launcher.instrumentation.modlauncher.BootstrapLauncherTransformer;
import me.earth.headlessmc.launcher.instrumentation.paulscode.PaulscodeTransformer;
import me.earth.headlessmc.launcher.instrumentation.xvfb.XvfbLwjglTransformer;
import me.earth.headlessmc.launcher.launch.LaunchOptions;
import me.earth.headlessmc.launcher.version.Library;
import me.earth.headlessmc.launcher.version.family.FamilyUtil;
import org.jetbrains.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.List;

@CustomLog
@UtilityClass
public class InstrumentationHelper {
public static final String RUNTIME_JAR = "headlessmc-runtime.jar";
public static final String LWJGL_JAR = "headlessmc-lwjgl.jar";

public static Instrumentation create(LaunchOptions options) {
val transformers = new ArrayList<Transformer>(6);
val transformers = new ArrayList<Transformer>(8);
if (options.isLwjgl()) {
transformers.add(new HmcLwjglTransformer());
transformers.add(new ResourceExtractor(options.getFiles(), LWJGL_JAR));
Expand All @@ -42,7 +49,32 @@ public static Instrumentation create(LaunchOptions options) {
transformers.add(new BootstrapLauncherTransformer());
}

if (options.isXvfb()) {
addXvfbTransformer(options, transformers);
}

return new Instrumentation(transformers, options.getFiles().getBase());
}

@VisibleForTesting
static void addXvfbTransformer(LaunchOptions options, List<Transformer> transformers) {
log.error("Hello?!");
Boolean oldLwjgl = FamilyUtil.iterateParents(options.getVersion(), version -> {
for (Library library : version.getLibraries()) {
log.error("Cehcking " + library.getName());
if ("org.lwjgl.lwjgl".equals(library.getPackage()) && library.getVersionNumber().startsWith("2")) {
log.error("Hello? " + library);
return true;
}
}

return null;
});

if (oldLwjgl != null && oldLwjgl) {
log.info("Running with old lwjgl, using xvfb transformer");
transformers.add(new XvfbLwjglTransformer());
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package me.earth.headlessmc.launcher.instrumentation.xvfb;

import lombok.CustomLog;
import me.earth.headlessmc.launcher.instrumentation.AbstractClassTransformer;
import me.earth.headlessmc.launcher.instrumentation.InstrumentationHelper;
import me.earth.headlessmc.launcher.instrumentation.Target;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;

import java.util.Locale;

/**
* Lwjgl 2.9.4 (Mc 1.12.2) crashes in Github actions with xvfb at getAvailableDisplayModes,
* because the array returned by XRandR.getResolutions is empty.
* This patches that by always using XF86VIDMODE instead of XRANDR.
*/
@CustomLog
public class XvfbLwjglTransformer extends AbstractClassTransformer {
public XvfbLwjglTransformer() {
super("org/lwjgl/opengl/LinuxDisplay");
}

@Override
protected void transform(ClassNode cn) {
// TODO: Actually isXrandrSupported can also be overriden by setting the system property LWJGL_DISABLE_XRANDR
// but this would theoretically cover the case when XF86VIDMODE is not supported?
for (MethodNode mn : cn.methods) {
if ("getBestDisplayModeExtension".equals(mn.name) && "()I".equals(mn.desc)) {
for (AbstractInsnNode insnNode : mn.instructions) {
if (insnNode instanceof IntInsnNode && insnNode.getOpcode() == Opcodes.BIPUSH) {
IntInsnNode intInsnNode = (IntInsnNode) insnNode;
if (intInsnNode.operand == 10) { // private static final int XRANDR = 10;
log.info("Found BI_PUSH XRANDR, replacing with XF86VIDMODE");
intInsnNode.operand = 11; // XF86VIDMODE, idk lets just try this?
}
}
}
}
}
}

@Override
public boolean matches(Target target) {
return target.getPath().toLowerCase(Locale.ENGLISH).contains("lwjgl")
&& !target.getPath().endsWith(InstrumentationHelper.LWJGL_JAR);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class LaunchOptions {
private final boolean inMemory;
private final boolean forceSimple;
private final boolean forceBoot;
private final boolean xvfb;
private final boolean prepare;

@SuppressWarnings("unused")
Expand All @@ -46,10 +47,12 @@ private LaunchOptionsBuilder() {

public LaunchOptionsBuilder parseFlags(
Launcher ctx, boolean quit, String... args) {
boolean xvfb = false;
boolean lwjgl = flag(ctx, "-lwjgl", INVERT_LWJGL_FLAG, ALWAYS_LWJGL_FLAG, args);
// if offline only allow launching with the lwjgl flag!
if (!lwjgl && launcher.getAccountManager().getOfflineChecker().isOffline()) {
if (!new XvfbService(launcher.getConfigService(), launcher.getProcessFactory().getOs()).isRunningWithXvfb()) {
xvfb = new XvfbService(launcher.getConfigService(), launcher.getProcessFactory().getOs()).isRunningWithXvfb();
if (!xvfb) {
log.warning("You are offline, game will start in headless mode!");
lwjgl = true;
} else {
Expand All @@ -68,6 +71,7 @@ public LaunchOptionsBuilder parseFlags(
.forceSimple(CommandUtil.hasFlag("-forceSimple", args))
.forceBoot(CommandUtil.hasFlag("-forceBoot", args))
.parseJvmArgs(args)
.xvfb(xvfb)
.noIn(quit);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package me.earth.headlessmc.launcher.instrumentation;

import me.earth.headlessmc.launcher.UsesResources;
import me.earth.headlessmc.launcher.launch.LaunchOptions;
import me.earth.headlessmc.launcher.version.ParsesVersions;
import me.earth.headlessmc.launcher.version.Version;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class InstrumentationHelperTest implements UsesResources, ParsesVersions {
@Test
public void testAddXvfbTransformer() {
Version version = parseVersion(getJsonObject("version_with_old_lwjgl.json"));
LaunchOptions launchOptions = LaunchOptions.builder().version(version).build();
List<Transformer> transformers = new ArrayList<>();
InstrumentationHelper.addXvfbTransformer(launchOptions, transformers);
assertEquals(1, transformers.size());

version = parseVersion(getJsonObject("version_with_new_lwjgl.json"));
launchOptions = LaunchOptions.builder().version(version).build();
transformers = new ArrayList<>();
InstrumentationHelper.addXvfbTransformer(launchOptions, transformers);
assertEquals(0, transformers.size());
}

}
20 changes: 20 additions & 0 deletions headlessmc-launcher/src/test/resources/version_with_new_lwjgl.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"id": "version-without-parent",
"inheritsFrom": "1.18",
"releaseTime": "2022-06-17T19:10:29+0000",
"time": "2022-06-17T19:10:29+0000",
"type": "release",
"mainClass": "me.earth.headlessmc.runtime.Main",
"arguments": {
"game": [],
"jvm": [
"-DMainClass\u003d net.minecraft.client.main.Main "
]
},
"libraries": [
{
"name": "org.lwjgl.lwjgl:lwjgl-platform:3.1.0",
"url": "http://lwjgl-url"
}
]
}
20 changes: 20 additions & 0 deletions headlessmc-launcher/src/test/resources/version_with_old_lwjgl.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"id": "version-without-parent",
"inheritsFrom": "1.18",
"releaseTime": "2022-06-17T19:10:29+0000",
"time": "2022-06-17T19:10:29+0000",
"type": "release",
"mainClass": "me.earth.headlessmc.runtime.Main",
"arguments": {
"game": [],
"jvm": [
"-DMainClass\u003d net.minecraft.client.main.Main "
]
},
"libraries": [
{
"name": "org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209",
"url": "http://lwjgl-url"
}
]
}

0 comments on commit 37cb912

Please sign in to comment.