Skip to content

Commit 48f83aa

Browse files
committed
Support legacy mapping file system properties
Support extracting natives Support LegacyDev default arguments
1 parent 7a7906b commit 48f83aa

5 files changed

Lines changed: 327 additions & 22 deletions

File tree

build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ dependencies {
3232

3333
implementation libs.gson
3434
implementation libs.jopt
35+
implementation libs.srgutils
3536

3637
implementation libs.bundles.utils
3738
}
@@ -83,6 +84,7 @@ tasks.named('shadowJar', ShadowJar) {
8384
relocate 'com.google.errorprone.annotations', 'net.minecraftforge.launcher.shadow.errorprone'
8485
relocate 'com.google.gson', 'net.minecraftforge.launcher.shadow.gson'
8586
relocate 'net.minecraftforge.util', 'net.minecraftforge.launcher.shadow.util'
87+
relocate 'net.minecraftforge.srgutils', 'net.minecraftforge.launcher.shadow.srgutils'
8688
// Rewrite JOpt's message files, so that help text is displayed nicely.
8789
transform(com.github.jengelman.gradle.plugins.shadow.transformers.PropertiesFileTransformer) {
8890
paths = [ 'Messages.properties$' ]
@@ -103,6 +105,7 @@ publishing {
103105

104106
publications.register('mavenJava', MavenPublication) {
105107
from components.shadow
108+
artifact sourcesJar
106109

107110
changelog.publish(it)
108111
gradleutils.promote(it)

settings.gradle

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,13 @@ dependencyResolutionManagement.versionCatalogs.register('libs') {
2828

2929
library 'gson', 'com.google.code.gson', 'gson' version '2.11.0'
3030
library 'jopt', 'net.sf.jopt-simple', 'jopt-simple' version '6.0-alpha-3'
31+
library 'srgutils', 'net.minecraftforge', 'srgutils' version '0.6.5'
32+
33+
library 'srgutils', 'net.minecraftforge', 'srgutils' version '0.6.5'
3134

3235
library 'utils-download', 'net.minecraftforge', 'download-utils' version '0.4.0'
33-
library 'utils-hash', 'net.minecraftforge', 'hash-utils' version '0.1.12'
34-
library 'utils-data', 'net.minecraftforge', 'json-data-utils' version '0.4.0'
36+
library 'utils-hash', 'net.minecraftforge', 'hash-utils' version '0.2.2'
37+
library 'utils-data', 'net.minecraftforge', 'json-data-utils' version '0.4.7'
3538
library 'utils-logging', 'net.minecraftforge', 'log-utils' version '0.5.0'
3639
bundle 'utils', [ 'utils-download', 'utils-hash', 'utils-data', 'utils-logging' ]
3740

src/main/java/net/minecraftforge/launcher/DownloadAssets.java

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,31 @@
99
import net.minecraftforge.util.data.json.MinecraftVersion;
1010
import net.minecraftforge.util.download.DownloadUtils;
1111
import net.minecraftforge.util.hash.HashFunction;
12+
import net.minecraftforge.util.logging.Logger;
1213

1314
import java.io.File;
15+
import java.io.IOException;
1416
import java.util.Map;
1517

1618
/** Handles downloading assets for Minecraft. */
1719
final class DownloadAssets {
20+
static final Logger LOGGER = Main.LOGGER;
21+
22+
static void checkAssets(String repo, File assetsDir, MinecraftVersion versionJson, boolean forceSkip) {
23+
if (forceSkip) {
24+
LOGGER.info("Force Skipping assets");
25+
return;
26+
}
27+
28+
LOGGER.info("Checking assets");
29+
byte indent = LOGGER.push();
30+
try {
31+
DownloadAssets.download(repo, assetsDir, versionJson);
32+
} finally {
33+
LOGGER.pop(indent);
34+
}
35+
}
36+
1837
/**
1938
* Downloads assets for the given Minecraft version.
2039
*
@@ -36,19 +55,19 @@ static void download(String repo, File assetsDir, MinecraftVersion versionJson)
3655
File file = new File(objectsDir, assetDest);
3756
long fileLength = file.length();
3857
if (fileLength != 0) {
39-
Main.LOGGER.debug("Considering existing file with size " + fileLength + " for " + name);
58+
LOGGER.debug("Considering existing file with size " + fileLength + " for " + name);
4059
if (fileLength == asset.size) {
41-
Main.LOGGER.debug("Size check succeeded. Skipping.");
60+
LOGGER.debug("Size check succeeded. Skipping.");
4261
continue;
4362
}
4463
}
4564

4665
// We need to download assets? Release the log so the consumer is aware.
47-
Main.LOGGER.release();
66+
LOGGER.release();
4867
try {
4968
Main.LOGGER.info("Downloading missing asset: " + name);
5069
DownloadUtils.downloadFile(file, repo + assetDest);
51-
String newSha1 = HashFunction.SHA1.sneakyHash(file);
70+
String newSha1 = HashFunction.sha1().hash(file);
5271
if (!newSha1.equals(asset.hash)) {
5372
file.delete();
5473
throw new IllegalStateException(String.format("Failed to verify asset %s. Expected %s got %s", name, asset.hash, newSha1));
@@ -77,9 +96,13 @@ private static File downloadIndex(MinecraftVersion versionJson, File assetsDir)
7796
throw new IllegalStateException("Failed to download assets index", e);
7897
}
7998

80-
String newSha1 = HashFunction.SHA1.sneakyHash(index);
81-
if (!newSha1.equals(versionJson.assetIndex.sha1))
82-
throw new IllegalStateException(String.format("Failed to verify assets index. Expected %s got %s", versionJson.assetIndex.sha1, newSha1));
99+
try {
100+
String newSha1 = HashFunction.sha1().hash(index);
101+
if (!newSha1.equals(versionJson.assetIndex.sha1))
102+
throw new IllegalStateException(String.format("Failed to verify assets index. Expected %s got %s", versionJson.assetIndex.sha1, newSha1));
103+
} catch (IOException e) {
104+
throw new IllegalStateException("Failed to verify assets index", e);
105+
}
83106

84107
return index;
85108
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
/*
2+
* Copyright (c) Forge Development LLC and contributors
3+
* SPDX-License-Identifier: LGPL-2.1-only
4+
*/
5+
package net.minecraftforge.launcher;
6+
7+
import joptsimple.NonOptionArgumentSpec;
8+
import joptsimple.OptionParser;
9+
import joptsimple.OptionSet;
10+
import net.minecraftforge.util.data.json.MinecraftVersion;
11+
import net.minecraftforge.util.logging.Logger;
12+
import net.minecraftforge.util.os.OS;
13+
14+
import java.io.File;
15+
import java.io.FileOutputStream;
16+
import java.io.IOException;
17+
import java.io.InputStream;
18+
import java.lang.reflect.Field;
19+
import java.util.ArrayList;
20+
import java.util.Collections;
21+
import java.util.Enumeration;
22+
import java.util.HashMap;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.zip.ZipEntry;
26+
import java.util.zip.ZipFile;
27+
28+
/// This is the SlimeLauncher replacement for [LegacyDev](https://github.com/MinecraftForge/LegacyDev/).
29+
/// Which was written to Bridge 1.12.2 to FG3+
30+
class LegacyDev {
31+
static final Logger LOGGER = Main.LOGGER;
32+
static final String LEGACYDEV = "net.minecraftforge.legacydev.";
33+
static final String LEGACYDEV_CLIENT = LEGACYDEV + "MainClient";
34+
static final String LEGACYDEV_SERVER = LEGACYDEV + "MainServer";
35+
36+
static boolean is(String main) {
37+
return main.startsWith(LEGACYDEV);
38+
}
39+
40+
static String getMainClass() {
41+
String mainClass = getenv("mainClass");
42+
if (mainClass == null)
43+
throw new IllegalArgumentException("Must specify mainClass environment variable");
44+
LOGGER.info("Legacy Main Class: " + mainClass);
45+
return mainClass;
46+
}
47+
48+
private static Map<String, File> findAllClassPathEntries() {
49+
String[] parts = System.getProperty("java.class.path").split(File.pathSeparator);
50+
Map<String, File> files = new HashMap<>();
51+
for (String part : parts) {
52+
File file = new File(part);
53+
if (file.exists() && file.isFile())
54+
files.put(file.getName(), file);
55+
}
56+
return files;
57+
}
58+
59+
/// Extract native libraries and inject them into the classloader.
60+
/// This mimics the behavior of the Vanilla Minecraft Launcher
61+
/// Because old versions of lwjgl didn't auto-extract their libraries
62+
static void setupNatives(MinecraftVersion versionJson, File cache) {
63+
OS currentOS = OS.current();
64+
Map<String, File> classpath = findAllClassPathEntries();
65+
LOGGER.info("Extracting natives to " + cache.getAbsolutePath());
66+
LOGGER.push();
67+
try {
68+
for (MinecraftVersion.Lib lib : versionJson.getLibs()) {
69+
if (!lib.allows(currentOS) || lib.info.extract == null || lib.dl == null || lib.dl == lib.info.downloads.artifact) // We only want natives
70+
continue;
71+
72+
String name = lib.dl.path.substring(lib.dl.path.lastIndexOf('/') + 1);
73+
File file = classpath.get(name);
74+
if (file == null) {
75+
LOGGER.error(name + ": Missing");
76+
continue;
77+
}
78+
79+
LOGGER.info(name + " from " + file.getAbsolutePath());
80+
LOGGER.push();
81+
try (ZipFile zip = new ZipFile(file)) {
82+
zipEntry:
83+
for (Enumeration<? extends ZipEntry> en = zip.entries(); en.hasMoreElements(); ) {
84+
ZipEntry entry = en.nextElement();
85+
File output = new File(cache, entry.getName());
86+
if (output.exists())
87+
continue; // Assume its valid is already extracted
88+
89+
// Skip anything the json says to filter
90+
if (lib.info.extract.exclude != null) {
91+
for (String exclude : lib.info.extract.exclude) {
92+
if (entry.getName().startsWith(exclude))
93+
continue zipEntry;
94+
}
95+
}
96+
97+
if (output.getParentFile() != null)
98+
output.getParentFile().mkdirs();
99+
100+
try (FileOutputStream out = new FileOutputStream(output)) {
101+
InputStream stream = zip.getInputStream(entry);
102+
byte[] buf = new byte[8192];
103+
int length;
104+
while ((length = stream.read(buf)) != -1) {
105+
out.write(buf, 0, length);
106+
}
107+
}
108+
}
109+
} catch (IOException e) {
110+
throw new RuntimeException(e);
111+
} finally {
112+
LOGGER.pop();
113+
}
114+
}
115+
} finally {
116+
LOGGER.pop();
117+
}
118+
119+
String paths = System.getProperty("java.library.path");
120+
if (paths == null || paths.isEmpty())
121+
paths = cache.getAbsolutePath();
122+
else
123+
paths += File.pathSeparator + cache.getAbsolutePath();
124+
System.setProperty("java.library.path", paths);
125+
126+
// Add the library path to the classloader if it has already been cached. It shouldn't be by now, and this only matters on java <= 8 so this reflection should be fairly safe
127+
try {
128+
final String[] usrPathsValue = paths.split(File.pathSeparator);
129+
final Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths");
130+
usrPathsField.setAccessible(true);
131+
usrPathsField.set(null, usrPathsValue);
132+
} catch (Throwable t) {
133+
// If we catch an exception we're on a modern version of java, and thus probably don't need these hacks
134+
}
135+
}
136+
137+
/// Enhance the command line arguments like LegacyDev does
138+
/// This reads a lot of things from the environment because I was stupid when I originally designed Legacydev -Lex
139+
/// [Reference](https://github.com/MinecraftForge/LegacyDev/blob/master/src/main/java/net/minecraftforge/legacydev/Main.java#L81)
140+
static String[] enhanceArgs(String mainClass, String[] existing) {
141+
LOGGER.info("Enhancing Arguments");
142+
LOGGER.push();
143+
try {
144+
Map<String, String> values = new HashMap<>();
145+
String tweak = getenv("tweakClass");
146+
if (tweak != null && !tweak.isEmpty()) {
147+
LOGGER.info("tweakClass: " + tweak);
148+
values.put("tweakClass", tweak);
149+
}
150+
151+
if (LEGACYDEV_CLIENT.equals(mainClass)) {
152+
LOGGER.info("version: " + getenv("MC_VERSION"));
153+
values.put("version", getenv("MC_VERSION"));
154+
values.put("assetIndex", "{asset_index}");
155+
values.put("assetsDir", "{assets_root}");
156+
values.put("accessToken", "Forge");
157+
values.put("userProperties", "[]");
158+
values.put("username", null);
159+
values.put("password", null);
160+
}
161+
162+
final OptionParser parser = new OptionParser();
163+
parser.allowsUnrecognizedOptions();
164+
165+
for (String key : values.keySet())
166+
parser.accepts(key).withRequiredArg().ofType(String.class);
167+
168+
final NonOptionArgumentSpec<String> nonOption = parser.nonOptions();
169+
170+
final OptionSet options = parser.parse(existing);
171+
for (String key : values.keySet()) {
172+
if (options.hasArgument(key)) {
173+
String value = (String) options.valueOf(key);
174+
values.put(key, value);
175+
}
176+
}
177+
178+
List<String> extras = new ArrayList<>(nonOption.values(options));
179+
LOGGER.info("Extra: " + extras);
180+
181+
List<String> lst = new ArrayList<>(values.size() * 2 + extras.size());
182+
for (Map.Entry<String, String> entry : values.entrySet()) {
183+
if (entry.getValue() == null || entry.getValue().isEmpty())
184+
continue;
185+
lst.add("--" + entry.getKey());
186+
lst.add(entry.getValue());
187+
}
188+
lst.addAll(extras);
189+
return lst.toArray(new String[lst.size()]);
190+
} finally {
191+
LOGGER.pop();
192+
}
193+
}
194+
195+
private static String getenv(String name) {
196+
String value = System.getenv(name);
197+
return value == null || value.isEmpty() ? null : value;
198+
}
199+
200+
private static boolean nullOrEmpty(String value) {
201+
return value == null || value.isEmpty();
202+
}
203+
}

0 commit comments

Comments
 (0)