Skip to content

Commit

Permalink
Classic loading fix. Update README
Browse files Browse the repository at this point in the history
  • Loading branch information
Lassebq committed Oct 2, 2024
1 parent 86a40d7 commit 0f3bbc6
Show file tree
Hide file tree
Showing 5 changed files with 306 additions and 46 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,23 @@ LaunchWrapper is bundled in [BetterJSONs](https://github.com/MCPHackers/BetterJS
## Features
- Does not depend on any hard-coded obfuscated names and is mostly compatible with every Minecraft version
- This also includes modded versions
- Strips game window from Java **AWT** (**Abstract Window Toolkit**) and lets the game use internal **LWJGL** frame
- **deAWT** (aka **M1Fix**) - Strips game window from Java **AWT** (Abstract Window Toolkit) and lets the game use internal **LWJGL** frame
- Replaces mouse input code with **LWJGL** calls (Fixes any mouse input issues in Classic, Indev and Infdev. Commonly included with deAWT or M1Fix mods)
- BitDepthFix
- Online mode authentication fix
- LaunchWrapper works with Risugami's Modloader so long as you're using Java 8
- Built-in [Modloader Fix](https://github.com/coffeenotfound/ModloaderFix-b1.7.3)
- Replaces mouse input code with **LWJGL** calls (Fixes any mouse input issues in classic, indev and infdev)
- Allows changing game directory in versions before 1.6
- Makes save slots in classic and indev functional and saves to `.minecraft/levels` directory
- Adds ability to launch classic and pre-classic at custom resolution or in fullscreen
- Fixes TimSort crash when using Java 8+
- Proxies all requests from old skin servers to the current skin API and converts skins to required format
- **SkinFix** - Proxies all requests from old skin servers to the current skin API and converts skins to required format
- Skins don't work in Java versions before 8u181, due to the requrement of TLS 1.2 support
- Adds ability to customize built-in **LWJGL** frame
- Changing display title
- Setting icon
- Fixes sounds by using sounds from `.minecraft/assets`
- You need a valid asset index to be specified in `--assetIndex`
- Enabling VSync for versions without that option
- The wrapper is fully compatible with Java 5+ if the game is vanilla
- The wrapper also fixes Beta 1.3, Pre-classic and Classic compatibility with Java 5
Expand Down
59 changes: 24 additions & 35 deletions src/main/java/org/mcphackers/launchwrapper/LaunchConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
public class LaunchConfig {
private static final File defaultGameDir = getDefaultGameDir();
private Map<String, LaunchParameter<?>> parameters = new HashMap<String, LaunchParameter<?>>();
private Map<String, Object> unknownParameters = new HashMap<String, Object>();
private List<String> extraParameters = new ArrayList<String>();

public LaunchParameterSwitch demo = new LaunchParameterSwitch("demo");
public LaunchParameterSwitch fullscreen = new LaunchParameterSwitch("fullscreen");
Expand Down Expand Up @@ -104,7 +104,7 @@ private static File getDefaultGameDir() {
@SuppressWarnings("unchecked")
public LaunchConfig clone() {
LaunchConfig newConfig = new LaunchConfig();
newConfig.unknownParameters.putAll(unknownParameters);
newConfig.extraParameters.addAll(extraParameters);
for(Map.Entry<String, LaunchParameter<?>> entry : parameters.entrySet()) {
LaunchParameter<Object> param = (LaunchParameter<Object>)newConfig.parameters.get(entry.getKey());
if(param == null) {
Expand All @@ -120,28 +120,25 @@ public LaunchConfig() {

public LaunchConfig(String[] args) {
for(int i = 0; i < args.length; i++) {
if(args[i].startsWith("--")) {
String paramName = args[i].substring(2);
LaunchParameter<?> param = parameters.get(paramName);
if(param == null) {
if(i + 1 >= args.length || args[i+1].startsWith("--")) {
unknownParameters.put(paramName, Boolean.TRUE);
} else {
unknownParameters.put(paramName, args[i+1]);
i++;
}
continue;
}
if(param.isSwitch()) {
((LaunchParameterSwitch) param).setFlag();
continue;
}
if(i + 1 < args.length) {
try {
param.setString(args[i + 1]);
i++;
} catch (IllegalArgumentException e) {
}
if(!args[i].startsWith("--")) {
extraParameters.add(args[i]);
continue;
}
String paramName = args[i].substring(2);
LaunchParameter<?> param = parameters.get(paramName);
if(param == null) {
extraParameters.add(args[i]);
continue;
}
if(param.isSwitch()) {
((LaunchParameterSwitch) param).setFlag();
continue;
}
if(i + 1 < args.length) {
try {
param.setString(args[i + 1]);
i++;
} catch (IllegalArgumentException e) {
}
}
}
Expand Down Expand Up @@ -186,19 +183,11 @@ public String[] getArgs() {
}
}
}
for(String paramName : unknownParameters.keySet()) {
if(paramName == null) {
continue;
}
Object value = unknownParameters.get(paramName); // Either Boolean.TRUE or String
if(value == Boolean.TRUE) {
list.add("--" + paramName);
for(String param : extraParameters) {
if(param == null) {
continue;
}
if(value != null) {
list.add("--" + paramName);
list.add(value.toString());
}
list.add(param);
}
String[] arr = new String[list.size()];
return list.toArray(arr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.mcphackers.launchwrapper.tweak.injection.legacy.AddMain;
import org.mcphackers.launchwrapper.tweak.injection.legacy.BitDepthFix;
import org.mcphackers.launchwrapper.tweak.injection.legacy.ClassicCrashScreen;
import org.mcphackers.launchwrapper.tweak.injection.legacy.ClassicLoadingFix;
import org.mcphackers.launchwrapper.tweak.injection.legacy.FixClassicSession;
import org.mcphackers.launchwrapper.tweak.injection.legacy.FixGrayScreen;
import org.mcphackers.launchwrapper.tweak.injection.legacy.FixShutdown;
Expand Down Expand Up @@ -65,6 +66,7 @@ public List<Injection> getInjections() {
return Arrays.asList(
context,
new ClassicCrashScreen(context),
new ClassicLoadingFix(context),
new UnlicensedCopyText(context),
new FixSplashScreen(context),
new FixGrayScreen(context),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ && compareInsn(insns2[3], INVOKEVIRTUAL, minecraft.name, null, null)) {

@Override
public boolean apply(ClassNodeSource source, LaunchConfig config) {
// TODO catch MinecraftServer crashes in 1.3+
ClassNode minecraft = context.getMinecraft();
MethodNode run = context.getRun();
FieldNode running = context.getIsRunning();
Expand Down Expand Up @@ -150,6 +151,7 @@ && compareInsn(testInsn2, INVOKEVIRTUAL, minecraft.name, null, "()V")) {
if(openScreen == null) {
return false;
}
AbstractInsnNode instanceOf = null;

for(AbstractInsnNode insn = openScreen.instructions.getFirst(); insn != null; insn = nextInsn(insn)) {
AbstractInsnNode[] insns2 = fill(insn, 8);
Expand All @@ -167,13 +169,11 @@ && compareInsn(insns2[7], INVOKESPECIAL)) {
&& compareInsn(insns2[1], IFEQ)
&& compareInsn(insns2[2], RETURN)) {
errScreen = source.getClass(((TypeInsnNode)insn).desc);
instanceOf = insn;
break;
}
}

if(setWorld == null && cleanup == null) {
return false;
}
// In indev and early infdev crash screen is already present, we just need to patch it to not exit game loop
if(errScreen != null && setWorld != null) {
boolean patched = false;
Expand Down Expand Up @@ -327,7 +327,12 @@ && compareInsn(insns2[1], DUP)) {
if(errScreen == null) {
return false;
}
patchErrorScreen(source, errScreen, openScreen);
if(patchErrorScreen(source, errScreen, openScreen) && instanceOf != null) {
openScreen.instructions.set(instanceOf, new InsnNode(ICONST_0));
}
if(setWorld == null && cleanup == null) {
return false;
}

int n = getFreeIndex(run.instructions);
InsnList handle = new InsnList();
Expand Down Expand Up @@ -425,8 +430,8 @@ && compareInsn(insns[3], INVOKESPECIAL, minecraft.name, null, null)) {
return setWorld;
}

public void patchErrorScreen(ClassNodeSource source, ClassNode errScreen, MethodNode openScreen) {
//TODO patch cancel button
public boolean patchErrorScreen(ClassNodeSource source, ClassNode errScreen, MethodNode openScreen) {
boolean needCancelButton = true;
String[] fields = {"message", "description"};
MethodNode init = NodeHelper.getMethod(errScreen, "<init>", "(Ljava/lang/String;Ljava/lang/String;)V");
if(init == null) {
Expand Down Expand Up @@ -489,7 +494,120 @@ public void patchErrorScreen(ClassNodeSource source, ClassNode errScreen, Method
}
}
}
// Cancel button patch
// TODO don't patch button if it's already present
ClassNode screen = source.getClass(errScreen.superName);
cancelButton:
if(screen != null) {
FieldNode mcField = null;
FieldNode buttonsList = null;
for(FieldNode f : screen.fields) {
if(f.desc.equals("Ljava/util/List;") && buttonsList == null) {
buttonsList = f;
}
if(f.desc.equals("L" + context.getMinecraft().name + ";")) {
mcField = f;
}
}
if(buttonsList == null || mcField == null) {
break cancelButton;
}
String buttonType = null;
for(MethodNode m : screen.methods) {
for(AbstractInsnNode insn = m.instructions.getFirst(); insn != null; insn = nextInsn(insn)) {
AbstractInsnNode[] insns = fill(insn, 4);

if(compareInsn(insns[0], GETFIELD, screen.name, buttonsList.name, buttonsList.desc)
&& compareInsn(insns[1], ILOAD)
&& compareInsn(insns[2], INVOKEINTERFACE, "java/util/List", "get", "(I)Ljava/lang/Object;")
&& compareInsn(insns[3], CHECKCAST)) {
buttonType = ((TypeInsnNode)insns[3]).desc;
break;
}
}
if(buttonType != null) {
break;
}
}
MethodNode buttonClicked = null;
for(MethodNode m : screen.methods) {
if(!m.desc.equals("(L" + buttonType + ";)V")) {
continue;
}
buttonClicked = m;
break;
}
ClassNode button = source.getClass(buttonType);
if(button != null) {
if(NodeHelper.getMethod(button, "<init>", "(IIILjava/lang/String;)V") == null) {
break cancelButton;
}
}
FieldInsnNode width = null;
for(MethodNode m : errScreen.methods) {
if(!m.desc.equals("(II)V") && !m.desc.equals("(IIF)V")) {
continue;
}
for(AbstractInsnNode insn = m.instructions.getFirst(); insn != null; insn = nextInsn(insn)) {
AbstractInsnNode[] insns = fill(insn, 6);
if(compareInsn(insns[0], ICONST_0)
&& compareInsn(insns[1], ICONST_0)
&& compareInsn(insns[2], ALOAD, 0)
&& compareInsn(insns[3], GETFIELD, null, null, "I")
&& compareInsn(insns[4], ALOAD, 0)
&& compareInsn(insns[5], GETFIELD, null, null, "I")) {
width = (FieldInsnNode)insns[3];
break;
}
}
if(width != null) {
break;
}
}
MethodNode initScreen = null;
for(MethodNode m : errScreen.methods) {
if(!m.desc.equals("()V") || m.name.equals("<init>")) {
continue;
}
initScreen = m;
break;
}
if(initScreen == null || width == null) {
break cancelButton;
}
if(NodeHelper.getMethod(errScreen, buttonClicked.name, buttonClicked.desc) != null) {
break cancelButton;
}
MethodNode buttonClickedNew = new MethodNode(buttonClicked.access, buttonClicked.name, buttonClicked.desc, null, null);
errScreen.methods.add(buttonClickedNew);
InsnList list = buttonClickedNew.instructions;
list.add(new VarInsnNode(ALOAD, 0));
list.add(new FieldInsnNode(GETFIELD, screen.name, mcField.name, mcField.desc));
list.add(new InsnNode(ACONST_NULL));
list.add(new MethodInsnNode(INVOKEVIRTUAL, context.getMinecraft().name, openScreen.name, openScreen.desc));
list.add(new InsnNode(RETURN));

list = new InsnList();
list.add(new VarInsnNode(ALOAD, 0));
list.add(new FieldInsnNode(GETFIELD, screen.name, buttonsList.name, buttonsList.desc));
list.add(new TypeInsnNode(NEW, buttonType));
list.add(new InsnNode(DUP));
list.add(new InsnNode(ICONST_0));
list.add(new VarInsnNode(ALOAD, 0));
list.add(new FieldInsnNode(GETFIELD, screen.name, width.name, width.desc));
list.add(new InsnNode(ICONST_2));
list.add(new InsnNode(IDIV));
list.add(intInsn(100));
list.add(new InsnNode(ISUB));
list.add(intInsn(140));
list.add(new LdcInsnNode("Cancel")); // TODO translate string?
list.add(new MethodInsnNode(INVOKESPECIAL, buttonType, "<init>", "(IIILjava/lang/String;)V"));
list.add(new MethodInsnNode(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z"));
list.add(new InsnNode(POP));
initScreen.instructions.insert(list);
}
source.overrideClass(errScreen);
return needCancelButton;
}

}
Loading

0 comments on commit 0f3bbc6

Please sign in to comment.