fix: 1.17 Forge auto installation

This commit is contained in:
huanghongxun 2021-07-25 17:29:00 +08:00
parent 14d1bccd14
commit 1dc4e3f730
8 changed files with 156 additions and 33 deletions

View File

@ -214,4 +214,5 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
public static final String VANILLA_MAIN = "net.minecraft.client.main.Main";
public static final String LAUNCH_WRAPPER_MAIN = "net.minecraft.launchwrapper.Launch";
public static final String MOD_LAUNCHER_MAIN = "cpw.mods.modlauncher.Launcher";
public static final String BOOTSTRAP_LAUNCHER_MAIN = "cpw.mods.bootstraplauncher.BootstrapLauncher";
}

View File

@ -17,29 +17,26 @@
*/
package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.game.Artifact;
import org.jackhuang.hmcl.game.CompatibilityRule;
import org.jackhuang.hmcl.game.GameRepository;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.game.VersionLibraryBuilder;
import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.SimpleMultimap;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.FORGE;
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.LITELOADER;
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.OPTIFINE;
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
public class MaintainTask extends Task<Version> {
private final GameRepository repository;
@ -68,7 +65,10 @@ public class MaintainTask extends Task<Version> {
return maintainOptiFineLibrary(repository, maintainGameWithLaunchWrapper(unique(version), true), false);
} else if (mainClass != null && mainClass.equals(LibraryAnalyzer.MOD_LAUNCHER_MAIN)) {
// Forge 1.13 and OptiFine
return maintainOptiFineLibrary(repository, maintainGameWithModLauncher(repository, unique(version)), true);
return maintainOptiFineLibrary(repository, maintainGameWithCpwModLauncher(repository, unique(version)), true);
} else if (mainClass != null && mainClass.equals(LibraryAnalyzer.BOOTSTRAP_LAUNCHER_MAIN)) {
// Forge 1.17
return maintainGameWithCpwBoostrapLauncher(repository, unique(version));
} else {
// Vanilla Minecraft does not need maintain
// Fabric does not need maintain, nothing compatible with fabric now.
@ -122,7 +122,7 @@ public class MaintainTask extends Task<Version> {
return mainClass == null ? ret : ret.setMainClass(mainClass);
}
private static Version maintainGameWithModLauncher(GameRepository repository, Version version) {
private static Version maintainGameWithCpwModLauncher(GameRepository repository, Version version) {
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
VersionLibraryBuilder builder = new VersionLibraryBuilder(version);
@ -149,6 +149,59 @@ public class MaintainTask extends Task<Version> {
return builder.build();
}
private static String updateIgnoreList(GameRepository repository, Version version, String ignoreList) {
String[] ignores = ignoreList.split(",");
List<String> newIgnoreList = new ArrayList<>();
// To resolve the the problem that name of primary jar may conflict with the module naming convention,
// we need to manually ignore ${primary_jar}.
newIgnoreList.add("${primary_jar}");
Path libraryDirectory = repository.getLibrariesDirectory(version).toPath().toAbsolutePath();
// The default ignoreList is too loose and may cause some problems, we replace them with the absolute version.
// For example, if "client-extra" is in ignoreList, and game directory contains "client-extra" component, all
// libraries will be ignored, which is not expected.
for (String classpathName : repository.getClasspath(version)) {
Path classpathFile = Paths.get(classpathName).toAbsolutePath();
String fileName = classpathFile.getFileName().toString();
if (Stream.of(ignores).anyMatch(fileName::contains)) {
// This library should be ignored for Jigsaw module finding by Forge.
String absolutePath;
if (classpathFile.startsWith(libraryDirectory)) {
// Note: It's assumed using "/" instead of File.separator in classpath
absolutePath = "${library_directory}${file_separator}" + libraryDirectory.relativize(classpathFile).toString().replace(File.separator, "${file_separator}");
} else {
absolutePath = classpathFile.toString();
}
newIgnoreList.add(StringUtils.substringBefore(absolutePath, ","));
}
}
return String.join(",", newIgnoreList);
}
// Fix wrong configurations when launching 1.17+ with Forge.
private static Version maintainGameWithCpwBoostrapLauncher(GameRepository repository, Version version) {
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
VersionLibraryBuilder builder = new VersionLibraryBuilder(version);
if (!libraryAnalyzer.has(FORGE)) return version;
// The default ignoreList set by Forge installer does not fulfill our requirements
List<Argument> jvm = builder.getMutableJvmArguments();
for (int i = 0; i < jvm.size(); i++) {
Argument jvmArg = jvm.get(i);
if (jvmArg instanceof StringArgument) {
String jvmArgStr = jvmArg.toString();
if (jvmArgStr.startsWith("-DignoreList=")) {
jvm.set(i, new StringArgument("-DignoreList=" + updateIgnoreList(repository, version, jvmArgStr.substring("-DignoreList=".length()))));
}
}
}
return builder.build();
}
private static Version maintainOptiFineLibrary(GameRepository repository, Version version, boolean remove) {
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
List<Library> libraries = new ArrayList<>(version.getLibraries());

View File

@ -173,7 +173,7 @@ public final class ForgeInstallTask extends Task<Version> {
ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class);
if (!gameVersion.get().equals(profile.getMinecraft()))
throw new VersionMismatchException(profile.getMinecraft(), gameVersion.get());
return new ForgeNewInstallTask(dependencyManager, version, modifyVersion(gameVersion.get(), profile.getPath().getVersion().replaceAll("(?i)forge", "")), installer);
return new ForgeNewInstallTask(dependencyManager, version, modifyVersion(gameVersion.get(), profile.getVersion()), installer);
} else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) {
ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class);
if (!gameVersion.get().equals(profile.getInstall().getMinecraft()))

View File

@ -25,10 +25,7 @@ import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import org.jackhuang.hmcl.util.gson.TolerableValidationException;
import org.jackhuang.hmcl.util.gson.Validation;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
@Immutable
@ -37,15 +34,17 @@ public class ForgeNewInstallProfile implements Validation {
private final int spec;
private final String minecraft;
private final String json;
private final String version;
private final Artifact path;
private final List<Library> libraries;
private final List<Processor> processors;
private final Map<String, Datum> data;
public ForgeNewInstallProfile(int spec, String minecraft, String json, Artifact path, List<Library> libraries, List<Processor> processors, Map<String, Datum> data) {
public ForgeNewInstallProfile(int spec, String minecraft, String json, String version, Artifact path, List<Library> libraries, List<Processor> processors, Map<String, Datum> data) {
this.spec = spec;
this.minecraft = minecraft;
this.json = json;
this.version = version;
this.path = path;
this.libraries = libraries;
this.processors = processors;
@ -74,12 +73,20 @@ public class ForgeNewInstallProfile implements Validation {
return json;
}
/**
*
* @return forge version.
*/
public String getVersion() {
return version;
}
/**
* Maven artifact path for the main jar to install.
* @return artifact path of the main jar.
*/
public Artifact getPath() {
return path;
public Optional<Artifact> getPath() {
return Optional.ofNullable(path);
}
/**
@ -112,7 +119,7 @@ public class ForgeNewInstallProfile implements Validation {
@Override
public void validate() throws JsonParseException, TolerableValidationException {
if (minecraft == null || json == null || path == null)
if (minecraft == null || json == null || version == null)
throw new JsonParseException("ForgeNewInstallProfile is malformed");
}

View File

@ -76,6 +76,50 @@ public class ForgeNewInstallTask extends Task<Version> {
setSignificance(TaskSignificance.MINOR);
}
private static String replaceTokens(Map<String, String> tokens, String value) {
StringBuilder buf = new StringBuilder();
for (int x = 0; x < value.length(); x++) {
char c = value.charAt(x);
if (c == '\\') {
if (x == value.length() - 1)
throw new IllegalArgumentException("Illegal pattern (Bad escape): " + value);
buf.append(value.charAt(++x));
} else if (c == '{' || c == '\'') {
StringBuilder key = new StringBuilder();
for (int y = x + 1; y <= value.length(); y++) {
if (y == value.length())
throw new IllegalArgumentException("Illegal pattern (Unclosed " + c + "): " + value);
char d = value.charAt(y);
if (d == '\\') {
if (y == value.length() - 1)
throw new IllegalArgumentException("Illegal pattern (Bad escape): " + value);
key.append(value.charAt(++y));
} else {
if (c == '{' && d == '}') {
x = y;
break;
}
if (c == '\'' && d == '\'') {
x = y;
break;
}
key.append(d);
}
}
if (c == '\'') {
buf.append(key);
} else {
if (!tokens.containsKey(key.toString()))
throw new IllegalArgumentException("Illegal pattern: " + value + " Missing Key: " + key);
buf.append(tokens.get(key.toString()));
}
} else {
buf.append(c);
}
}
return buf.toString();
}
private <E extends Exception> String parseLiteral(String literal, Map<String, String> var, ExceptionalFunction<String, String, E> plainConverter) throws E {
if (StringUtils.isSurrounded(literal, "{", "}"))
return var.get(StringUtils.removeSurrounding(literal, "{", "}"));
@ -84,7 +128,7 @@ public class ForgeNewInstallTask extends Task<Version> {
else if (StringUtils.isSurrounded(literal, "[", "]"))
return gameRepository.getArtifactFile(version, Artifact.fromDescriptor(StringUtils.removeSurrounding(literal, "[", "]"))).toString();
else
return plainConverter.apply(literal);
return plainConverter.apply(replaceTokens(var, literal));
}
@Override
@ -116,10 +160,10 @@ public class ForgeNewInstallTask extends Task<Version> {
}
}
{
Path mainJar = profile.getPath().getPath(fs.getPath("maven"));
if (profile.getPath().isPresent()) {
Path mainJar = profile.getPath().get().getPath(fs.getPath("maven"));
if (Files.exists(mainJar)) {
Path dest = gameRepository.getArtifactFile(version, profile.getPath());
Path dest = gameRepository.getArtifactFile(version, profile.getPath().get());
FileUtils.copyFile(mainJar, dest);
}
}
@ -153,6 +197,10 @@ public class ForgeNewInstallTask extends Task<Version> {
data.put("SIDE", "client");
data.put("MINECRAFT_JAR", gameRepository.getVersionJar(version).getAbsolutePath());
data.put("MINECRAFT_VERSION", gameRepository.getVersionJar(version).getAbsolutePath());
data.put("ROOT", gameRepository.getBaseDirectory().getAbsolutePath());
data.put("INSTALLER", installer.toAbsolutePath().toString());
data.put("LIBRARY_DIR", gameRepository.getLibrariesDirectory(version).getAbsolutePath());
for (ForgeNewInstallProfile.Processor processor : processors) {
Map<String, String> outputs = new HashMap<>();

View File

@ -21,7 +21,9 @@ import org.jackhuang.hmcl.task.Task;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Supports operations on versioning.
@ -220,4 +222,14 @@ public interface GameRepository extends VersionProvider {
*/
File getLoggingObject(String version, String assetId, LoggingInfo loggingInfo);
default List<String> getClasspath(Version version) {
List<String> classpath = new ArrayList<>();
for (Library library : version.getLibraries())
if (library.appliesToCurrentEnvironment() && !library.isNative()) {
File f = getLibraryFile(version, library);
if (f.exists() && f.isFile())
classpath.add(f.getAbsolutePath());
}
return classpath;
}
}

View File

@ -20,7 +20,8 @@ package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.platform.CommandBuilder;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
/**
*
@ -145,6 +146,11 @@ public final class VersionLibraryBuilder {
}
}
public List<Argument> getMutableJvmArguments() {
jvmChanged = true;
return jvm;
}
public void addGameArgument(String... args) {
for (String arg : args)
game.add(new StringArgument(arg));

View File

@ -160,13 +160,7 @@ public class DefaultLauncher extends Launcher {
}
}
LinkedList<String> classpath = new LinkedList<>();
for (Library library : version.getLibraries())
if (library.appliesToCurrentEnvironment() && !library.isNative()) {
File f = repository.getLibraryFile(version, library);
if (f.exists() && f.isFile())
classpath.add(f.getAbsolutePath());
}
List<String> classpath = repository.getClasspath(version);
File jar = repository.getVersionJar(version);
if (!jar.exists() || !jar.isFile())
@ -308,7 +302,9 @@ public class DefaultLauncher extends Launcher {
// libraries_directory stands for historical reasons here. We don't know the official launcher
// had already defined "library_directory" as the placeholder for path to ".minecraft/libraries"
// when we propose this placeholder.
pair("${libraries_directory}", repository.getLibrariesDirectory(version).getAbsolutePath())
pair("${libraries_directory}", repository.getLibrariesDirectory(version).getAbsolutePath()),
// file_separator is used in -DignoreList
pair("${file_separator}", OperatingSystem.FILE_SEPARATOR)
);
}