Skip to content

Commit 6e228be

Browse files
authored
forge: support CleanroomMC installer (#516)
1 parent ed61115 commit 6e228be

File tree

7 files changed

+2571
-32
lines changed

7 files changed

+2571
-32
lines changed

src/main/java/me/itzg/helpers/forge/ForgeInstaller.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import java.util.Arrays;
1313
import java.util.List;
1414
import java.util.Objects;
15+
import java.util.Optional;
1516
import java.util.regex.Matcher;
1617
import java.util.regex.Pattern;
1718
import lombok.NonNull;
@@ -30,11 +31,6 @@ public class ForgeInstaller {
3031
"Exec:\\s+(?<exec>.+)"
3132
+ "|The server installed successfully, you should now be able to run the file (?<universalJar>.+)");
3233

33-
private static final List<String> entryJarFormats = Arrays.asList(
34-
"forge-%s-%s.jar",
35-
"forge-%s-%s-shim.jar"
36-
);
37-
3834
private final InstallerResolver installerResolver;
3935

4036
public ForgeInstaller(InstallerResolver installerResolver) {
@@ -96,7 +92,7 @@ else if (
9692
final Path forgeInstallerJar = installerResolver.download(resolved.minecraft, resolved.forge, outputDir);
9793

9894
try {
99-
newManifest = install(forgeInstallerJar, outputDir, resolved.minecraft, variant, resolved.forge);
95+
newManifest = install(forgeInstallerJar, outputDir, resolved.minecraft, Optional.ofNullable(resolved.variantOverride).orElse(variant), resolved.forge);
10096

10197
} finally {
10298

@@ -217,7 +213,7 @@ private ForgeManifest install(Path installerJar, Path outputDir, String minecraf
217213
// A 1.12.2 style installer that doesn't report entry point in logs
218214
// >= 1.20.4 where "Exec:" line is no longer included in logs
219215
if (entryFile == null) {
220-
final Path resolved = findEntryJar(outputDir, minecraftVersion, forgeVersion);
216+
final Path resolved = findEntryJar(outputDir, variant, minecraftVersion, forgeVersion);
221217
if (resolved != null) {
222218
entryFile = resolved.toAbsolutePath();
223219
}
@@ -254,9 +250,20 @@ private ForgeManifest install(Path installerJar, Path outputDir, String minecraf
254250
}
255251
}
256252

257-
private static Path findEntryJar(Path outputDir, String minecraftVersion, String forgeVersion) {
258-
for (final String entryJarFormat : entryJarFormats) {
259-
final Path path = outputDir.resolve(String.format(entryJarFormat, minecraftVersion, forgeVersion));
253+
@FunctionalInterface
254+
interface ServerJarNameBuilder {
255+
String build(String variant, String minecraftVersion, String forgeVersion);
256+
}
257+
258+
private final static List<ServerJarNameBuilder> serverJarNameBuilders = Arrays.asList(
259+
(v, m, f) -> String.format("%s-%s-%s.jar", v, m, f),
260+
(v,m, f) -> String.format("%s-%s-%s-shim.jar", v, m, f),
261+
(v,m, f) -> String.format("%s-%s.jar", v, f)
262+
);
263+
264+
private static Path findEntryJar(Path outputDir, String variant, String minecraftVersion, String forgeVersion) {
265+
for (final ServerJarNameBuilder builder : serverJarNameBuilders) {
266+
final Path path = outputDir.resolve(builder.build(variant.toLowerCase(), minecraftVersion, forgeVersion));
260267
if (Files.exists(path)) {
261268
return path;
262269
}

src/main/java/me/itzg/helpers/forge/ProvidedInstallerResolver.java

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.fasterxml.jackson.databind.JsonNode;
44
import com.fasterxml.jackson.databind.node.ObjectNode;
55
import java.io.IOException;
6+
import java.io.InputStream;
67
import java.nio.file.Path;
78
import java.util.regex.Matcher;
89
import java.util.regex.Pattern;
@@ -11,7 +12,12 @@
1112
import me.itzg.helpers.json.ObjectMappers;
1213

1314
public class ProvidedInstallerResolver implements InstallerResolver {
15+
1416
private static final Pattern OLD_FORGE_ID_VERSION = Pattern.compile("forge(.+)", Pattern.CASE_INSENSITIVE);
17+
public static final String PROP_ID = "id";
18+
public static final String PROP_INHERITS_FROM = "inheritsFrom";
19+
public static final String INSTALLER_ID_FORGE = "forge";
20+
public static final String INSTALLER_ID_CLEANROOM = "cleanroom";
1521

1622
private final Path forgeInstaller;
1723

@@ -31,7 +37,7 @@ public VersionPair resolve() {
3137
throw new GenericException("Failed to locate version from provided installer file");
3238
}
3339

34-
return new VersionPair(versions.minecraft, versions.forge);
40+
return versions;
3541
}
3642

3743
@Override
@@ -46,22 +52,10 @@ public void cleanup(Path forgeInstallerJar) {
4652

4753
private VersionPair extractVersion(Path forgeInstaller) throws IOException {
4854

49-
// Extract version from installer jar's version.json file
50-
// where top level "id" field is used
51-
52-
final VersionPair fromVersionJson = IoStreams.readFileFromZip(forgeInstaller, "version.json", inputStream -> {
53-
final ObjectNode parsed = ObjectMappers.defaultMapper()
54-
.readValue(inputStream, ObjectNode.class);
55-
56-
final String id = parsed.get("id").asText("");
57-
58-
final String[] idParts = id.split("-");
59-
if (idParts.length != 3 || !idParts[1].equals("forge")) {
60-
throw new GenericException("Unexpected format of id from Forge installer's version.json: " + id);
61-
}
62-
63-
return new VersionPair(idParts[0], idParts[2]);
64-
});
55+
final VersionPair fromVersionJson = IoStreams.readFileFromZip(forgeInstaller, "version.json",
56+
ProvidedInstallerResolver::extractFromVersionJson
57+
);
58+
// will be null if version.json wasn't present
6559
if (fromVersionJson != null) {
6660
return fromVersionJson;
6761
}
@@ -70,7 +64,7 @@ private VersionPair extractVersion(Path forgeInstaller) throws IOException {
7064
final ObjectNode parsed = ObjectMappers.defaultMapper()
7165
.readValue(inputStream, ObjectNode.class);
7266

73-
final JsonNode idNode = parsed.path("versionInfo").path("id");
67+
final JsonNode idNode = parsed.path("versionInfo").path(PROP_ID);
7468
if (idNode.isTextual()) {
7569
final String[] idParts = idNode.asText().split("-");
7670

@@ -100,4 +94,33 @@ private VersionPair extractVersion(Path forgeInstaller) throws IOException {
10094
}
10195
});
10296
}
97+
98+
/**
99+
* Extract version from installer jar's version.json file where top level "id" and "inheritedFrom" fields are used
100+
* @throws GenericException if something wasn't right about the version.json
101+
*/
102+
public static VersionPair extractFromVersionJson(InputStream versionJsonIn) throws IOException {
103+
final ObjectNode parsed = ObjectMappers.defaultMapper()
104+
.readValue(versionJsonIn, ObjectNode.class);
105+
106+
final String id = parsed.get(PROP_ID).asText();
107+
final JsonNode inheritsFromNode = parsed.get(PROP_INHERITS_FROM);
108+
if (inheritsFromNode.isMissingNode()) {
109+
throw new GenericException("Installer version.json is missing " + PROP_INHERITS_FROM);
110+
}
111+
final String minecraftVersion = inheritsFromNode.asText();
112+
113+
final String[] idParts = id.split("-");
114+
if (idParts.length >= 3) {
115+
if (idParts[1].equals(INSTALLER_ID_FORGE)) {
116+
return new VersionPair(minecraftVersion, idParts[2]);
117+
}
118+
if (idParts[0].equals(INSTALLER_ID_CLEANROOM)) {
119+
return new VersionPair(minecraftVersion, String.join("-", idParts[1], idParts[2]))
120+
.setVariantOverride(INSTALLER_ID_CLEANROOM);
121+
}
122+
}
123+
124+
throw new GenericException("Unexpected format of id from Forge installer's version.json: " + id);
125+
}
103126
}
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package me.itzg.helpers.forge;
22

3-
import lombok.AllArgsConstructor;
43
import lombok.EqualsAndHashCode;
4+
import lombok.RequiredArgsConstructor;
5+
import lombok.Setter;
56
import lombok.ToString;
67

7-
@AllArgsConstructor
8+
@RequiredArgsConstructor
89
@ToString @EqualsAndHashCode
910
public class VersionPair {
1011

11-
String minecraft;
12-
String forge;
12+
final String minecraft;
13+
final String forge;
14+
@Setter
15+
String variantOverride;
1316
}

src/test/java/me/itzg/helpers/forge/ProvidedInstallerResolverTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@
22

33
import static org.assertj.core.api.Assertions.assertThat;
44

5+
import java.io.IOException;
6+
import java.io.InputStream;
57
import java.net.URISyntaxException;
68
import java.net.URL;
79
import java.nio.file.Paths;
10+
import java.util.stream.Stream;
811
import org.junit.jupiter.api.Test;
12+
import org.junit.jupiter.params.ParameterizedTest;
13+
import org.junit.jupiter.params.provider.Arguments;
14+
import org.junit.jupiter.params.provider.MethodSource;
915

1016
class ProvidedInstallerResolverTest {
1117

@@ -22,4 +28,28 @@ void resolvesVersionFromFile() throws URISyntaxException {
2228
assertThat(versions.minecraft).isEqualTo("1.20.2");
2329
assertThat(versions.forge).isEqualTo("48.1.0");
2430
}
31+
32+
@ParameterizedTest
33+
@MethodSource("resolvesIdVariantsArgs")
34+
void resolvesIdVariants(String versionJsonName, String expectedMinecraftVersion, String expectedInstallerVersion)
35+
throws IOException {
36+
try (InputStream versionJsonStream = ProvidedInstallerResolverTest.class.getResourceAsStream(
37+
"/forge/" + versionJsonName)) {
38+
assertThat(versionJsonStream).isNotNull();
39+
40+
final VersionPair result = ProvidedInstallerResolver.extractFromVersionJson(versionJsonStream);
41+
assertThat(result).isNotNull();
42+
assertThat(result.minecraft).isEqualTo(expectedMinecraftVersion);
43+
assertThat(result.forge).isEqualTo(expectedInstallerVersion);
44+
}
45+
}
46+
47+
public static Stream<Arguments> resolvesIdVariantsArgs() {
48+
return Stream.of(
49+
Arguments.arguments("version-forge-1.20.2.json", "1.20.2", "48.1.0"),
50+
Arguments.arguments("version-forge-1.12.2.json", "1.12.2", "14.23.5.2860"),
51+
Arguments.arguments("version-cleanroom.json", "1.12.2", "0.2.4-alpha")
52+
);
53+
}
54+
2555
}

0 commit comments

Comments
 (0)