Skip to content

Commit

Permalink
Asset index-based sound download. WIP Fabric tweak. WIP resource over…
Browse files Browse the repository at this point in the history
…rides
  • Loading branch information
Lassebq committed Aug 20, 2024
1 parent eb8b15c commit 041c59d
Show file tree
Hide file tree
Showing 14 changed files with 331 additions and 94 deletions.
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ LaunchWrapper is bundled in [BetterJSONs](https://github.com/MCPHackers/BetterJS
## Features
- Strips game window from Java **AWT** (**Abstract Window Toolkit**) and lets the game use internal **LWJGL** frame
- BitDepthFix for versions before Beta 1.8
- LaunchWrapper works with Risugami's Modloader so long as you're using Java 8
- Allows changing assets directory in 1.6 snapshots (the game didn't have a parameter to do that yet)
- Allows changing game directory in versions before 1.6
- Replaces mouse input code with **LWJGL** calls
- Replaces mouse input code with **LWJGL** calls (Fixes any mouse input issues in classic, indev and infdev)
- Fixes TimSort crash when using Java 8+
- Adds ability to customize built-in **LWJGL** frame
- Changing display title
Expand Down Expand Up @@ -45,7 +46,7 @@ Arguments are formatted the same way as in Mojang launch wrapper. <br>
For example: `--username DemoUser --width 1280 --height 720 --title "Minecraft Demo" --demo`

Arguments may be as follows:
- `awtFrame` - disable LWJGL frame patch and use AWT
- `awtFrame` - disable LWJGL frame patch and use AWT (Not recommended)
- `isom` - Launch IsomPreviewApplet
- `forceVsync` - Launch the game with VSync enabled
- `forceResizable` - Early Indev and Classic don't properly update viewport, so the wrapper disables frame resizing. To enable it anyway use this argument
Expand All @@ -55,11 +56,14 @@ Arguments may be as follows:
- **pre-b1.9-pre4** - flip bottom textures and crop to 64x32
- **pre-1.8** - crop to 64x32
- **default** - do nothing with requested skin
- `resourcesProxyPort` - Betacraft proxy port for sound resources
- `serverSHA1` - Compare minecraft_server.jar in .minecraft/server against this hash
- `serverURL` - URL to download the server from if the hash is mismatched or the jar is missing
- `icon` - List of paths of icon PNGs separated by `;`
- `assetsDir` - Will be used by LaunchWrapper to locate custom index.json
- `assetIndex` - Name of the `assetsDir`/indexes/index.json without .json extension which will be used
- `title` - The display title
- `applet` - Makes the game think you're running an applet (Which removes quit button for example)
- `icon` - List of paths of icon PNGs separated by `;`
- `applet` - Makes the game think you're running an applet which:
- Removes quit button in versions between Beta 1.0 and release 1.5.2
- Changes mouse input code in classic
- `oneSixFlag` - Toggles notice about the release of 1.6 in 1.5.2
- `serverSHA1` - Compare minecraft_server.jar in .minecraft/server against this hash
- `serverURL` - URL to download the server from if the hash is mismatched or the jar is missing
- \+ any [Minecraft launch arguments](https://wiki.vg/Launching_the_game#Game_Arguments) or applet parameters
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ public class LaunchConfig {
public LaunchParameterSwitch forceVsync = new LaunchParameterSwitch("forceVsync", false, true);
public LaunchParameterSwitch forceResizable = new LaunchParameterSwitch("forceResizable", false, true);
public LaunchParameterEnum<SkinType> skinProxy = new LaunchParameterEnum<SkinType>("skinProxy", SkinType.DEFAULT, true);
public LaunchParameterNumber resourcesProxyPort = new LaunchParameterNumber("resourcesProxyPort", null, true);
public LaunchParameterString serverURL = new LaunchParameterString("serverURL", null, true);
public LaunchParameterString serverSHA1 = new LaunchParameterString("serverSHA1", null, true);
public LaunchParameterFileList icon = new LaunchParameterFileList("icon", null, true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public static File saveLevel(int index, String levelName) {
}
}
lvlNames[index] = levelName;
// Since minecraft doesn't have a delete button on levels, just save a new one with this name and it'll get deleted
if(levelName.equals("---")) {
level.delete();
level = null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.mcphackers.launchwrapper.loader;

import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;

import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;

public class ClassLoaderURLHandler extends URLStreamHandler {

private LaunchClassLoader classLoader;

public ClassLoaderURLHandler(LaunchClassLoader classLoader) {
this.classLoader = classLoader;
}

class ClassLoaderURLConnection extends URLConnection {

protected ClassLoaderURLConnection(URL url) {
super(url);
}

@Override
public void connect() throws IOException {
}

@Override
public InputStream getInputStream() throws IOException {
String path = url.getPath();
ClassNode node = classLoader.overridenClasses.get(path);
if(node == null) {
throw new FileNotFoundException();
}
ClassWriter writer = new SafeClassWriter(classLoader, COMPUTE_MAXS | COMPUTE_FRAMES);
node.accept(writer);
byte[] classData = writer.toByteArray();
return new ByteArrayInputStream(classData);
}

}

@Override
protected URLConnection openConnection(URL url) throws IOException {
return new ClassLoaderURLConnection(url);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public class LaunchClassLoader extends URLClassLoader implements ClassNodeSource
private ClassLoaderTweak tweak;
private Map<String, Class<?>> exceptions = new HashMap<String, Class<?>>();
/** Keys should contain dots */
private Map<String, ClassNode> overridenClasses = new HashMap<String, ClassNode>();
Map<String, ClassNode> overridenClasses = new HashMap<String, ClassNode>();
Map<String, byte[]> overridenResources = new HashMap<String, byte[]>(); //TODO
/** Keys should contain slashes */
private Map<String, ClassNode> classNodeCache = new HashMap<String, ClassNode>();
private File debugOutput;
Expand All @@ -60,15 +61,35 @@ public void addURL(URL url) {
}

public URL findResource(String name) {
URL url = super.findResource(name);
URL url = getOverridenResourceURL(name);
if(url != null) {
return url;
}
return parent.getResource(name);
url = super.findResource(name);
return url == null ? parent.getResource(name) : url;
}

public URL getResource(String name) {
return super.getResource(name);
URL url = getOverridenResourceURL(name);
if(url != null) {
return url;
}
url = super.getResource(name);
return url == null ? parent.getResource(name) : url;
}

private URL getOverridenResourceURL(String name) {
if(overridenResources.get(name) != null) {
//TODO
}
try {
if(overridenClasses.get(classNameFromResource(name)) != null) {
URL url = new URL("jar", "", -1, classNameFromResource(name), new ClassLoaderURLHandler(this));
return url;
}
} catch (MalformedURLException e) {
}
return null;
}

public Enumeration<URL> findResources(String name) throws IOException {
Expand Down Expand Up @@ -150,7 +171,7 @@ private void saveDebugClass(ClassNode node) {
cls.getParentFile().mkdirs();
// TraceClassVisitor trace = new TraceClassVisitor(new PrintWriter(new File(debugOutput, node.name + ".dump")));
// node.accept(trace);
ClassWriter writer = new SafeClassWriter(this, COMPUTE_MAXS);
ClassWriter writer = new SafeClassWriter(this, COMPUTE_MAXS | COMPUTE_FRAMES);
node.accept(writer);
byte[] classData = writer.toByteArray();
FileOutputStream fos = new FileOutputStream(cls);
Expand Down Expand Up @@ -230,6 +251,13 @@ private static String classResourceName(String name) {
return name.replace('.', '/') + ".class";
}

private static String classNameFromResource(String resource) {
if(resource.endsWith(".class")) {
return resource.substring(resource.length() - 7);
}
return resource;
}

private InputStream getClassAsStream(String name) {
String className = classResourceName(name);
InputStream is = super.getResourceAsStream(className);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.mcphackers.launchwrapper.protocol;

import java.io.File;
import java.io.FileInputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.json.JSONObject;
import org.mcphackers.launchwrapper.util.Util;

public class AssetRequests {
public class AssetObject {
public File file;
public String path;
public String hash;
public long size;

public AssetObject(File f, String name, String sha1, long s) {
file = f;
path = name;
hash = sha1;
size = s;
}
}

private Map<String, AssetObject> mappedAssets = new HashMap<String, AssetObject>();

public AssetRequests(File assetsDir, String index) {
if(index == null || assetsDir == null) {
return;
}
try {
String inputString = new String(Util.readStream(new FileInputStream(new File(assetsDir, "indexes/" + index + ".json"))));

JSONObject objects = new JSONObject(inputString).getJSONObject("objects");
for(String s : objects.keySet()) {
JSONObject entry = objects.optJSONObject(s);
if(entry == null) {
continue;
}
String hash = entry.optString("hash");
long size = entry.optLong("size");
if(hash == null) {
System.out.println("[LaunchWrapper] Invalid resource: " + s);
continue;
}
File object = new File(assetsDir, "objects/" + hash.substring(0, 2) + "/" + hash);
if(!object.exists() || object.length() != size) {
System.out.println("[LaunchWrapper] Invalid resource: " + s);
continue;
}
// A little slow and probably pointless
// String sha1 = Util.getSHA1(new FileInputStream(new File(assetsDir, "objects/" + hash.substring(0, 2) + "/" + hash)));
// if(!sha1.equals(hash)) {
// continue;
// }
AssetObject obj = new AssetObject(object, s, hash, size);
mappedAssets.put(s, obj);

}
} catch (Exception e) {
e.printStackTrace();
}
}

public AssetObject get(String key) {
return mappedAssets.get(key);
}

public Collection<AssetObject> list() {
return mappedAssets.values();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.mcphackers.launchwrapper.protocol;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;

import org.mcphackers.launchwrapper.protocol.AssetRequests.AssetObject;

public class AssetURLConnection extends URLConnection {
private AssetRequests assets;

public AssetURLConnection(URL url, AssetRequests assets) {
super(url);
this.assets = assets;
}

@SuppressWarnings("resource")
private InputStream getIndex(boolean xml) throws IOException {
PipedInputStream in = new PipedInputStream();
PrintWriter out = new PrintWriter(new PipedOutputStream(in), true);

new Thread(() -> {
if(xml) {
out.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>");
out.write("<ListBucketResult>");
}
for(AssetObject asset : assets.list()) {
// path,size,last_updated_timestamp(unused)
if(xml) {
out.write("<Contents>");
out.write("<Key>");
out.write(asset.path);
out.write("</Key>");
out.write("<Size>");
out.write(Long.toString(asset.size));
out.write("</Size>");
out.write("</Contents>");
} else {
out.write(asset.path + ',' + asset.size + ",0\n");
}
}
if(xml) {
out.write("</ListBucketResult>");
}
out.close();
}).start();
return in;
}

@Override
public InputStream getInputStream() throws IOException {
String key = url.getPath().replaceAll("%20", " ").substring(1);
key = key.substring(key.indexOf('/') + 1);
if(key.isEmpty()) {
boolean xml = url.getPath().startsWith("/MinecraftResources/");
return getIndex(xml);
}
AssetObject object = assets.get(key);
if(object != null) {
return new FileInputStream(object.file);
}
throw new FileNotFoundException();
}

@Override
public void connect() throws IOException {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
import java.net.URLConnection;

import org.mcphackers.launchwrapper.Launch;
import org.mcphackers.launchwrapper.LaunchConfig;

public class LegacyURLStreamHandler extends URLStreamHandlerProxy {

private SkinType skins;
private int port;
private LaunchConfig config;
private AssetRequests assets;

public LegacyURLStreamHandler(SkinType skins, int port) {
this.skins = skins;
this.port = port;
public LegacyURLStreamHandler(LaunchConfig config) {
this.config = config;
this.assets = new AssetRequests(config.assetsDir.get(), config.assetIndex.get());
}

@Override
Expand All @@ -40,9 +41,9 @@ protected URLConnection openConnection(URL url) throws IOException {
if(path.equals("/listmaps.jsp"))
return new ListLevelsURLConnection(url);
if(path.startsWith("/MinecraftResources/") || path.startsWith("/resources/"))
return new ResourceIndexURLConnection(url, port);
return new AssetURLConnection(url, assets);
if(path.startsWith("/MinecraftSkins/") || path.startsWith("/skin/") || path.startsWith("/MinecraftCloaks/") || path.startsWith("/cloak/"))
return new SkinURLConnection(url, skins);
return new SkinURLConnection(url, config.skinProxy.get());
if(host.equals("assets.minecraft.net") && path.equals("/1_6_has_been_released.flag"))
if(Launch.getConfig().oneSixFlag.get())
return new BasicResponseURLConnection(url, "https://web.archive.org/web/20130702232237if_/https://mojang.com/2013/07/minecraft-the-horse-update/");
Expand Down

This file was deleted.

Loading

0 comments on commit 041c59d

Please sign in to comment.