将 GameRepository 从 java.io.File 迁移至 NIO (#4496)

https://github.com/HMCL-dev/HMCL/issues/2987
This commit is contained in:
Glavo
2025-09-17 16:07:59 +08:00
committed by GitHub
parent 6a497df0d1
commit 26e274a89a
55 changed files with 483 additions and 416 deletions

View File

@@ -64,7 +64,7 @@ public final class HMCLGameLauncher extends DefaultLauncher {
if (config().isDisableAutoGameOptions()) if (config().isDisableAutoGameOptions())
return; return;
Path runDir = repository.getRunDirectory(version.getId()).toPath(); Path runDir = repository.getRunDirectory(version.getId());
Path optionsFile = runDir.resolve("options.txt"); Path optionsFile = runDir.resolve("options.txt");
Path configFolder = runDir.resolve("config"); Path configFolder = runDir.resolve("config");

View File

@@ -44,7 +44,6 @@ import org.jackhuang.hmcl.util.platform.SystemInfo;
import org.jackhuang.hmcl.util.versioning.VersionNumber; import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.InvalidPathException; import java.nio.file.InvalidPathException;
@@ -58,7 +57,7 @@ import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.Pair.pair; import static org.jackhuang.hmcl.util.Pair.pair;
public class HMCLGameRepository extends DefaultGameRepository { public final class HMCLGameRepository extends DefaultGameRepository {
private final Profile profile; private final Profile profile;
// local version settings // local version settings
@@ -67,7 +66,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
public final EventManager<Event> onVersionIconChanged = new EventManager<>(); public final EventManager<Event> onVersionIconChanged = new EventManager<>();
public HMCLGameRepository(Profile profile, File baseDirectory) { public HMCLGameRepository(Profile profile, Path baseDirectory) {
super(baseDirectory); super(baseDirectory);
this.profile = profile; this.profile = profile;
} }
@@ -86,7 +85,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
} }
@Override @Override
public File getRunDirectory(String id) { public Path getRunDirectory(String id) {
switch (getGameDirectoryType(id)) { switch (getGameDirectoryType(id)) {
case VERSION_FOLDER: case VERSION_FOLDER:
return getVersionRoot(id); return getVersionRoot(id);
@@ -94,7 +93,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
return super.getRunDirectory(id); return super.getRunDirectory(id);
case CUSTOM: case CUSTOM:
try { try {
return Path.of(getVersionSetting(id).getGameDir()).toFile(); return Path.of(getVersionSetting(id).getGameDir());
} catch (InvalidPathException ignored) { } catch (InvalidPathException ignored) {
return getVersionRoot(id); return getVersionRoot(id);
} }
@@ -122,7 +121,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
}); });
try { try {
Path file = getBaseDirectory().toPath().resolve("launcher_profiles.json"); Path file = getBaseDirectory().resolve("launcher_profiles.json");
if (!Files.exists(file) && !versions.isEmpty()) { if (!Files.exists(file) && !versions.isEmpty()) {
Files.createDirectories(file.getParent()); Files.createDirectories(file.getParent());
Files.writeString(file, PROFILE); Files.writeString(file, PROFILE);
@@ -132,7 +131,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
} }
} }
public void changeDirectory(File newDirectory) { public void changeDirectory(Path newDirectory) {
setBaseDirectory(newDirectory); setBaseDirectory(newDirectory);
refreshVersionsAsync().start(); refreshVersionsAsync().start();
} }
@@ -143,13 +142,13 @@ public class HMCLGameRepository extends DefaultGameRepository {
} }
public void clean(String id) throws IOException { public void clean(String id) throws IOException {
clean(getBaseDirectory().toPath()); clean(getBaseDirectory());
clean(getRunDirectory(id).toPath()); clean(getRunDirectory(id));
} }
public void duplicateVersion(String srcId, String dstId, boolean copySaves) throws IOException { public void duplicateVersion(String srcId, String dstId, boolean copySaves) throws IOException {
Path srcDir = getVersionRoot(srcId).toPath(); Path srcDir = getVersionRoot(srcId);
Path dstDir = getVersionRoot(dstId).toPath(); Path dstDir = getVersionRoot(dstId);
Version fromVersion = getVersion(srcId); Version fromVersion = getVersion(srcId);
@@ -183,22 +182,22 @@ public class HMCLGameRepository extends DefaultGameRepository {
VersionSetting newVersionSetting = initLocalVersionSetting(dstId, oldVersionSetting); VersionSetting newVersionSetting = initLocalVersionSetting(dstId, oldVersionSetting);
saveVersionSetting(dstId); saveVersionSetting(dstId);
File srcGameDir = getRunDirectory(srcId); Path srcGameDir = getRunDirectory(srcId);
File dstGameDir = getRunDirectory(dstId); Path dstGameDir = getRunDirectory(dstId);
if (originalGameDirType != GameDirectoryType.VERSION_FOLDER) if (originalGameDirType != GameDirectoryType.VERSION_FOLDER)
FileUtils.copyDirectory(srcGameDir.toPath(), dstGameDir.toPath(), path -> Modpack.acceptFile(path, blackList, null)); FileUtils.copyDirectory(srcGameDir, dstGameDir, path -> Modpack.acceptFile(path, blackList, null));
} }
private File getLocalVersionSettingFile(String id) { private Path getLocalVersionSettingFile(String id) {
return new File(getVersionRoot(id), "hmclversion.cfg"); return getVersionRoot(id).resolve("hmclversion.cfg");
} }
private void loadLocalVersionSetting(String id) { private void loadLocalVersionSetting(String id) {
File file = getLocalVersionSettingFile(id); Path file = getLocalVersionSettingFile(id);
if (file.exists()) if (Files.exists(file))
try { try {
VersionSetting versionSetting = GSON.fromJson(Files.readString(file.toPath()), VersionSetting.class); VersionSetting versionSetting = JsonUtils.fromJsonFile(file, VersionSetting.class);
initLocalVersionSetting(id, versionSetting); initLocalVersionSetting(id, versionSetting);
} catch (Exception ex) { } catch (Exception ex) {
// If [JsonParseException], [IOException] or [NullPointerException] happens, the json file is malformed and needed to be recreated. // If [JsonParseException], [IOException] or [NullPointerException] happens, the json file is malformed and needed to be recreated.
@@ -261,12 +260,12 @@ public class HMCLGameRepository extends DefaultGameRepository {
return vs; return vs;
} }
public Optional<File> getVersionIconFile(String id) { public Optional<Path> getVersionIconFile(String id) {
File root = getVersionRoot(id); Path root = getVersionRoot(id);
for (String extension : FXUtils.IMAGE_EXTENSIONS) { for (String extension : FXUtils.IMAGE_EXTENSIONS) {
File file = new File(root, "icon." + extension); Path file = root.resolve("icon." + extension);
if (file.exists()) { if (Files.exists(file)) {
return Optional.of(file); return Optional.of(file);
} }
} }
@@ -274,21 +273,26 @@ public class HMCLGameRepository extends DefaultGameRepository {
return Optional.empty(); return Optional.empty();
} }
public void setVersionIconFile(String id, File iconFile) throws IOException { public void setVersionIconFile(String id, Path iconFile) throws IOException {
String ext = FileUtils.getExtension(iconFile.getName()).toLowerCase(Locale.ROOT); String ext = FileUtils.getExtension(iconFile).toLowerCase(Locale.ROOT);
if (!FXUtils.IMAGE_EXTENSIONS.contains(ext)) { if (!FXUtils.IMAGE_EXTENSIONS.contains(ext)) {
throw new IllegalArgumentException("Unsupported icon file: " + ext); throw new IllegalArgumentException("Unsupported icon file: " + ext);
} }
deleteIconFile(id); deleteIconFile(id);
FileUtils.copyFile(iconFile.toPath(), getVersionRoot(id).toPath().resolve("icon." + ext)); FileUtils.copyFile(iconFile, getVersionRoot(id).resolve("icon." + ext));
} }
public void deleteIconFile(String id) { public void deleteIconFile(String id) {
File root = getVersionRoot(id); Path root = getVersionRoot(id);
for (String extension : FXUtils.IMAGE_EXTENSIONS) { for (String extension : FXUtils.IMAGE_EXTENSIONS) {
new File(root, "icon." + extension).delete(); Path file = root.resolve("icon." + extension);
try {
Files.deleteIfExists(file);
} catch (IOException e) {
LOG.warning("Failed to delete icon file: " + file, e);
}
} }
} }
@@ -301,10 +305,10 @@ public class HMCLGameRepository extends DefaultGameRepository {
if (iconType == VersionIconType.DEFAULT) { if (iconType == VersionIconType.DEFAULT) {
Version version = getVersion(id).resolve(this); Version version = getVersion(id).resolve(this);
Optional<File> iconFile = getVersionIconFile(id); Optional<Path> iconFile = getVersionIconFile(id);
if (iconFile.isPresent()) { if (iconFile.isPresent()) {
try { try {
return FXUtils.loadImage(iconFile.get().toPath()); return FXUtils.loadImage(iconFile.get());
} catch (Exception e) { } catch (Exception e) {
LOG.warning("Failed to load version icon of " + id, e); LOG.warning("Failed to load version icon of " + id, e);
} }
@@ -339,7 +343,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
public void saveVersionSetting(String id) { public void saveVersionSetting(String id) {
if (!localVersionSettings.containsKey(id)) if (!localVersionSettings.containsKey(id))
return; return;
Path file = getLocalVersionSettingFile(id).toPath().toAbsolutePath().normalize(); Path file = getLocalVersionSettingFile(id).toAbsolutePath().normalize();
try { try {
Files.createDirectories(file.getParent()); Files.createDirectories(file.getParent());
} catch (IOException e) { } catch (IOException e) {
@@ -373,7 +377,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
vs.setUsesGlobal(true); vs.setUsesGlobal(true);
} }
public LaunchOptions getLaunchOptions(String version, JavaRuntime javaVersion, File gameDir, List<String> javaAgents, List<String> javaArguments, boolean makeLaunchScript) { public LaunchOptions getLaunchOptions(String version, JavaRuntime javaVersion, Path gameDir, List<String> javaAgents, List<String> javaArguments, boolean makeLaunchScript) {
VersionSetting vs = getVersionSetting(version); VersionSetting vs = getVersionSetting(version);
LaunchOptions.Builder builder = new LaunchOptions.Builder() LaunchOptions.Builder builder = new LaunchOptions.Builder()
@@ -384,7 +388,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
.setProfileName(Metadata.TITLE) .setProfileName(Metadata.TITLE)
.setGameArguments(StringUtils.tokenize(vs.getMinecraftArgs())) .setGameArguments(StringUtils.tokenize(vs.getMinecraftArgs()))
.setOverrideJavaArguments(StringUtils.tokenize(vs.getJavaArgs())) .setOverrideJavaArguments(StringUtils.tokenize(vs.getJavaArgs()))
.setMaxMemory(vs.isNoJVMArgs() && vs.isAutoMemory() ? null : (int)(getAllocatedMemory( .setMaxMemory(vs.isNoJVMArgs() && vs.isAutoMemory() ? null : (int) (getAllocatedMemory(
vs.getMaxMemory() * 1024L * 1024L, vs.getMaxMemory() * 1024L * 1024L,
SystemInfo.getPhysicalMemoryStatus().getAvailable(), SystemInfo.getPhysicalMemoryStatus().getAvailable(),
vs.isAutoMemory() vs.isAutoMemory()
@@ -430,15 +434,15 @@ public class HMCLGameRepository extends DefaultGameRepository {
} }
} }
File json = getModpackConfiguration(version); Path json = getModpackConfiguration(version);
if (json.exists()) { if (Files.exists(json)) {
try { try {
String jsonText = Files.readString(json.toPath()); String jsonText = Files.readString(json);
ModpackConfiguration<?> modpackConfiguration = JsonUtils.GSON.fromJson(jsonText, ModpackConfiguration.class); ModpackConfiguration<?> modpackConfiguration = JsonUtils.GSON.fromJson(jsonText, ModpackConfiguration.class);
ModpackProvider provider = ModpackHelper.getProviderByType(modpackConfiguration.getType()); ModpackProvider provider = ModpackHelper.getProviderByType(modpackConfiguration.getType());
if (provider != null) provider.injectLaunchOptions(jsonText, builder); if (provider != null) provider.injectLaunchOptions(jsonText, builder);
} catch (IOException | JsonParseException e) { } catch (IOException | JsonParseException e) {
e.printStackTrace(); LOG.warning("Failed to parse modpack configuration file " + json, e);
} }
} }
@@ -449,8 +453,8 @@ public class HMCLGameRepository extends DefaultGameRepository {
} }
@Override @Override
public File getModpackConfiguration(String version) { public Path getModpackConfiguration(String version) {
return new File(getVersionRoot(version), "modpack.cfg"); return getVersionRoot(version).resolve("modpack.cfg");
} }
public void markVersionAsModpack(String id) { public void markVersionAsModpack(String id) {
@@ -463,16 +467,24 @@ public class HMCLGameRepository extends DefaultGameRepository {
public void markVersionLaunchedAbnormally(String id) { public void markVersionLaunchedAbnormally(String id) {
try { try {
Files.createFile(getVersionRoot(id).toPath().resolve(".abnormal")); Files.createFile(getVersionRoot(id).resolve(".abnormal"));
} catch (IOException ignored) { } catch (IOException ignored) {
} }
} }
public boolean unmarkVersionLaunchedAbnormally(String id) { public boolean unmarkVersionLaunchedAbnormally(String id) {
File file = new File(getVersionRoot(id), ".abnormal"); Path file = getVersionRoot(id).resolve(".abnormal");
boolean result = file.isFile(); if (Files.isRegularFile(file)) {
file.delete(); try {
return result; Files.delete(file);
} catch (IOException e) {
LOG.warning("Failed to delete abnormal mark file: " + file, e);
}
return true;
} else {
return false;
}
} }
private static final Gson GSON = new GsonBuilder() private static final Gson GSON = new GsonBuilder()

View File

@@ -31,6 +31,8 @@ import org.jackhuang.hmcl.util.io.CompressingUtils;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -51,9 +53,9 @@ public final class HMCLModpackInstallTask extends Task<Void> {
this.name = name; this.name = name;
this.modpack = modpack; this.modpack = modpack;
File run = repository.getRunDirectory(name); Path run = repository.getRunDirectory(name);
File json = repository.getModpackConfiguration(name); Path json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists()) if (repository.hasVersion(name) && Files.notExists(json))
throw new IllegalArgumentException("Version " + name + " already exists"); throw new IllegalArgumentException("Version " + name + " already exists");
dependents.add(dependency.gameBuilder().name(name).gameVersion(modpack.getGameVersion()).buildAsync()); dependents.add(dependency.gameBuilder().name(name).gameVersion(modpack.getGameVersion()).buildAsync());
@@ -64,16 +66,16 @@ public final class HMCLModpackInstallTask extends Task<Void> {
ModpackConfiguration<Modpack> config = null; ModpackConfiguration<Modpack> config = null;
try { try {
if (json.exists()) { if (Files.exists(json)) {
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(Modpack.class)); config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(Modpack.class));
if (!HMCLModpackProvider.INSTANCE.getName().equals(config.getType())) if (!HMCLModpackProvider.INSTANCE.getName().equals(config.getType()))
throw new IllegalArgumentException("Version " + name + " is not a HMCL modpack. Cannot update this version."); throw new IllegalArgumentException("Version " + name + " is not a HMCL modpack. Cannot update this version.");
} }
} catch (JsonParseException | IOException ignore) { } catch (JsonParseException | IOException ignore) {
} }
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList("/minecraft"), it -> !"pack.json".equals(it), config)); dependents.add(new ModpackInstallTask<>(zipFile, run.toFile(), modpack.getEncoding(), Collections.singletonList("/minecraft"), it -> !"pack.json".equals(it), config));
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList("/minecraft"), modpack, HMCLModpackProvider.INSTANCE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack")); dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList("/minecraft"), modpack, HMCLModpackProvider.INSTANCE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name).toFile()).withStage("hmcl.modpack"));
} }
@Override @Override

View File

@@ -55,6 +55,7 @@ import java.io.IOException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.net.URI; import java.net.URI;
import java.nio.file.AccessDeniedException; import java.nio.file.AccessDeniedException;
import java.nio.file.Path;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@@ -150,7 +151,7 @@ public final class LauncherHelper {
dependencyManager.checkGameCompletionAsync(version.get(), integrityCheck), dependencyManager.checkGameCompletionAsync(version.get(), integrityCheck),
Task.composeAsync(() -> { Task.composeAsync(() -> {
try { try {
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion)); ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion).toFile());
ModpackProvider provider = ModpackHelper.getProviderByType(configuration.getType()); ModpackProvider provider = ModpackHelper.getProviderByType(configuration.getType());
if (provider == null) return null; if (provider == null) return null;
else return provider.createCompletionTask(dependencyManager, selectedVersion); else return provider.createCompletionTask(dependencyManager, selectedVersion);
@@ -164,16 +165,16 @@ public final class LauncherHelper {
Library lib = NativePatcher.getWindowsMesaLoader(java, renderer, OperatingSystem.SYSTEM_VERSION); Library lib = NativePatcher.getWindowsMesaLoader(java, renderer, OperatingSystem.SYSTEM_VERSION);
if (lib == null) if (lib == null)
return null; return null;
File file = dependencyManager.getGameRepository().getLibraryFile(version.get(), lib); Path file = dependencyManager.getGameRepository().getLibraryFile(version.get(), lib);
if (file.getAbsolutePath().indexOf('=') >= 0) { if (file.toAbsolutePath().toString().indexOf('=') >= 0) {
LOG.warning("Invalid character '=' in the libraries directory path, unable to attach software renderer loader"); LOG.warning("Invalid character '=' in the libraries directory path, unable to attach software renderer loader");
return null; return null;
} }
String agent = file.getAbsolutePath() + "=" + renderer.name().toLowerCase(Locale.ROOT); String agent = FileUtils.getAbsolutePath(file) + "=" + renderer.name().toLowerCase(Locale.ROOT);
if (GameLibrariesTask.shouldDownloadLibrary(repository, version.get(), lib, integrityCheck)) { if (GameLibrariesTask.shouldDownloadLibrary(repository, version.get(), lib, integrityCheck)) {
return new LibraryDownloadTask(dependencyManager, file.toPath(), lib) return new LibraryDownloadTask(dependencyManager, file, lib)
.thenRunAsync(() -> javaAgents.add(agent)); .thenRunAsync(() -> javaAgents.add(agent));
} else { } else {
javaAgents.add(agent); javaAgents.add(agent);
@@ -189,7 +190,7 @@ public final class LauncherHelper {
.thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in")) .thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in"))
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> { .thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
LaunchOptions launchOptions = repository.getLaunchOptions( LaunchOptions launchOptions = repository.getLaunchOptions(
selectedVersion, javaVersionRef.get(), profile.getGameDir(), javaAgents, javaArguments, scriptFile != null); selectedVersion, javaVersionRef.get(), profile.getGameDir().toPath(), javaAgents, javaArguments, scriptFile != null);
LOG.info("Here's the structure of game mod directory:\n" + FileUtils.printFileStructure(repository.getModManager(selectedVersion).getModsDirectory(), 10)); LOG.info("Here's the structure of game mod directory:\n" + FileUtils.printFileStructure(repository.getModManager(selectedVersion).getModsDirectory(), 10));

View File

@@ -44,8 +44,8 @@ public final class LogExporter {
} }
public static CompletableFuture<Void> exportLogs(Path zipFile, DefaultGameRepository gameRepository, String versionId, String logs, String launchScript) { public static CompletableFuture<Void> exportLogs(Path zipFile, DefaultGameRepository gameRepository, String versionId, String logs, String launchScript) {
Path runDirectory = gameRepository.getRunDirectory(versionId).toPath(); Path runDirectory = gameRepository.getRunDirectory(versionId);
Path baseDirectory = gameRepository.getBaseDirectory().toPath(); Path baseDirectory = gameRepository.getBaseDirectory();
List<String> versions = new ArrayList<>(); List<String> versions = new ArrayList<>();
String currentVersionId = versionId; String currentVersionId = versionId;

View File

@@ -132,12 +132,12 @@ public final class Profile implements Observable {
public Profile(String name, File initialGameDir, VersionSetting global, String selectedVersion, boolean useRelativePath) { public Profile(String name, File initialGameDir, VersionSetting global, String selectedVersion, boolean useRelativePath) {
this.name = new SimpleStringProperty(this, "name", name); this.name = new SimpleStringProperty(this, "name", name);
gameDir = new SimpleObjectProperty<>(this, "gameDir", initialGameDir); gameDir = new SimpleObjectProperty<>(this, "gameDir", initialGameDir);
repository = new HMCLGameRepository(this, initialGameDir); repository = new HMCLGameRepository(this, initialGameDir.toPath());
this.global.set(global == null ? new VersionSetting() : global); this.global.set(global == null ? new VersionSetting() : global);
this.selectedVersion.set(selectedVersion); this.selectedVersion.set(selectedVersion);
this.useRelativePath.set(useRelativePath); this.useRelativePath.set(useRelativePath);
gameDir.addListener((a, b, newValue) -> repository.changeDirectory(newValue)); gameDir.addListener((a, b, newValue) -> repository.changeDirectory(newValue.toPath()));
this.selectedVersion.addListener(o -> checkSelectedVersion()); this.selectedVersion.addListener(o -> checkSelectedVersion());
listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion(), EventPriority.HIGHEST)); listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion(), EventPriority.HIGHEST));

View File

@@ -129,7 +129,7 @@ public class GameCrashWindow extends Stage {
return pair(CrashReportAnalyzer.analyze(rawLog), crashReport != null ? CrashReportAnalyzer.findKeywordsFromCrashReport(crashReport) : new HashSet<>()); return pair(CrashReportAnalyzer.analyze(rawLog), crashReport != null ? CrashReportAnalyzer.findKeywordsFromCrashReport(crashReport) : new HashSet<>());
}), Task.supplyAsync(() -> { }), Task.supplyAsync(() -> {
Path latestLog = repository.getRunDirectory(version.getId()).toPath().resolve("logs/latest.log"); Path latestLog = repository.getRunDirectory(version.getId()).resolve("logs/latest.log");
if (!Files.isReadable(latestLog)) { if (!Files.isReadable(latestLog)) {
return pair(new HashSet<CrashReportAnalyzer.Result>(), new HashSet<String>()); return pair(new HashSet<CrashReportAnalyzer.Result>(), new HashSet<String>());
} }
@@ -379,7 +379,7 @@ public class GameCrashWindow extends Stage {
TwoLineListItem gameDir = new TwoLineListItem(); TwoLineListItem gameDir = new TwoLineListItem();
gameDir.getStyleClass().setAll("two-line-item-second-large"); gameDir.getStyleClass().setAll("two-line-item-second-large");
gameDir.setTitle(i18n("game.directory")); gameDir.setTitle(i18n("game.directory"));
gameDir.setSubtitle(launchOptions.getGameDir().getAbsolutePath()); gameDir.setSubtitle(launchOptions.getGameDir().toAbsolutePath().toString());
FXUtils.installFastTooltip(gameDir, i18n("game.directory")); FXUtils.installFastTooltip(gameDir, i18n("game.directory"));
TwoLineListItem javaDir = new TwoLineListItem(); TwoLineListItem javaDir = new TwoLineListItem();

View File

@@ -145,7 +145,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
public static void download(Profile profile, @Nullable String version, RemoteMod.Version file, String subdirectoryName) { public static void download(Profile profile, @Nullable String version, RemoteMod.Version file, String subdirectoryName) {
if (version == null) version = profile.getSelectedVersion(); if (version == null) version = profile.getSelectedVersion();
Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version).toPath() : profile.getRepository().getBaseDirectory().toPath(); Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version) : profile.getRepository().getBaseDirectory();
Controllers.prompt(i18n("archive.file.name"), (result, resolve, reject) -> { Controllers.prompt(i18n("archive.file.name"), (result, resolve, reject) -> {
if (!FileUtils.isNameValid(result)) { if (!FileUtils.isNameValid(result)) {

View File

@@ -95,9 +95,9 @@ public class ModpackInstallWizardProvider implements WizardProvider {
} }
try { try {
if (serverModpackManifest != null) { if (serverModpackManifest != null) {
return ModpackHelper.getUpdateTask(profile, serverModpackManifest, modpack.getEncoding(), name, ModpackHelper.readModpackConfiguration(profile.getRepository().getModpackConfiguration(name))); return ModpackHelper.getUpdateTask(profile, serverModpackManifest, modpack.getEncoding(), name, ModpackHelper.readModpackConfiguration(profile.getRepository().getModpackConfiguration(name).toFile()));
} else { } else {
return ModpackHelper.getUpdateTask(profile, selected, modpack.getEncoding(), name, ModpackHelper.readModpackConfiguration(profile.getRepository().getModpackConfiguration(name))); return ModpackHelper.getUpdateTask(profile, selected, modpack.getEncoding(), name, ModpackHelper.readModpackConfiguration(profile.getRepository().getModpackConfiguration(name).toFile()));
} }
} catch (UnsupportedModpackException | ManuallyCreatedModpackException e) { } catch (UnsupportedModpackException | ManuallyCreatedModpackException e) {
Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageType.ERROR); Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageType.ERROR);

View File

@@ -61,7 +61,7 @@ public final class ModpackFileSelectionPage extends BorderPane implements Wizard
this.adviser = adviser; this.adviser = adviser;
JFXTreeView<String> treeView = new JFXTreeView<>(); JFXTreeView<String> treeView = new JFXTreeView<>();
rootNode = getTreeItem(profile.getRepository().getRunDirectory(version), "minecraft"); rootNode = getTreeItem(profile.getRepository().getRunDirectory(version).toFile(), "minecraft");
treeView.setRoot(rootNode); treeView.setRoot(rootNode);
treeView.setSelectionModel(new NoneMultipleSelectionModel<>()); treeView.setSelectionModel(new NoneMultipleSelectionModel<>());
this.setCenter(treeView); this.setCenter(treeView);

View File

@@ -245,12 +245,12 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
String nativesDirName = "natives-" + Platform.SYSTEM_PLATFORM; String nativesDirName = "natives-" + Platform.SYSTEM_PLATFORM;
if (versionId == null) { if (versionId == null) {
return String.format("%s/%s/%s", return String.format("%s/%s/%s",
profile.getRepository().getBaseDirectory().toPath().resolve("versions").toAbsolutePath().normalize(), profile.getRepository().getBaseDirectory().resolve("versions").toAbsolutePath().normalize(),
i18n("settings.advanced.natives_directory.default.version_id"), i18n("settings.advanced.natives_directory.default.version_id"),
nativesDirName nativesDirName
); );
} else { } else {
return profile.getRepository().getVersionRoot(versionId).toPath() return profile.getRepository().getVersionRoot(versionId)
.toAbsolutePath().normalize() .toAbsolutePath().normalize()
.resolve(nativesDirName) .resolve(nativesDirName)
.toString(); .toString();

View File

@@ -175,7 +175,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
} }
public void openModFolder() { public void openModFolder() {
FXUtils.openFolder(new File(profile.getRepository().getRunDirectory(versionId), "mods")); FXUtils.openFolder(profile.getRepository().getRunDirectory(versionId).resolve("mods").toFile());
} }
public void checkUpdates() { public void checkUpdates() {

View File

@@ -78,7 +78,7 @@ public class VersionIconDialog extends DialogPane {
File selectedFile = chooser.showOpenDialog(Controllers.getStage()); File selectedFile = chooser.showOpenDialog(Controllers.getStage());
if (selectedFile != null) { if (selectedFile != null) {
try { try {
profile.getRepository().setVersionIconFile(versionId, selectedFile); profile.getRepository().setVersionIconFile(versionId, selectedFile.toPath());
if (vs != null) { if (vs != null) {
vs.setVersionIcon(VersionIconType.DEFAULT); vs.setVersionIcon(VersionIconType.DEFAULT);

View File

@@ -45,7 +45,6 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.util.Optional; import java.util.Optional;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -158,7 +157,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
} }
private void onBrowse(String sub) { private void onBrowse(String sub) {
FXUtils.openFolder(new File(getProfile().getRepository().getRunDirectory(getVersion()), sub)); FXUtils.openFolder(getProfile().getRepository().getRunDirectory(getVersion()).resolve(sub).toFile());
} }
private void redownloadAssetIndex() { private void redownloadAssetIndex() {
@@ -166,14 +165,14 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
} }
private void clearLibraries() { private void clearLibraries() {
FileUtils.deleteDirectoryQuietly(getProfile().getRepository().getBaseDirectory().toPath().resolve("libraries")); FileUtils.deleteDirectoryQuietly(getProfile().getRepository().getBaseDirectory().resolve("libraries"));
} }
private void clearAssets() { private void clearAssets() {
HMCLGameRepository baseDirectory = getProfile().getRepository(); HMCLGameRepository baseDirectory = getProfile().getRepository();
FileUtils.deleteDirectoryQuietly(baseDirectory.getBaseDirectory().toPath().resolve("assets")); FileUtils.deleteDirectoryQuietly(baseDirectory.getBaseDirectory().resolve("assets"));
if (version.get() != null) { if (version.get() != null) {
FileUtils.deleteDirectoryQuietly(baseDirectory.getRunDirectory(version.get().getVersion()).toPath().resolve("resources")); FileUtils.deleteDirectoryQuietly(baseDirectory.getRunDirectory(version.get().getVersion()).resolve("resources"));
} }
} }

View File

@@ -56,7 +56,6 @@ import org.jackhuang.hmcl.util.platform.SystemInfo;
import org.jackhuang.hmcl.util.platform.hardware.PhysicalMemoryStatus; import org.jackhuang.hmcl.util.platform.hardware.PhysicalMemoryStatus;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.nio.file.Paths;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -635,7 +634,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
versionSetting.javaVersionProperty().addListener(javaListener); versionSetting.javaVersionProperty().addListener(javaListener);
gameDirItem.selectedDataProperty().bindBidirectional(versionSetting.gameDirTypeProperty()); gameDirItem.selectedDataProperty().bindBidirectional(versionSetting.gameDirTypeProperty());
gameDirSublist.subtitleProperty().bind(Bindings.createStringBinding(() -> Paths.get(profile.getRepository().getRunDirectory(versionId).getAbsolutePath()).normalize().toString(), gameDirSublist.subtitleProperty().bind(Bindings.createStringBinding(() -> profile.getRepository().getRunDirectory(versionId).toAbsolutePath().normalize().toString(),
versionSetting.gameDirProperty(), versionSetting.gameDirTypeProperty())); versionSetting.gameDirProperty(), versionSetting.gameDirTypeProperty()));
lastVersionSetting = versionSetting; lastVersionSetting = versionSetting;

View File

@@ -143,7 +143,7 @@ public final class Versions {
} }
public static void openFolder(Profile profile, String version) { public static void openFolder(Profile profile, String version) {
FXUtils.openFolder(profile.getRepository().getRunDirectory(version)); FXUtils.openFolder(profile.getRepository().getRunDirectory(version).toFile());
} }
public static void duplicateVersion(Profile profile, String version) { public static void duplicateVersion(Profile profile, String version) {
@@ -193,8 +193,8 @@ public final class Versions {
ensureSelectedAccount(account -> { ensureSelectedAccount(account -> {
GameRepository repository = profile.getRepository(); GameRepository repository = profile.getRepository();
FileChooser chooser = new FileChooser(); FileChooser chooser = new FileChooser();
if (repository.getRunDirectory(id).isDirectory()) if (Files.isDirectory(repository.getRunDirectory(id)))
chooser.setInitialDirectory(repository.getRunDirectory(id)); chooser.setInitialDirectory(repository.getRunDirectory(id).toFile());
chooser.setTitle(i18n("version.launch_script.save")); chooser.setTitle(i18n("version.launch_script.save"));
chooser.getExtensionFilters().add(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS chooser.getExtensionFilters().add(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
? new FileChooser.ExtensionFilter(i18n("extension.bat"), "*.bat") ? new FileChooser.ExtensionFilter(i18n("extension.bat"), "*.bat")

View File

@@ -31,7 +31,7 @@ import org.junit.jupiter.api.Test;
import java.io.File; import java.io.File;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -49,11 +49,13 @@ public class GameCrashWindowTest {
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
FXUtils.runInFX(() -> { FXUtils.runInFX(() -> {
Path workingPath = Path.of(System.getProperty("user.dir"));
GameCrashWindow window = new GameCrashWindow(process, ProcessListener.ExitType.APPLICATION_ERROR, null, GameCrashWindow window = new GameCrashWindow(process, ProcessListener.ExitType.APPLICATION_ERROR, null,
new ClassicVersion(), new ClassicVersion(),
new LaunchOptions.Builder() new LaunchOptions.Builder()
.setJava(new JavaRuntime(Paths.get("."), new JavaInfo(Platform.SYSTEM_PLATFORM, "16", null), false, false)) .setJava(new JavaRuntime(workingPath, new JavaInfo(Platform.SYSTEM_PLATFORM, "16", null), false, false))
.setGameDir(new File(".")) .setGameDir(workingPath)
.create(), .create(),
Arrays.stream(logs.split("\\n")) Arrays.stream(logs.split("\\n"))
.map(Log::new) .map(Log::new)

View File

@@ -28,9 +28,10 @@ import org.jackhuang.hmcl.game.DefaultGameRepository;
import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -79,11 +80,11 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
public Task<?> checkGameCompletionAsync(Version version, boolean integrityCheck) { public Task<?> checkGameCompletionAsync(Version version, boolean integrityCheck) {
return Task.allOf( return Task.allOf(
Task.composeAsync(() -> { Task.composeAsync(() -> {
File versionJar = repository.getVersionJar(version); Path versionJar = repository.getVersionJar(version);
if (!versionJar.exists() || versionJar.length() == 0)
return new GameDownloadTask(this, null, version); return Files.notExists(versionJar) || FileUtils.size(versionJar) == 0L
else ? new GameDownloadTask(this, null, version)
return null; : null;
}).thenComposeAsync(checkPatchCompletionAsync(version, integrityCheck)), }).thenComposeAsync(checkPatchCompletionAsync(version, integrityCheck)),
new GameAssetDownloadTask(this, version, GameAssetDownloadTask.DOWNLOAD_INDEX_IF_NECESSARY, integrityCheck) new GameAssetDownloadTask(this, version, GameAssetDownloadTask.DOWNLOAD_INDEX_IF_NECESSARY, integrityCheck)
.setSignificance(Task.TaskSignificance.MODERATE), .setSignificance(Task.TaskSignificance.MODERATE),
@@ -134,7 +135,7 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
if (GameLibrariesTask.shouldDownloadLibrary(repository, version, installer, integrityCheck)) { if (GameLibrariesTask.shouldDownloadLibrary(repository, version, installer, integrityCheck)) {
tasks.add(installLibraryAsync(gameVersion, original, "optifine", optifinePatchVersion)); tasks.add(installLibraryAsync(gameVersion, original, "optifine", optifinePatchVersion));
} else { } else {
tasks.add(OptiFineInstallTask.install(this, original, repository.getLibraryFile(version, installer).toPath())); tasks.add(OptiFineInstallTask.install(this, original, repository.getLibraryFile(version, installer)));
} }
} }
} }

View File

@@ -160,7 +160,7 @@ public class MaintainTask extends Task<Version> {
optiFine.ifPresent(library -> { optiFine.ifPresent(library -> {
builder.addJvmArgument("-Dhmcl.transformer.candidates=${library_directory}/" + library.getPath()); builder.addJvmArgument("-Dhmcl.transformer.candidates=${library_directory}/" + library.getPath());
if (!libraryExisting) builder.addLibrary(hmclTransformerDiscoveryService); if (!libraryExisting) builder.addLibrary(hmclTransformerDiscoveryService);
Path libraryPath = repository.getLibraryFile(version, hmclTransformerDiscoveryService).toPath(); Path libraryPath = repository.getLibraryFile(version, hmclTransformerDiscoveryService);
try (InputStream input = MaintainTask.class.getResourceAsStream("/assets/game/HMCLTransformerDiscoveryService-1.0.jar")) { try (InputStream input = MaintainTask.class.getResourceAsStream("/assets/game/HMCLTransformerDiscoveryService-1.0.jar")) {
Files.createDirectories(libraryPath.getParent()); Files.createDirectories(libraryPath.getParent());
Files.copy(Objects.requireNonNull(input, "Bundled HMCLTransformerDiscoveryService is missing."), libraryPath, StandardCopyOption.REPLACE_EXISTING); Files.copy(Objects.requireNonNull(input, "Bundled HMCLTransformerDiscoveryService is missing."), libraryPath, StandardCopyOption.REPLACE_EXISTING);
@@ -181,7 +181,7 @@ public class MaintainTask extends Task<Version> {
// we need to manually ignore ${primary_jar}. // we need to manually ignore ${primary_jar}.
newIgnoreList.add("${primary_jar}"); newIgnoreList.add("${primary_jar}");
Path libraryDirectory = repository.getLibrariesDirectory(version).toPath().toAbsolutePath(); Path libraryDirectory = repository.getLibrariesDirectory(version).toAbsolutePath().normalize();
// The default ignoreList is too loose and may cause some problems, we replace them with the absolute version. // 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 // For example, if "client-extra" is in ignoreList, and game directory contains "client-extra" component, all
@@ -260,7 +260,7 @@ public class MaintainTask extends Task<Version> {
Library library = libraries.get(i); Library library = libraries.get(i);
if (library.is("optifine", "OptiFine")) { if (library.is("optifine", "OptiFine")) {
Library newLibrary = new Library(new Artifact("optifine", "OptiFine", library.getVersion(), "installer")); Library newLibrary = new Library(new Artifact("optifine", "OptiFine", library.getVersion(), "installer"));
if (repository.getLibraryFile(version, newLibrary).exists()) { if (Files.exists(repository.getLibraryFile(version, newLibrary))) {
libraries.set(i, null); libraries.set(i, null);
// OptiFine should be loaded after Forge in classpath. // OptiFine should be loaded after Forge in classpath.
// Although we have altered priority of OptiFine higher than Forge, // Although we have altered priority of OptiFine higher than Forge,

View File

@@ -59,7 +59,7 @@ public final class FabricAPIInstallTask extends Task<Version> {
public void execute() throws IOException { public void execute() throws IOException {
dependencies.add(new FileDownloadTask( dependencies.add(new FileDownloadTask(
remote.getVersion().getFile().getUrl(), remote.getVersion().getFile().getUrl(),
dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("fabric-api-" + remote.getVersion().getVersion() + ".jar"), dependencyManager.getGameRepository().getRunDirectory(version.getId()).resolve("mods").resolve("fabric-api-" + remote.getVersion().getVersion() + ".jar"),
remote.getVersion().getFile().getIntegrityCheck()) remote.getVersion().getFile().getIntegrityCheck())
); );
} }

View File

@@ -284,7 +284,7 @@ public class ForgeNewInstallTask extends Task<Version> {
for (Library library : profile.getLibraries()) { for (Library library : profile.getLibraries()) {
Path file = fs.getPath("maven").resolve(library.getPath()); Path file = fs.getPath("maven").resolve(library.getPath());
if (Files.exists(file)) { if (Files.exists(file)) {
Path dest = gameRepository.getLibraryFile(version, library).toPath(); Path dest = gameRepository.getLibraryFile(version, library);
FileUtils.copyFile(file, dest); FileUtils.copyFile(file, dest);
} }
} }
@@ -391,11 +391,11 @@ public class ForgeNewInstallTask extends Task<Version> {
} }
vars.put("SIDE", "client"); vars.put("SIDE", "client");
vars.put("MINECRAFT_JAR", gameRepository.getVersionJar(version).getAbsolutePath()); vars.put("MINECRAFT_JAR", FileUtils.getAbsolutePath(gameRepository.getVersionJar(version)));
vars.put("MINECRAFT_VERSION", gameRepository.getVersionJar(version).getAbsolutePath()); vars.put("MINECRAFT_VERSION", FileUtils.getAbsolutePath(gameRepository.getVersionJar(version)));
vars.put("ROOT", gameRepository.getBaseDirectory().getAbsolutePath()); vars.put("ROOT", FileUtils.getAbsolutePath(gameRepository.getBaseDirectory()));
vars.put("INSTALLER", installer.toAbsolutePath().toString()); vars.put("INSTALLER", installer.toAbsolutePath().toString());
vars.put("LIBRARY_DIR", gameRepository.getLibrariesDirectory(version).getAbsolutePath()); vars.put("LIBRARY_DIR", FileUtils.getAbsolutePath(gameRepository.getLibrariesDirectory(version)));
updateProgress(0, processors.size()); updateProgress(0, processors.size());

View File

@@ -72,7 +72,7 @@ public class ForgeOldInstallTask extends Task<Version> {
// unpack the universal jar in the installer file. // unpack the universal jar in the installer file.
Library forgeLibrary = new Library(installProfile.getInstall().getPath()); Library forgeLibrary = new Library(installProfile.getInstall().getPath());
Path forgeFile = dependencyManager.getGameRepository().getLibraryFile(version, forgeLibrary).toPath(); Path forgeFile = dependencyManager.getGameRepository().getLibraryFile(version, forgeLibrary);
Files.createDirectories(forgeFile.getParent()); Files.createDirectories(forgeFile.getParent());
ZipEntry forgeEntry = zipFile.getEntry(installProfile.getInstall().getFilePath()); ZipEntry forgeEntry = zipFile.getEntry(installProfile.getInstall().getFilePath());

View File

@@ -53,7 +53,7 @@ public final class GameDownloadTask extends Task<Void> {
@Override @Override
public void execute() { public void execute() {
Path jar = dependencyManager.getGameRepository().getVersionJar(version).toPath(); Path jar = dependencyManager.getGameRepository().getVersionJar(version);
var task = new FileDownloadTask( var task = new FileDownloadTask(
dependencyManager.getDownloadProvider().injectURLWithCandidates(version.getDownloadInfo().getUrl()), dependencyManager.getDownloadProvider().injectURLWithCandidates(version.getDownloadInfo().getUrl()),

View File

@@ -88,7 +88,7 @@ public final class GameLibrariesTask extends Task<Void> {
} }
public static boolean shouldDownloadLibrary(GameRepository gameRepository, Version version, Library library, boolean integrityCheck) { public static boolean shouldDownloadLibrary(GameRepository gameRepository, Version version, Library library, boolean integrityCheck) {
Path file = gameRepository.getLibraryFile(version, library).toPath(); Path file = gameRepository.getLibraryFile(version, library);
if (!Files.isRegularFile(file)) return true; if (!Files.isRegularFile(file)) return true;
if (!integrityCheck) { if (!integrityCheck) {
@@ -142,8 +142,7 @@ public final class GameLibrariesTask extends Task<Void> {
&& gameRepository instanceof DefaultGameRepository defaultGameRepository) { && gameRepository instanceof DefaultGameRepository defaultGameRepository) {
List<FMLLib> fmlLibs = getFMLLibs(library.getVersion()); List<FMLLib> fmlLibs = getFMLLibs(library.getVersion());
if (fmlLibs != null) { if (fmlLibs != null) {
Path libDir = defaultGameRepository.getBaseDirectory().toPath() Path libDir = defaultGameRepository.getBaseDirectory().resolve("lib")
.resolve("lib")
.toAbsolutePath().normalize(); .toAbsolutePath().normalize();
for (FMLLib fmlLib : fmlLibs) { for (FMLLib fmlLib : fmlLibs) {
@@ -158,7 +157,7 @@ public final class GameLibrariesTask extends Task<Void> {
} }
} }
Path file = gameRepository.getLibraryFile(version, library).toPath(); Path file = gameRepository.getLibraryFile(version, library);
if ("optifine".equals(library.getGroupId()) && Files.exists(file) && GameVersionNumber.asGameVersion(gameRepository.getGameVersion(version)).compareTo("1.20.4") == 0) { if ("optifine".equals(library.getGroupId()) && Files.exists(file) && GameVersionNumber.asGameVersion(gameRepository.getGameVersion(version)).compareTo("1.20.4") == 0) {
String forgeVersion = LibraryAnalyzer.analyze(version, "1.20.4") String forgeVersion = LibraryAnalyzer.analyze(version, "1.20.4")
.getVersion(LibraryAnalyzer.LibraryType.FORGE) .getVersion(LibraryAnalyzer.LibraryType.FORGE)

View File

@@ -62,7 +62,7 @@ public final class GameVerificationFixTask extends Task<Void> {
@Override @Override
public void execute() throws IOException { public void execute() throws IOException {
Path jar = dependencyManager.getGameRepository().getVersionJar(version).toPath(); Path jar = dependencyManager.getGameRepository().getVersionJar(version);
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, gameVersion); LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, gameVersion);
if (Files.exists(jar) && GameVersionNumber.compare(gameVersion, "1.6") < 0 && analyzer.has(LibraryAnalyzer.LibraryType.FORGE)) { if (Files.exists(jar) && GameVersionNumber.compare(gameVersion, "1.6") < 0 && analyzer.has(LibraryAnalyzer.LibraryType.FORGE)) {

View File

@@ -51,7 +51,7 @@ public final class VersionJsonSaveTask extends Task<Version> {
@Override @Override
public void execute() throws Exception { public void execute() throws Exception {
Path json = repository.getVersionJson(version.getId()).toPath().toAbsolutePath(); Path json = repository.getVersionJson(version.getId()).toAbsolutePath();
Files.createDirectories(json.getParent()); Files.createDirectories(json.getParent());
JsonUtils.writeToJsonFile(json, version); JsonUtils.writeToJsonFile(json, version);
} }

View File

@@ -280,7 +280,7 @@ public class NeoForgeOldInstallTask extends Task<Version> {
for (Library library : profile.getLibraries()) { for (Library library : profile.getLibraries()) {
Path file = fs.getPath("maven").resolve(library.getPath()); Path file = fs.getPath("maven").resolve(library.getPath());
if (Files.exists(file)) { if (Files.exists(file)) {
Path dest = gameRepository.getLibraryFile(version, library).toPath(); Path dest = gameRepository.getLibraryFile(version, library);
FileUtils.copyFile(file, dest); FileUtils.copyFile(file, dest);
} }
} }
@@ -387,11 +387,11 @@ public class NeoForgeOldInstallTask extends Task<Version> {
} }
vars.put("SIDE", "client"); vars.put("SIDE", "client");
vars.put("MINECRAFT_JAR", gameRepository.getVersionJar(version).getAbsolutePath()); vars.put("MINECRAFT_JAR", FileUtils.getAbsolutePath(gameRepository.getVersionJar(version)));
vars.put("MINECRAFT_VERSION", gameRepository.getVersionJar(version).getAbsolutePath()); vars.put("MINECRAFT_VERSION", FileUtils.getAbsolutePath(gameRepository.getVersionJar(version)));
vars.put("ROOT", gameRepository.getBaseDirectory().getAbsolutePath()); vars.put("ROOT", FileUtils.getAbsolutePath(gameRepository.getBaseDirectory()));
vars.put("INSTALLER", installer.toAbsolutePath().toString()); vars.put("INSTALLER", installer.toAbsolutePath().toString());
vars.put("LIBRARY_DIR", gameRepository.getLibrariesDirectory(version).getAbsolutePath()); vars.put("LIBRARY_DIR", FileUtils.getAbsolutePath(gameRepository.getLibrariesDirectory(version)));
updateProgress(0, processors.size()); updateProgress(0, processors.size());

View File

@@ -35,7 +35,6 @@ import org.jenkinsci.constant_pool_scanner.ConstantPoolScanner;
import org.jenkinsci.constant_pool_scanner.ConstantType; import org.jenkinsci.constant_pool_scanner.ConstantType;
import org.jenkinsci.constant_pool_scanner.Utf8Constant; import org.jenkinsci.constant_pool_scanner.Utf8Constant;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.FileSystem; import java.nio.file.FileSystem;
import java.nio.file.Files; import java.nio.file.Files;
@@ -131,7 +130,7 @@ public final class OptiFineInstallTask extends Task<Version> {
List<Library> libraries = new ArrayList<>(4); List<Library> libraries = new ArrayList<>(4);
libraries.add(optiFineLibrary); libraries.add(optiFineLibrary);
Path optiFineInstallerLibraryPath = gameRepository.getLibraryFile(version, optiFineInstallerLibrary).toPath(); Path optiFineInstallerLibraryPath = gameRepository.getLibraryFile(version, optiFineInstallerLibrary);
FileUtils.copyFile(dest, optiFineInstallerLibraryPath); FileUtils.copyFile(dest, optiFineInstallerLibraryPath);
try (FileSystem fs2 = CompressingUtils.createWritableZipFileSystem(optiFineInstallerLibraryPath)) { try (FileSystem fs2 = CompressingUtils.createWritableZipFileSystem(optiFineInstallerLibraryPath)) {
@@ -141,14 +140,14 @@ public final class OptiFineInstallTask extends Task<Version> {
// Install launch wrapper modified by OptiFine // Install launch wrapper modified by OptiFine
boolean hasLaunchWrapper = false; boolean hasLaunchWrapper = false;
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(dest)) { try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(dest)) {
Path optiFineLibraryPath = gameRepository.getLibraryFile(version, optiFineLibrary).toPath(); Path optiFineLibraryPath = gameRepository.getLibraryFile(version, optiFineLibrary);
if (Files.exists(fs.getPath("optifine/Patcher.class"))) { if (Files.exists(fs.getPath("optifine/Patcher.class"))) {
String[] command = { String[] command = {
JavaRuntime.getDefault().getBinary().toString(), JavaRuntime.getDefault().getBinary().toString(),
"-cp", "-cp",
dest.toString(), dest.toString(),
"optifine.Patcher", "optifine.Patcher",
gameRepository.getVersionJar(version).getAbsolutePath(), gameRepository.getVersionJar(version).toAbsolutePath().normalize().toString(),
dest.toString(), dest.toString(),
optiFineLibraryPath.toString() optiFineLibraryPath.toString()
}; };
@@ -166,9 +165,9 @@ public final class OptiFineInstallTask extends Task<Version> {
Path launchWrapper2 = fs.getPath("launchwrapper-2.0.jar"); Path launchWrapper2 = fs.getPath("launchwrapper-2.0.jar");
if (Files.exists(launchWrapper2)) { if (Files.exists(launchWrapper2)) {
Library launchWrapper = new Library(new Artifact("optifine", "launchwrapper", "2.0")); Library launchWrapper = new Library(new Artifact("optifine", "launchwrapper", "2.0"));
File launchWrapperFile = gameRepository.getLibraryFile(version, launchWrapper); Path launchWrapperFile = gameRepository.getLibraryFile(version, launchWrapper);
Files.createDirectories(launchWrapperFile.toPath().toAbsolutePath().getParent()); Files.createDirectories(launchWrapperFile.toAbsolutePath().getParent());
FileUtils.copyFile(launchWrapper2, launchWrapperFile.toPath()); FileUtils.copyFile(launchWrapper2, launchWrapperFile);
hasLaunchWrapper = true; hasLaunchWrapper = true;
libraries.add(launchWrapper); libraries.add(launchWrapper);
} }
@@ -181,9 +180,9 @@ public final class OptiFineInstallTask extends Task<Version> {
Library launchWrapper = new Library(new Artifact("optifine", "launchwrapper-of", launchWrapperVersion)); Library launchWrapper = new Library(new Artifact("optifine", "launchwrapper-of", launchWrapperVersion));
if (Files.exists(launchWrapperJar)) { if (Files.exists(launchWrapperJar)) {
File launchWrapperFile = gameRepository.getLibraryFile(version, launchWrapper); Path launchWrapperFile = gameRepository.getLibraryFile(version, launchWrapper);
Files.createDirectories(launchWrapperFile.toPath().toAbsolutePath().getParent()); Files.createDirectories(launchWrapperFile.toAbsolutePath().getParent());
FileUtils.copyFile(launchWrapperJar, launchWrapperFile.toPath()); FileUtils.copyFile(launchWrapperJar, launchWrapperFile);
hasLaunchWrapper = true; hasLaunchWrapper = true;
libraries.add(launchWrapper); libraries.add(launchWrapper);

View File

@@ -59,7 +59,7 @@ public final class QuiltAPIInstallTask extends Task<Version> {
public void execute() throws IOException { public void execute() throws IOException {
dependencies.add(new FileDownloadTask( dependencies.add(new FileDownloadTask(
remote.getVersion().getFile().getUrl(), remote.getVersion().getFile().getUrl(),
dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("quilt-api-" + remote.getVersion().getVersion() + ".jar"), dependencyManager.getGameRepository().getRunDirectory(version.getId()).resolve("mods").resolve("quilt-api-" + remote.getVersion().getVersion() + ".jar"),
remote.getVersion().getFile().getIntegrityCheck()) remote.getVersion().getFile().getIntegrityCheck())
); );
} }

View File

@@ -17,16 +17,15 @@
*/ */
package org.jackhuang.hmcl.game; package org.jackhuang.hmcl.game;
import java.io.File; import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant; import java.time.Instant;
import java.util.Arrays; import java.util.Arrays;
/** /// The Minecraft version for 1.5.x and earlier.
* The Minecraft version for 1.5.x and earlier. ///
* /// @author huangyuhui
* @author huangyuhui public final class ClassicVersion extends Version {
*/
public class ClassicVersion extends Version {
public ClassicVersion() { public ClassicVersion() {
super(true, "Classic", null, null, "${auth_player_name} ${auth_session} --workDir ${game_directory}", super(true, "Classic", null, null, "${auth_player_name} ${auth_session} --workDir ${game_directory}",
@@ -35,8 +34,7 @@ public class ClassicVersion extends Version {
null, null, null, ReleaseType.UNKNOWN, Instant.now(), Instant.now(), 0, false, false, null); null, null, null, ReleaseType.UNKNOWN, Instant.now(), Instant.now(), 0, false, false, null);
} }
private static class ClassicLibrary extends Library { private static final class ClassicLibrary extends Library {
public ClassicLibrary(String name) { public ClassicLibrary(String name) {
super(new Artifact("", "", ""), null, super(new Artifact("", "", ""), null,
new LibrariesDownloadInfo(new LibraryDownloadInfo("bin/" + name + ".jar"), null), new LibrariesDownloadInfo(new LibraryDownloadInfo("bin/" + name + ".jar"), null),
@@ -44,11 +42,11 @@ public class ClassicVersion extends Version {
} }
} }
public static boolean hasClassicVersion(File baseDirectory) { public static boolean hasClassicVersion(Path baseDirectory) {
File bin = new File(baseDirectory, "bin"); Path bin = baseDirectory.resolve("bin");
return bin.exists() return Files.isDirectory(bin)
&& new File(bin, "lwjgl.jar").exists() && Files.exists(bin.resolve("lwjgl.jar"))
&& new File(bin, "jinput.jar").exists() && Files.exists(bin.resolve("jinput.jar"))
&& new File(bin, "lwjgl_util.jar").exists(); && Files.exists(bin.resolve("lwjgl_util.jar"));
} }
} }

View File

@@ -32,11 +32,11 @@ import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.platform.Platform; import org.jackhuang.hmcl.util.platform.Platform;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.InvalidPathException; import java.nio.file.InvalidPathException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -50,19 +50,19 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
*/ */
public class DefaultGameRepository implements GameRepository { public class DefaultGameRepository implements GameRepository {
private File baseDirectory; private Path baseDirectory;
protected Map<String, Version> versions; protected Map<String, Version> versions;
private final ConcurrentHashMap<File, Optional<String>> gameVersions = new ConcurrentHashMap<>(); private final ConcurrentHashMap<Path, Optional<String>> gameVersions = new ConcurrentHashMap<>();
public DefaultGameRepository(File baseDirectory) { public DefaultGameRepository(Path baseDirectory) {
this.baseDirectory = baseDirectory; this.baseDirectory = baseDirectory;
} }
public File getBaseDirectory() { public Path getBaseDirectory() {
return baseDirectory; return baseDirectory;
} }
public void setBaseDirectory(File baseDirectory) { public void setBaseDirectory(Path baseDirectory) {
this.baseDirectory = baseDirectory; this.baseDirectory = baseDirectory;
} }
@@ -89,25 +89,25 @@ public class DefaultGameRepository implements GameRepository {
} }
@Override @Override
public File getLibrariesDirectory(Version version) { public Path getLibrariesDirectory(Version version) {
return new File(getBaseDirectory(), "libraries"); return getBaseDirectory().resolve("libraries");
} }
@Override @Override
public File getLibraryFile(Version version, Library lib) { public Path getLibraryFile(Version version, Library lib) {
if ("local".equals(lib.getHint())) { if ("local".equals(lib.getHint())) {
if (lib.getFileName() != null) { if (lib.getFileName() != null) {
return new File(getVersionRoot(version.getId()), "libraries/" + lib.getFileName()); return getVersionRoot(version.getId()).resolve("libraries/" + lib.getFileName());
} }
return new File(getVersionRoot(version.getId()), "libraries/" + lib.getArtifact().getFileName()); return getVersionRoot(version.getId()).resolve("libraries/" + lib.getArtifact().getFileName());
} }
return new File(getLibrariesDirectory(version), lib.getPath()); return getLibrariesDirectory(version).resolve(lib.getPath());
} }
public Path getArtifactFile(Version version, Artifact artifact) { public Path getArtifactFile(Version version, Artifact artifact) {
return artifact.getPath(getBaseDirectory().toPath().resolve("libraries")); return artifact.getPath(getBaseDirectory().resolve("libraries"));
} }
public GameDirectoryType getGameDirectoryType(String id) { public GameDirectoryType getGameDirectoryType(String id) {
@@ -115,22 +115,19 @@ public class DefaultGameRepository implements GameRepository {
} }
@Override @Override
public File getRunDirectory(String id) { public Path getRunDirectory(String id) {
switch (getGameDirectoryType(id)) { return switch (getGameDirectoryType(id)) {
case VERSION_FOLDER: case VERSION_FOLDER -> getVersionRoot(id);
return getVersionRoot(id); case ROOT_FOLDER -> getBaseDirectory();
case ROOT_FOLDER: default -> throw new IllegalStateException();
return getBaseDirectory(); };
default:
throw new IllegalStateException();
}
} }
@Override @Override
public File getVersionJar(Version version) { public Path getVersionJar(Version version) {
Version v = version.resolve(this); Version v = version.resolve(this);
String id = Optional.ofNullable(v.getJar()).orElse(v.getId()); String id = Optional.ofNullable(v.getJar()).orElse(v.getId());
return new File(getVersionRoot(id), id + ".jar"); return getVersionRoot(id).resolve(id + ".jar");
} }
@Override @Override
@@ -140,33 +137,33 @@ public class DefaultGameRepository implements GameRepository {
// be consistent. // be consistent.
return gameVersions.computeIfAbsent(getVersionJar(version), versionJar -> { return gameVersions.computeIfAbsent(getVersionJar(version), versionJar -> {
Optional<String> gameVersion = GameVersion.minecraftVersion(versionJar); Optional<String> gameVersion = GameVersion.minecraftVersion(versionJar);
if (!gameVersion.isPresent()) { if (gameVersion.isEmpty()) {
LOG.warning("Cannot find out game version of " + version.getId() + ", primary jar: " + versionJar.toString() + ", jar exists: " + versionJar.exists()); LOG.warning("Cannot find out game version of " + version.getId() + ", primary jar: " + versionJar.toString() + ", jar exists: " + Files.exists(versionJar));
} }
return gameVersion; return gameVersion;
}); });
} }
@Override @Override
public File getNativeDirectory(String id, Platform platform) { public Path getNativeDirectory(String id, Platform platform) {
return new File(getVersionRoot(id), "natives-" + platform); return getVersionRoot(id).resolve("natives-" + platform);
} }
@Override @Override
public File getVersionRoot(String id) { public Path getVersionRoot(String id) {
return new File(getBaseDirectory(), "versions/" + id); return getBaseDirectory().resolve("versions/" + id);
} }
public File getVersionJson(String id) { public Path getVersionJson(String id) {
return new File(getVersionRoot(id), id + ".json"); return getVersionRoot(id).resolve(id + ".json");
} }
public Version readVersionJson(String id) throws IOException, JsonParseException { public Version readVersionJson(String id) throws IOException, JsonParseException {
return readVersionJson(getVersionJson(id)); return readVersionJson(getVersionJson(id));
} }
public Version readVersionJson(File file) throws IOException, JsonParseException { public Version readVersionJson(Path file) throws IOException, JsonParseException {
String jsonText = Files.readString(file.toPath()); String jsonText = Files.readString(file);
try { try {
// Try TLauncher version json format // Try TLauncher version json format
return JsonUtils.fromNonNullJson(jsonText, TLauncherVersion.class).toVersion(); return JsonUtils.fromNonNullJson(jsonText, TLauncherVersion.class).toVersion();
@@ -179,7 +176,7 @@ public class DefaultGameRepository implements GameRepository {
} catch (JsonParseException ignored) { } catch (JsonParseException ignored) {
} }
LOG.warning("Cannot parse version json: " + file.toString() + "\n" + jsonText); LOG.warning("Cannot parse version json: " + file + "\n" + jsonText);
throw new JsonParseException("Version json incorrect"); throw new JsonParseException("Version json incorrect");
} }
@@ -190,8 +187,8 @@ public class DefaultGameRepository implements GameRepository {
try { try {
Version fromVersion = getVersion(from); Version fromVersion = getVersion(from);
Path fromDir = getVersionRoot(from).toPath(); Path fromDir = getVersionRoot(from);
Path toDir = getVersionRoot(to).toPath(); Path toDir = getVersionRoot(to);
Files.move(fromDir, toDir); Files.move(fromDir, toDir);
Path fromJson = toDir.resolve(from + ".json"); Path fromJson = toDir.resolve(from + ".json");
@@ -219,7 +216,7 @@ public class DefaultGameRepository implements GameRepository {
// fix inheritsFrom of versions that inherits from version [from]. // fix inheritsFrom of versions that inherits from version [from].
for (Version version : getVersions()) { for (Version version : getVersions()) {
if (from.equals(version.getInheritsFrom())) { if (from.equals(version.getInheritsFrom())) {
Path targetPath = getVersionJson(version.getId()).toPath(); Path targetPath = getVersionJson(version.getId());
Files.createDirectories(targetPath.getParent()); Files.createDirectories(targetPath.getParent());
JsonUtils.writeToJsonFile(targetPath, version.setInheritsFrom(to)); JsonUtils.writeToJsonFile(targetPath, version.setInheritsFrom(to));
} }
@@ -235,25 +232,29 @@ public class DefaultGameRepository implements GameRepository {
if (EventBus.EVENT_BUS.fireEvent(new RemoveVersionEvent(this, id)) == Event.Result.DENY) if (EventBus.EVENT_BUS.fireEvent(new RemoveVersionEvent(this, id)) == Event.Result.DENY)
return false; return false;
if (!versions.containsKey(id)) if (!versions.containsKey(id))
return FileUtils.deleteDirectoryQuietly(getVersionRoot(id).toPath()); return FileUtils.deleteDirectoryQuietly(getVersionRoot(id));
File file = getVersionRoot(id); Path file = getVersionRoot(id);
if (!file.exists()) if (Files.notExists(file))
return true; return true;
// test if no file in this version directory is occupied. // test if no file in this version directory is occupied.
File removedFile = new File(file.getAbsoluteFile().getParentFile(), file.getName() + "_removed"); Path removedFile = file.toAbsolutePath().resolveSibling(FileUtils.getName(file) + "_removed");
if (!file.renameTo(removedFile)) try {
Files.move(file, removedFile, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
LOG.warning("Failed to rename file " + file, e);
return false; return false;
}
try { try {
versions.remove(id); versions.remove(id);
if (FileUtils.moveToTrash(removedFile.toPath())) { if (FileUtils.moveToTrash(removedFile)) {
return true; return true;
} }
// remove json files first to ensure HMCL will not recognize this folder as a valid version. // remove json files first to ensure HMCL will not recognize this folder as a valid version.
for (Path path : FileUtils.listFilesByExtension(removedFile.toPath(), "json")) { for (Path path : FileUtils.listFilesByExtension(removedFile, "json")) {
try { try {
Files.delete(path); Files.delete(path);
} catch (IOException e) { } catch (IOException e) {
@@ -263,7 +264,7 @@ public class DefaultGameRepository implements GameRepository {
// remove the version from version list regardless of whether the directory was removed successfully or not. // remove the version from version list regardless of whether the directory was removed successfully or not.
try { try {
FileUtils.deleteDirectory(removedFile.toPath()); FileUtils.deleteDirectory(removedFile);
} catch (IOException e) { } catch (IOException e) {
LOG.warning("Unable to remove version folder: " + file, e); LOG.warning("Unable to remove version folder: " + file, e);
} }
@@ -283,83 +284,95 @@ public class DefaultGameRepository implements GameRepository {
SimpleVersionProvider provider = new SimpleVersionProvider(); SimpleVersionProvider provider = new SimpleVersionProvider();
File[] files = new File(getBaseDirectory(), "versions").listFiles(); Path versionsDir = getBaseDirectory().resolve("versions");
if (files != null) if (Files.isDirectory(versionsDir)) {
Arrays.stream(files).parallel().filter(File::isDirectory).flatMap(dir -> { try (Stream<Path> stream = Files.list(versionsDir)) {
String id = dir.getName(); stream.parallel().filter(Files::isDirectory).flatMap(dir -> {
File json = new File(dir, id + ".json"); String id = FileUtils.getName(dir);
Path json = dir.resolve(id + ".json");
// If user renamed the json file by mistake or created the json file in a wrong name, // If user renamed the json file by mistake or created the json file in a wrong name,
// we will find the only json and rename it to correct name. // we will find the only json and rename it to correct name.
if (!json.exists()) { if (Files.notExists(json)) {
List<Path> jsons = FileUtils.listFilesByExtension(dir.toPath(), "json"); List<Path> jsons = FileUtils.listFilesByExtension(dir, "json");
if (jsons.size() == 1) { if (jsons.size() == 1) {
LOG.info("Renaming json file " + jsons.get(0) + " to " + json); LOG.info("Renaming json file " + jsons.get(0) + " to " + json);
if (!jsons.get(0).toFile().renameTo(json)) {
LOG.warning("Cannot rename json file, ignoring version " + id); try {
Files.move(jsons.get(0), json);
} catch (IOException e) {
LOG.warning("Cannot rename json file, ignoring version " + id, e);
return Stream.empty();
}
Path jar = dir.resolve(FileUtils.getNameWithoutExtension(jsons.get(0)) + ".jar");
if (Files.exists(jar)) {
try {
Files.move(jar, dir.resolve(id + ".jar"));
} catch (IOException e) {
LOG.warning("Cannot rename jar file, ignoring version " + id, e);
return Stream.empty();
}
}
} else {
LOG.info("No available json file found, ignoring version " + id);
return Stream.empty(); return Stream.empty();
} }
File jar = new File(dir, FileUtils.getNameWithoutExtension(jsons.get(0)) + ".jar");
if (jar.exists() && !jar.renameTo(new File(dir, id + ".jar"))) {
LOG.warning("Cannot rename jar file, ignoring version " + id);
return Stream.empty();
}
} else {
LOG.info("No available json file found, ignoring version " + id);
return Stream.empty();
} }
}
Version version;
try {
version = readVersionJson(json);
} catch (Exception e) {
LOG.warning("Malformed version json " + id, e);
// JsonSyntaxException or IOException or NullPointerException(!!)
if (EventBus.EVENT_BUS.fireEvent(new GameJsonParseFailedEvent(this, json, id)) != Event.Result.ALLOW)
return Stream.empty();
Version version;
try { try {
version = readVersionJson(json); version = readVersionJson(json);
} catch (Exception e2) { } catch (Exception e) {
LOG.error("User corrected version json is still malformed", e2); LOG.warning("Malformed version json " + id, e);
return Stream.empty(); // JsonSyntaxException or IOException or NullPointerException(!!)
} if (EventBus.EVENT_BUS.fireEvent(new GameJsonParseFailedEvent(this, json.toFile(), id)) != Event.Result.ALLOW)
} return Stream.empty();
if (!id.equals(version.getId())) {
try {
String from = id;
String to = version.getId();
Path fromDir = getVersionRoot(from).toPath();
Path toDir = getVersionRoot(to).toPath();
Files.move(fromDir, toDir);
Path fromJson = toDir.resolve(from + ".json");
Path fromJar = toDir.resolve(from + ".jar");
Path toJson = toDir.resolve(to + ".json");
Path toJar = toDir.resolve(to + ".jar");
try { try {
Files.move(fromJson, toJson); version = readVersionJson(json);
if (Files.exists(fromJar)) } catch (Exception e2) {
Files.move(fromJar, toJar); LOG.error("User corrected version json is still malformed", e2);
} catch (IOException e) { return Stream.empty();
// recovery
Lang.ignoringException(() -> Files.move(toJson, fromJson));
Lang.ignoringException(() -> Files.move(toJar, fromJar));
Lang.ignoringException(() -> Files.move(toDir, fromDir));
throw e;
} }
} catch (IOException e) {
LOG.warning("Ignoring version " + version.getId() + " because version id does not match folder name " + id + ", and we cannot correct it.", e);
return Stream.empty();
} }
}
return Stream.of(version); if (!id.equals(version.getId())) {
}).forEachOrdered(provider::addVersion); try {
String from = id;
String to = version.getId();
Path fromDir = getVersionRoot(from);
Path toDir = getVersionRoot(to);
Files.move(fromDir, toDir);
Path fromJson = toDir.resolve(from + ".json");
Path fromJar = toDir.resolve(from + ".jar");
Path toJson = toDir.resolve(to + ".json");
Path toJar = toDir.resolve(to + ".jar");
try {
Files.move(fromJson, toJson);
if (Files.exists(fromJar))
Files.move(fromJar, toJar);
} catch (IOException e) {
// recovery
Lang.ignoringException(() -> Files.move(toJson, fromJson));
Lang.ignoringException(() -> Files.move(toJar, fromJar));
Lang.ignoringException(() -> Files.move(toDir, fromDir));
throw e;
}
} catch (IOException e) {
LOG.warning("Ignoring version " + version.getId() + " because version id does not match folder name " + id + ", and we cannot correct it.", e);
return Stream.empty();
}
}
return Stream.of(version);
}).forEachOrdered(provider::addVersion);
} catch (IOException e) {
LOG.warning("Failed to load versions from " + versionsDir, e);
}
}
for (Version version : provider.getVersionMap().values()) { for (Version version : provider.getVersionMap().values()) {
try { try {
@@ -407,7 +420,7 @@ public class DefaultGameRepository implements GameRepository {
@Override @Override
public Path getAssetDirectory(String version, String assetId) { public Path getAssetDirectory(String version, String assetId) {
return getBaseDirectory().toPath().resolve("assets"); return getBaseDirectory().resolve("assets");
} }
@Override @Override
@@ -456,7 +469,7 @@ public class DefaultGameRepository implements GameRepository {
return assetsDir; return assetsDir;
if (index.isVirtual()) { if (index.isVirtual()) {
Path resourcesDir = getRunDirectory(version).toPath().resolve("resources"); Path resourcesDir = getRunDirectory(version).resolve("resources");
int cnt = 0; int cnt = 0;
int tot = index.getObjects().size(); int tot = index.getObjects().size();
@@ -499,8 +512,8 @@ public class DefaultGameRepository implements GameRepository {
return versions != null; return versions != null;
} }
public File getModpackConfiguration(String version) { public Path getModpackConfiguration(String version) {
return new File(getVersionRoot(version), "modpack.json"); return getVersionRoot(version).resolve("modpack.json");
} }
/** /**
@@ -514,13 +527,13 @@ public class DefaultGameRepository implements GameRepository {
@Nullable @Nullable
public ModpackConfiguration<?> readModpackConfiguration(String version) throws IOException, VersionNotFoundException { public ModpackConfiguration<?> readModpackConfiguration(String version) throws IOException, VersionNotFoundException {
if (!hasVersion(version)) throw new VersionNotFoundException(version); if (!hasVersion(version)) throw new VersionNotFoundException(version);
File file = getModpackConfiguration(version); Path file = getModpackConfiguration(version);
if (!file.exists()) return null; if (Files.notExists(file)) return null;
return JsonUtils.fromJsonFile(file.toPath(), ModpackConfiguration.class); return JsonUtils.fromJsonFile(file, ModpackConfiguration.class);
} }
public boolean isModpack(String version) { public boolean isModpack(String version) {
return getModpackConfiguration(version).exists(); return Files.exists(getModpackConfiguration(version));
} }
public ModManager getModManager(String version) { public ModManager getModManager(String version) {
@@ -528,15 +541,15 @@ public class DefaultGameRepository implements GameRepository {
} }
public Path getSavesDirectory(String id) { public Path getSavesDirectory(String id) {
return getRunDirectory(id).toPath().resolve("saves"); return getRunDirectory(id).resolve("saves");
} }
public Path getBackupsDirectory(String id) { public Path getBackupsDirectory(String id) {
return getRunDirectory(id).toPath().resolve("backups"); return getRunDirectory(id).resolve("backups");
} }
public Path getSchematicsDirectory(String id) { public Path getSchematicsDirectory(String id) {
return getRunDirectory(id).toPath().resolve("schematics"); return getRunDirectory(id).resolve("schematics");
} }
@Override @Override

View File

@@ -18,10 +18,11 @@
package org.jackhuang.hmcl.game; package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.platform.Platform; import org.jackhuang.hmcl.util.platform.Platform;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
@@ -30,7 +31,7 @@ import java.util.Set;
/** /**
* Supports operations on versioning. * Supports operations on versioning.
* * <p>
* Note that game repository will not do any operations which need connection with Internet, if do, * Note that game repository will not do any operations which need connection with Internet, if do,
* see {@link org.jackhuang.hmcl.download.DependencyManager} * see {@link org.jackhuang.hmcl.download.DependencyManager}
* *
@@ -79,7 +80,7 @@ public interface GameRepository extends VersionProvider {
/** /**
* Load version list. * Load version list.
* * <p>
* This method should be called before launching a version. * This method should be called before launching a version.
* A time-costly operation. * A time-costly operation.
* You'd better execute this method in a new thread. * You'd better execute this method in a new thread.
@@ -95,16 +96,16 @@ public interface GameRepository extends VersionProvider {
* The root folders the versions must be unique. * The root folders the versions must be unique.
* For example, .minecraft/versions/&lt;version name&gt;/. * For example, .minecraft/versions/&lt;version name&gt;/.
*/ */
File getVersionRoot(String id); Path getVersionRoot(String id);
/** /**
* Gets the current running directory of the given version for game. * Gets the current running directory of the given version for game.
* *
* @param id the version id * @param id the version id
*/ */
File getRunDirectory(String id); Path getRunDirectory(String id);
File getLibrariesDirectory(Version version); Path getLibrariesDirectory(Version version);
/** /**
* Get the library file in disk. * Get the library file in disk.
@@ -114,11 +115,11 @@ public interface GameRepository extends VersionProvider {
* @param lib the library, {@link Version#getLibraries()} * @param lib the library, {@link Version#getLibraries()}
* @return the library file * @return the library file
*/ */
File getLibraryFile(Version version, Library lib); Path getLibraryFile(Version version, Library lib);
/** /**
* Get the directory that native libraries will be unzipped to. * Get the directory that native libraries will be unzipped to.
* * <p>
* You'd better return a unique directory. * You'd better return a unique directory.
* Or if it returns a temporary directory, {@link org.jackhuang.hmcl.launch.Launcher#makeLaunchScript} will fail. * Or if it returns a temporary directory, {@link org.jackhuang.hmcl.launch.Launcher#makeLaunchScript} will fail.
* If you do want to return a temporary directory, make {@link org.jackhuang.hmcl.launch.Launcher#makeLaunchScript} * If you do want to return a temporary directory, make {@link org.jackhuang.hmcl.launch.Launcher#makeLaunchScript}
@@ -128,7 +129,7 @@ public interface GameRepository extends VersionProvider {
* @param platform the platform of native libraries * @param platform the platform of native libraries
* @return the native directory * @return the native directory
*/ */
File getNativeDirectory(String id, Platform platform); Path getNativeDirectory(String id, Platform platform);
/** /**
* Get minecraft jar * Get minecraft jar
@@ -136,11 +137,11 @@ public interface GameRepository extends VersionProvider {
* @param version resolvedVersion * @param version resolvedVersion
* @return the minecraft jar * @return the minecraft jar
*/ */
File getVersionJar(Version version); Path getVersionJar(Version version);
/** /**
* Detect game version. * Detect game version.
* * <p>
* This method is time-consuming, but the result will be cached. * This method is time-consuming, but the result will be cached.
* Consider running this job in IO scheduler. * Consider running this job in IO scheduler.
* *
@@ -151,7 +152,7 @@ public interface GameRepository extends VersionProvider {
/** /**
* Detect game version. * Detect game version.
* * <p>
* This method is time-consuming, but the result will be cached. * This method is time-consuming, but the result will be cached.
* Consider running this job in IO scheduler. * Consider running this job in IO scheduler.
* *
@@ -168,7 +169,7 @@ public interface GameRepository extends VersionProvider {
* @param version version id * @param version version id
* @return the minecraft jar * @return the minecraft jar
*/ */
default File getVersionJar(String version) throws VersionNotFoundException { default Path getVersionJar(String version) throws VersionNotFoundException {
return getVersionJar(getVersion(version).resolve(this)); return getVersionJar(getVersion(version).resolve(this));
} }
@@ -254,9 +255,9 @@ public interface GameRepository extends VersionProvider {
Set<String> classpath = new LinkedHashSet<>(); Set<String> classpath = new LinkedHashSet<>();
for (Library library : version.getLibraries()) for (Library library : version.getLibraries())
if (library.appliesToCurrentEnvironment() && !library.isNative()) { if (library.appliesToCurrentEnvironment() && !library.isNative()) {
File f = getLibraryFile(version, library); Path f = getLibraryFile(version, library);
if (f.exists() && f.isFile()) if (Files.isRegularFile(f))
classpath.add(f.getAbsolutePath()); classpath.add(FileUtils.getAbsolutePath(f));
} }
return classpath; return classpath;
} }

View File

@@ -24,9 +24,10 @@ import org.jenkinsci.constant_pool_scanner.ConstantPoolScanner;
import org.jenkinsci.constant_pool_scanner.ConstantType; import org.jenkinsci.constant_pool_scanner.ConstantType;
import org.jenkinsci.constant_pool_scanner.StringConstant; import org.jenkinsci.constant_pool_scanner.StringConstant;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@@ -89,11 +90,11 @@ final class GameVersion {
return Optional.empty(); return Optional.empty();
} }
public static Optional<String> minecraftVersion(File file) { public static Optional<String> minecraftVersion(Path file) {
if (file == null || !file.exists() || !file.isFile() || !file.canRead()) if (file == null || !Files.isRegularFile(file))
return Optional.empty(); return Optional.empty();
try (ZipFile gameJar = new ZipFile(file)) { try (var gameJar = new ZipFile(file.toFile())) {
ZipEntry versionJson = gameJar.getEntry("version.json"); ZipEntry versionJson = gameJar.getEntry("version.json");
if (versionJson != null) { if (versionJson != null) {
Optional<String> result = getVersionFromJson(gameJar.getInputStream(versionJson)); Optional<String> result = getVersionFromJson(gameJar.getInputStream(versionJson));

View File

@@ -20,9 +20,9 @@ package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.java.JavaRuntime; import org.jackhuang.hmcl.java.JavaRuntime;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.Serializable; import java.io.Serializable;
import java.net.Proxy; import java.net.Proxy;
import java.nio.file.Path;
import java.util.*; import java.util.*;
/** /**
@@ -31,7 +31,7 @@ import java.util.*;
*/ */
public class LaunchOptions implements Serializable { public class LaunchOptions implements Serializable {
private File gameDir; private Path gameDir;
private JavaRuntime java; private JavaRuntime java;
private String versionName; private String versionName;
private String versionType; private String versionType;
@@ -68,7 +68,7 @@ public class LaunchOptions implements Serializable {
/** /**
* The game directory * The game directory
*/ */
public File getGameDir() { public Path getGameDir() {
return gameDir; return gameDir;
} }
@@ -314,7 +314,7 @@ public class LaunchOptions implements Serializable {
return options.javaAgents; return options.javaAgents;
} }
public Builder setGameDir(File gameDir) { public Builder setGameDir(Path gameDir) {
options.gameDir = gameDir; options.gameDir = gameDir;
return this; return this;
} }

View File

@@ -25,12 +25,14 @@ import org.jackhuang.hmcl.game.LibrariesDownloadInfo;
import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.LibraryDownloadInfo; import org.jackhuang.hmcl.game.LibraryDownloadInfo;
import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.gson.JsonSerializable;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@Immutable @Immutable
public class TLauncherLibrary { @JsonSerializable
public final class TLauncherLibrary {
@SerializedName("name") @SerializedName("name")
private final Artifact name; private final Artifact name;

View File

@@ -28,7 +28,7 @@ import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class TLauncherVersion implements Validation { public final class TLauncherVersion implements Validation {
private final String id; private final String id;
private final String minecraftArguments; private final String minecraftArguments;

View File

@@ -36,6 +36,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.*; import java.util.*;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -64,7 +65,7 @@ public class DefaultLauncher extends Launcher {
this.analyzer = LibraryAnalyzer.analyze(version, repository.getGameVersion(version).orElse(null)); this.analyzer = LibraryAnalyzer.analyze(version, repository.getGameVersion(version).orElse(null));
} }
private Command generateCommandLine(File nativeFolder) throws IOException { private Command generateCommandLine(Path nativeFolder) throws IOException {
CommandBuilder res = new CommandBuilder(); CommandBuilder res = new CommandBuilder();
switch (options.getProcessPriority()) { switch (options.getProcessPriority()) {
@@ -149,25 +150,25 @@ public class DefaultLauncher extends Launcher {
String formatMsgNoLookups = res.addDefault("-Dlog4j2.formatMsgNoLookups=", "true"); String formatMsgNoLookups = res.addDefault("-Dlog4j2.formatMsgNoLookups=", "true");
if (!"-Dlog4j2.formatMsgNoLookups=false".equals(formatMsgNoLookups) && isUsingLog4j()) { if (!"-Dlog4j2.formatMsgNoLookups=false".equals(formatMsgNoLookups) && isUsingLog4j()) {
res.addDefault("-Dlog4j.configurationFile=", getLog4jConfigurationFile().getAbsolutePath()); res.addDefault("-Dlog4j.configurationFile=", FileUtils.getAbsolutePath(getLog4jConfigurationFile()));
} }
// Default JVM Args // Default JVM Args
if (!options.isNoGeneratedJVMArgs()) { if (!options.isNoGeneratedJVMArgs()) {
appendJvmArgs(res); appendJvmArgs(res);
res.addDefault("-Dminecraft.client.jar=", repository.getVersionJar(version).toString()); res.addDefault("-Dminecraft.client.jar=", FileUtils.getAbsolutePath(repository.getVersionJar(version)));
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) { if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
res.addDefault("-Xdock:name=", "Minecraft " + version.getId()); res.addDefault("-Xdock:name=", "Minecraft " + version.getId());
repository.getAssetObject(version.getId(), version.getAssetIndex().getId(), "icons/minecraft.icns") repository.getAssetObject(version.getId(), version.getAssetIndex().getId(), "icons/minecraft.icns")
.ifPresent(minecraftIcns -> { .ifPresent(minecraftIcns -> {
res.addDefault("-Xdock:icon=", minecraftIcns.toAbsolutePath().toString()); res.addDefault("-Xdock:icon=", FileUtils.getAbsolutePath(minecraftIcns));
}); });
} }
if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS) if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS)
res.addDefault("-Duser.home=", options.getGameDir().getAbsoluteFile().getParent()); res.addDefault("-Duser.home=", options.getGameDir().toAbsolutePath().getParent().toString());
Proxy.Type proxyType = options.getProxyType(); Proxy.Type proxyType = options.getProxyType();
if (proxyType == null) { if (proxyType == null) {
@@ -250,23 +251,23 @@ public class DefaultLauncher extends Launcher {
classpath.removeIf(c -> c.contains("2.9.4-nightly-20150209")); classpath.removeIf(c -> c.contains("2.9.4-nightly-20150209"));
} }
File jar = repository.getVersionJar(version); Path jar = repository.getVersionJar(version);
if (!jar.exists() || !jar.isFile()) if (!Files.isRegularFile(jar))
throw new IOException("Minecraft jar does not exist"); throw new IOException("Minecraft jar does not exist");
classpath.add(jar.getAbsolutePath()); classpath.add(FileUtils.getAbsolutePath(jar.toAbsolutePath()));
// Provided Minecraft arguments // Provided Minecraft arguments
Path gameAssets = repository.getActualAssetDirectory(version.getId(), version.getAssetIndex().getId()); Path gameAssets = repository.getActualAssetDirectory(version.getId(), version.getAssetIndex().getId());
Map<String, String> configuration = getConfigurations(); Map<String, String> configuration = getConfigurations();
configuration.put("${classpath}", String.join(File.pathSeparator, classpath)); configuration.put("${classpath}", String.join(File.pathSeparator, classpath));
configuration.put("${game_assets}", gameAssets.toAbsolutePath().toString()); configuration.put("${game_assets}", FileUtils.getAbsolutePath(gameAssets));
configuration.put("${assets_root}", gameAssets.toAbsolutePath().toString()); configuration.put("${assets_root}", FileUtils.getAbsolutePath(gameAssets));
Optional<String> gameVersion = repository.getGameVersion(version); Optional<String> gameVersion = repository.getGameVersion(version);
// lwjgl assumes path to native libraries encoded by ASCII. // lwjgl assumes path to native libraries encoded by ASCII.
// Here is a workaround for this issue: https://github.com/HMCL-dev/HMCL/issues/1141. // Here is a workaround for this issue: https://github.com/HMCL-dev/HMCL/issues/1141.
String nativeFolderPath = nativeFolder.getAbsolutePath(); String nativeFolderPath = FileUtils.getAbsolutePath(nativeFolder);
Path tempNativeFolder = null; Path tempNativeFolder = null;
if ((OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) if ((OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)
&& !StringUtils.isASCII(nativeFolderPath) && !StringUtils.isASCII(nativeFolderPath)
@@ -376,9 +377,9 @@ public class DefaultLauncher extends Launcher {
protected void appendJvmArgs(CommandBuilder result) { protected void appendJvmArgs(CommandBuilder result) {
} }
public void decompressNatives(File destination) throws NotDecompressingNativesException { public void decompressNatives(Path destination) throws NotDecompressingNativesException {
try { try {
FileUtils.cleanDirectoryQuietly(destination.toPath()); FileUtils.cleanDirectoryQuietly(destination);
for (Library library : version.getLibraries()) for (Library library : version.getLibraries())
if (library.isNative()) if (library.isNative())
new Unzipper(repository.getLibraryFile(version, library), destination) new Unzipper(repository.getLibraryFile(version, library), destination)
@@ -408,12 +409,12 @@ public class DefaultLauncher extends Launcher {
return GameVersionNumber.compare(repository.getGameVersion(version).orElse("1.7"), "1.7") >= 0; return GameVersionNumber.compare(repository.getGameVersion(version).orElse("1.7"), "1.7") >= 0;
} }
public File getLog4jConfigurationFile() { public Path getLog4jConfigurationFile() {
return new File(repository.getVersionRoot(version.getId()), "log4j2.xml"); return repository.getVersionRoot(version.getId()).resolve("log4j2.xml");
} }
public void extractLog4jConfigurationFile() throws IOException { public void extractLog4jConfigurationFile() throws IOException {
File targetFile = getLog4jConfigurationFile(); Path targetFile = getLog4jConfigurationFile();
InputStream source; InputStream source;
if (GameVersionNumber.asGameVersion(repository.getGameVersion(version)).compareTo("1.12") < 0) { if (GameVersionNumber.asGameVersion(repository.getGameVersion(version)).compareTo("1.12") < 0) {
source = DefaultLauncher.class.getResourceAsStream("/assets/game/log4j2-1.7.xml"); source = DefaultLauncher.class.getResourceAsStream("/assets/game/log4j2-1.7.xml");
@@ -421,8 +422,8 @@ public class DefaultLauncher extends Launcher {
source = DefaultLauncher.class.getResourceAsStream("/assets/game/log4j2-1.12.xml"); source = DefaultLauncher.class.getResourceAsStream("/assets/game/log4j2-1.12.xml");
} }
try (InputStream input = source; OutputStream output = new FileOutputStream(targetFile)) { try (InputStream input = source) {
input.transferTo(output); Files.copy(input, targetFile, StandardCopyOption.REPLACE_EXISTING);
} }
} }
@@ -436,35 +437,35 @@ public class DefaultLauncher extends Launcher {
pair("${version_name}", Optional.ofNullable(options.getVersionName()).orElse(version.getId())), pair("${version_name}", Optional.ofNullable(options.getVersionName()).orElse(version.getId())),
pair("${profile_name}", Optional.ofNullable(options.getProfileName()).orElse("Minecraft")), pair("${profile_name}", Optional.ofNullable(options.getProfileName()).orElse("Minecraft")),
pair("${version_type}", Optional.ofNullable(options.getVersionType()).orElse(version.getType().getId())), pair("${version_type}", Optional.ofNullable(options.getVersionType()).orElse(version.getType().getId())),
pair("${game_directory}", repository.getRunDirectory(version.getId()).getAbsolutePath()), pair("${game_directory}", FileUtils.getAbsolutePath(repository.getRunDirectory(version.getId()))),
pair("${user_type}", authInfo.getUserType()), pair("${user_type}", authInfo.getUserType()),
pair("${assets_index_name}", version.getAssetIndex().getId()), pair("${assets_index_name}", version.getAssetIndex().getId()),
pair("${user_properties}", authInfo.getUserProperties()), pair("${user_properties}", authInfo.getUserProperties()),
pair("${resolution_width}", options.getWidth().toString()), pair("${resolution_width}", options.getWidth().toString()),
pair("${resolution_height}", options.getHeight().toString()), pair("${resolution_height}", options.getHeight().toString()),
pair("${library_directory}", repository.getLibrariesDirectory(version).getAbsolutePath()), pair("${library_directory}", FileUtils.getAbsolutePath(repository.getLibrariesDirectory(version))),
pair("${classpath_separator}", File.pathSeparator), pair("${classpath_separator}", File.pathSeparator),
pair("${primary_jar}", repository.getVersionJar(version).getAbsolutePath()), pair("${primary_jar}", FileUtils.getAbsolutePath(repository.getVersionJar(version))),
pair("${language}", Locale.getDefault().toLanguageTag()), pair("${language}", Locale.getDefault().toLanguageTag()),
// defined by HMCL // defined by HMCL
// libraries_directory stands for historical reasons here. We don't know the official 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" // had already defined "library_directory" as the placeholder for path to ".minecraft/libraries"
// when we propose this placeholder. // when we propose this placeholder.
pair("${libraries_directory}", repository.getLibrariesDirectory(version).getAbsolutePath()), pair("${libraries_directory}", FileUtils.getAbsolutePath(repository.getLibrariesDirectory(version))),
// file_separator is used in -DignoreList // file_separator is used in -DignoreList
pair("${file_separator}", File.separator), pair("${file_separator}", File.separator),
pair("${primary_jar_name}", FileUtils.getName(repository.getVersionJar(version).toPath())) pair("${primary_jar_name}", FileUtils.getName(repository.getVersionJar(version)))
); );
} }
@Override @Override
public ManagedProcess launch() throws IOException, InterruptedException { public ManagedProcess launch() throws IOException, InterruptedException {
File nativeFolder; Path nativeFolder;
if (options.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) { if (options.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) {
nativeFolder = repository.getNativeDirectory(version.getId(), options.getJava().getPlatform()); nativeFolder = repository.getNativeDirectory(version.getId(), options.getJava().getPlatform());
} else { } else {
nativeFolder = new File(options.getNativesDir()); nativeFolder = Path.of(options.getNativesDir());
} }
final Command command = generateCommandLine(nativeFolder); final Command command = generateCommandLine(nativeFolder);
@@ -474,7 +475,7 @@ public class DefaultLauncher extends Launcher {
if (command.tempNativeFolder != null) { if (command.tempNativeFolder != null) {
Files.deleteIfExists(command.tempNativeFolder); Files.deleteIfExists(command.tempNativeFolder);
Files.createSymbolicLink(command.tempNativeFolder, nativeFolder.toPath().toAbsolutePath()); Files.createSymbolicLink(command.tempNativeFolder, nativeFolder.toAbsolutePath());
} }
if (rawCommandLine.stream().anyMatch(StringUtils::isBlank)) { if (rawCommandLine.stream().anyMatch(StringUtils::isBlank)) {
@@ -488,22 +489,22 @@ public class DefaultLauncher extends Launcher {
if (isUsingLog4j()) if (isUsingLog4j())
extractLog4jConfigurationFile(); extractLog4jConfigurationFile();
File runDirectory = repository.getRunDirectory(version.getId()); Path runDirectory = repository.getRunDirectory(version.getId());
if (StringUtils.isNotBlank(options.getPreLaunchCommand())) { if (StringUtils.isNotBlank(options.getPreLaunchCommand())) {
ProcessBuilder builder = new ProcessBuilder(StringUtils.tokenize(options.getPreLaunchCommand(), getEnvVars())).directory(runDirectory); ProcessBuilder builder = new ProcessBuilder(StringUtils.tokenize(options.getPreLaunchCommand(), getEnvVars())).directory(runDirectory.toFile());
builder.environment().putAll(getEnvVars()); builder.environment().putAll(getEnvVars());
SystemUtils.callExternalProcess(builder); SystemUtils.callExternalProcess(builder);
} }
Process process; Process process;
try { try {
ProcessBuilder builder = new ProcessBuilder(rawCommandLine).directory(runDirectory); ProcessBuilder builder = new ProcessBuilder(rawCommandLine).directory(runDirectory.toFile());
if (listener == null) { if (listener == null) {
builder.inheritIO(); builder.inheritIO();
} }
String appdata = options.getGameDir().getAbsoluteFile().getParent(); Path appdata = options.getGameDir().toAbsolutePath().getParent();
if (appdata != null) builder.environment().put("APPDATA", appdata); if (appdata != null) builder.environment().put("APPDATA", appdata.toString());
builder.environment().putAll(getEnvVars()); builder.environment().putAll(getEnvVars());
process = builder.start(); process = builder.start();
@@ -522,8 +523,8 @@ public class DefaultLauncher extends Launcher {
Map<String, String> env = new LinkedHashMap<>(); Map<String, String> env = new LinkedHashMap<>();
env.put("INST_NAME", versionName); env.put("INST_NAME", versionName);
env.put("INST_ID", versionName); env.put("INST_ID", versionName);
env.put("INST_DIR", repository.getVersionRoot(version.getId()).getAbsolutePath()); env.put("INST_DIR", FileUtils.getAbsolutePath(repository.getVersionRoot(version.getId())));
env.put("INST_MC_DIR", repository.getRunDirectory(version.getId()).getAbsolutePath()); env.put("INST_MC_DIR", FileUtils.getAbsolutePath(repository.getRunDirectory(version.getId())));
env.put("INST_JAVA", options.getJava().getBinary().toString()); env.put("INST_JAVA", options.getJava().getBinary().toString());
Renderer renderer = options.getRenderer(); Renderer renderer = options.getRenderer();
@@ -582,11 +583,11 @@ public class DefaultLauncher extends Launcher {
public void makeLaunchScript(File scriptFile) throws IOException { public void makeLaunchScript(File scriptFile) throws IOException {
boolean isWindows = OperatingSystem.WINDOWS == OperatingSystem.CURRENT_OS; boolean isWindows = OperatingSystem.WINDOWS == OperatingSystem.CURRENT_OS;
File nativeFolder; Path nativeFolder;
if (options.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) { if (options.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) {
nativeFolder = repository.getNativeDirectory(version.getId(), options.getJava().getPlatform()); nativeFolder = repository.getNativeDirectory(version.getId(), options.getJava().getPlatform());
} else { } else {
nativeFolder = new File(options.getNativesDir()); nativeFolder = Path.of(options.getNativesDir());
} }
if (options.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) { if (options.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) {
@@ -640,9 +641,12 @@ public class DefaultLauncher extends Launcher {
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, charset))) { try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, charset))) {
if (usePowerShell) { if (usePowerShell) {
if (isWindows) { if (isWindows) {
writer.write("$Env:APPDATA="); Path appdata = options.getGameDir().toAbsolutePath().getParent();
writer.write(CommandBuilder.pwshString(options.getGameDir().getAbsoluteFile().getParent())); if (appdata != null) {
writer.newLine(); writer.write("$Env:APPDATA=");
writer.write(CommandBuilder.pwshString(appdata.toString()));
writer.newLine();
}
} }
for (Map.Entry<String, String> entry : envVars.entrySet()) { for (Map.Entry<String, String> entry : envVars.entrySet()) {
writer.write("$Env:" + entry.getKey() + "="); writer.write("$Env:" + entry.getKey() + "=");
@@ -650,7 +654,7 @@ public class DefaultLauncher extends Launcher {
writer.newLine(); writer.newLine();
} }
writer.write("Set-Location -Path "); writer.write("Set-Location -Path ");
writer.write(CommandBuilder.pwshString(repository.getRunDirectory(version.getId()).getAbsolutePath())); writer.write(CommandBuilder.pwshString(FileUtils.getAbsolutePath(repository.getRunDirectory(version.getId()))));
writer.newLine(); writer.newLine();
@@ -682,14 +686,19 @@ public class DefaultLauncher extends Launcher {
if (isWindows) { if (isWindows) {
writer.write("@echo off"); writer.write("@echo off");
writer.newLine(); writer.newLine();
writer.write("set APPDATA=" + options.getGameDir().getAbsoluteFile().getParent());
writer.newLine(); Path appdata = options.getGameDir().toAbsolutePath().getParent();
if (appdata != null) {
writer.write("set APPDATA=" + appdata);
writer.newLine();
}
for (Map.Entry<String, String> entry : envVars.entrySet()) { for (Map.Entry<String, String> entry : envVars.entrySet()) {
writer.write("set " + entry.getKey() + "=" + CommandBuilder.toBatchStringLiteral(entry.getValue())); writer.write("set " + entry.getKey() + "=" + CommandBuilder.toBatchStringLiteral(entry.getValue()));
writer.newLine(); writer.newLine();
} }
writer.newLine(); writer.newLine();
writer.write(new CommandBuilder().add("cd", "/D", repository.getRunDirectory(version.getId()).getAbsolutePath()).toString()); writer.write(new CommandBuilder().add("cd", "/D", FileUtils.getAbsolutePath(repository.getRunDirectory(version.getId()))).toString());
} else { } else {
writer.write("#!/usr/bin/env bash"); writer.write("#!/usr/bin/env bash");
writer.newLine(); writer.newLine();
@@ -698,10 +707,10 @@ public class DefaultLauncher extends Launcher {
writer.newLine(); writer.newLine();
} }
if (commandLine.tempNativeFolder != null) { if (commandLine.tempNativeFolder != null) {
writer.write(new CommandBuilder().add("ln", "-s", nativeFolder.getAbsolutePath(), commandLine.tempNativeFolder.toString()).toString()); writer.write(new CommandBuilder().add("ln", "-s", FileUtils.getAbsolutePath(nativeFolder), commandLine.tempNativeFolder.toString()).toString());
writer.newLine(); writer.newLine();
} }
writer.write(new CommandBuilder().add("cd", repository.getRunDirectory(version.getId()).getAbsolutePath()).toString()); writer.write(new CommandBuilder().add("cd", FileUtils.getAbsolutePath(repository.getRunDirectory(version.getId()))).toString());
} }
writer.newLine(); writer.newLine();
if (StringUtils.isNotBlank(options.getPreLaunchCommand())) { if (StringUtils.isNotBlank(options.getPreLaunchCommand())) {
@@ -751,7 +760,7 @@ public class DefaultLauncher extends Launcher {
if (StringUtils.isNotBlank(options.getPostExitCommand())) { if (StringUtils.isNotBlank(options.getPostExitCommand())) {
try { try {
ProcessBuilder builder = new ProcessBuilder(StringUtils.tokenize(options.getPostExitCommand(), getEnvVars())).directory(options.getGameDir()); ProcessBuilder builder = new ProcessBuilder(StringUtils.tokenize(options.getPostExitCommand(), getEnvVars())).directory(options.getGameDir().toFile());
builder.environment().putAll(getEnvVars()); builder.environment().putAll(getEnvVars());
SystemUtils.callExternalProcess(builder); SystemUtils.callExternalProcess(builder);
} catch (Throwable e) { } catch (Throwable e) {

View File

@@ -75,7 +75,7 @@ public final class ModManager {
} }
public Path getModsDirectory() { public Path getModsDirectory() {
return repository.getRunDirectory(id).toPath().resolve("mods"); return repository.getRunDirectory(id).resolve("mods");
} }
public LocalMod getLocalMod(String id, ModLoaderType modLoaderType) { public LocalMod getLocalMod(String id, ModLoaderType modLoaderType) {

View File

@@ -38,7 +38,7 @@ public class ModpackUpdateTask extends Task<Void> {
this.id = id; this.id = id;
this.updateTask = updateTask; this.updateTask = updateTask;
Path backup = repository.getBaseDirectory().toPath().resolve("backup"); Path backup = repository.getBaseDirectory().resolve("backup");
while (true) { while (true) {
int num = (int)(Math.random() * 10000000); int num = (int)(Math.random() * 10000000);
if (!Files.exists(backup.resolve(id + "-" + num))) { if (!Files.exists(backup.resolve(id + "-" + num))) {
@@ -55,7 +55,7 @@ public class ModpackUpdateTask extends Task<Void> {
@Override @Override
public void execute() throws Exception { public void execute() throws Exception {
FileUtils.copyDirectory(repository.getVersionRoot(id).toPath(), backupFolder); FileUtils.copyDirectory(repository.getVersionRoot(id), backupFolder);
} }
@Override @Override
@@ -71,7 +71,7 @@ public class ModpackUpdateTask extends Task<Void> {
// Restore backup // Restore backup
repository.removeVersionFromDisk(id); repository.removeVersionFromDisk(id);
FileUtils.copyDirectory(backupFolder, repository.getVersionRoot(id).toPath()); FileUtils.copyDirectory(backupFolder, repository.getVersionRoot(id));
repository.refreshVersionsAsync().start(); repository.refreshVersionsAsync().start();
} }

View File

@@ -31,6 +31,8 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@@ -84,9 +86,9 @@ public final class CurseCompletionTask extends Task<Void> {
if (manifest == null) if (manifest == null)
try { try {
File manifestFile = new File(repository.getVersionRoot(version), "manifest.json"); Path manifestFile = repository.getVersionRoot(version).resolve("manifest.json");
if (manifestFile.exists()) if (Files.exists(manifestFile))
this.manifest = JsonUtils.fromJsonFile(manifestFile.toPath(), CurseManifest.class); this.manifest = JsonUtils.fromJsonFile(manifestFile, CurseManifest.class);
} catch (Exception e) { } catch (Exception e) {
LOG.warning("Unable to read CurseForge modpack manifest.json", e); LOG.warning("Unable to read CurseForge modpack manifest.json", e);
} }
@@ -109,7 +111,7 @@ public final class CurseCompletionTask extends Task<Void> {
if (manifest == null) if (manifest == null)
return; return;
File root = repository.getVersionRoot(version); Path root = repository.getVersionRoot(version);
// Because in China, Curse is too difficult to visit, // Because in China, Curse is too difficult to visit,
// if failed, ignore it and retry next time. // if failed, ignore it and retry next time.
@@ -135,17 +137,18 @@ public final class CurseCompletionTask extends Task<Void> {
} }
}) })
.collect(Collectors.toList())); .collect(Collectors.toList()));
JsonUtils.writeToJsonFile(root.toPath().resolve("manifest.json"), newManifest); JsonUtils.writeToJsonFile(root.resolve("manifest.json"), newManifest);
File versionRoot = repository.getVersionRoot(modManager.getVersion()); Path versionRoot = repository.getVersionRoot(modManager.getVersion());
File resourcePacksRoot = new File(versionRoot, "resourcepacks"), shaderPacksRoot = new File(versionRoot, "shaderpacks"); Path resourcePacksRoot = versionRoot.resolve("resourcepacks");
Path shaderPacksRoot = versionRoot.resolve("shaderpacks");
finished.set(0); finished.set(0);
dependencies = newManifest.getFiles() dependencies = newManifest.getFiles()
.stream().parallel() .stream().parallel()
.filter(f -> f.getFileName() != null) .filter(f -> f.getFileName() != null)
.flatMap(f -> { .flatMap(f -> {
try { try {
File path = guessFilePath(f, resourcePacksRoot, shaderPacksRoot); File path = guessFilePath(f, resourcePacksRoot.toFile(), shaderPacksRoot.toFile());
if (path == null) { if (path == null) {
return Stream.empty(); return Stream.empty();
} }
@@ -171,9 +174,10 @@ public final class CurseCompletionTask extends Task<Void> {
/** /**
* Guess where to store the file. * Guess where to store the file.
* @param file The file. *
* @param file The file.
* @param resourcePacksRoot ./resourcepacks. * @param resourcePacksRoot ./resourcepacks.
* @param shaderPacksRoot ./shaderpacks. * @param shaderPacksRoot ./shaderpacks.
* @return ./resourcepacks/$filename or ./shaderpacks/$filename or ./mods/$filename if the file doesn't exist. null if the file existed. * @return ./resourcepacks/$filename or ./shaderpacks/$filename or ./mods/$filename if the file doesn't exist. null if the file existed.
* @throws IOException If IOException was encountered during getting data from CurseForge. * @throws IOException If IOException was encountered during getting data from CurseForge.
*/ */

View File

@@ -68,10 +68,10 @@ public final class CurseInstallTask extends Task<Void> {
this.manifest = manifest; this.manifest = manifest;
this.name = name; this.name = name;
this.repository = dependencyManager.getGameRepository(); this.repository = dependencyManager.getGameRepository();
this.run = repository.getRunDirectory(name); this.run = repository.getRunDirectory(name).toFile();
File json = repository.getModpackConfiguration(name); Path json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists()) if (repository.hasVersion(name) && Files.notExists(json))
throw new IllegalArgumentException("Version " + name + " already exists."); throw new IllegalArgumentException("Version " + name + " already exists.");
GameBuilder builder = dependencyManager.gameBuilder().name(name).gameVersion(manifest.getMinecraft().getGameVersion()); GameBuilder builder = dependencyManager.gameBuilder().name(name).gameVersion(manifest.getMinecraft().getGameVersion());
@@ -97,8 +97,8 @@ public final class CurseInstallTask extends Task<Void> {
ModpackConfiguration<CurseManifest> config = null; ModpackConfiguration<CurseManifest> config = null;
try { try {
if (json.exists()) { if (Files.exists(json)) {
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(CurseManifest.class)); config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(CurseManifest.class));
if (!CurseModpackProvider.INSTANCE.getName().equals(config.getType())) if (!CurseModpackProvider.INSTANCE.getName().equals(config.getType()))
throw new IllegalArgumentException("Version " + name + " is not a Curse modpack. Cannot update this version."); throw new IllegalArgumentException("Version " + name + " is not a Curse modpack. Cannot update this version.");
@@ -107,7 +107,7 @@ public final class CurseInstallTask extends Task<Void> {
} }
this.config = config; this.config = config;
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList(manifest.getOverrides()), any -> true, config).withStage("hmcl.modpack")); dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList(manifest.getOverrides()), any -> true, config).withStage("hmcl.modpack"));
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList(manifest.getOverrides()), manifest, CurseModpackProvider.INSTANCE, manifest.getName(), manifest.getVersion(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack")); dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList(manifest.getOverrides()), manifest, CurseModpackProvider.INSTANCE, manifest.getName(), manifest.getVersion(), repository.getModpackConfiguration(name).toFile()).withStage("hmcl.modpack"));
dependencies.add(new CurseCompletionTask(dependencyManager, name, manifest)); dependencies.add(new CurseCompletionTask(dependencyManager, name, manifest));
} }
@@ -136,7 +136,7 @@ public final class CurseInstallTask extends Task<Void> {
} }
} }
Path root = repository.getVersionRoot(name).toPath(); Path root = repository.getVersionRoot(name);
Files.createDirectories(root); Files.createDirectories(root);
JsonUtils.writeToJsonFile(root.resolve("manifest.json"), manifest); JsonUtils.writeToJsonFile(root.resolve("manifest.json"), manifest);
} }

View File

@@ -73,7 +73,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
this.repository = dependencyManager.getGameRepository(); this.repository = dependencyManager.getGameRepository();
this.modManager = repository.getModManager(version); this.modManager = repository.getModManager(version);
this.version = version; this.version = version;
this.configurationFile = repository.getModpackConfiguration(version); this.configurationFile = repository.getModpackConfiguration(version).toFile();
this.configuration = configuration; this.configuration = configuration;
setStage("hmcl.modpack.download"); setStage("hmcl.modpack.download");
@@ -110,7 +110,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
throw new IOException("Unable to parse server manifest.json from " + manifest.getFileApi(), e); throw new IOException("Unable to parse server manifest.json from " + manifest.getFileApi(), e);
} }
Path rootPath = repository.getVersionRoot(version).toPath(); Path rootPath = repository.getVersionRoot(version);
Files.createDirectories(rootPath); Files.createDirectories(rootPath);
Map<McbbsModpackManifest.File, McbbsModpackManifest.File> localFiles = manifest.getFiles().stream().collect(Collectors.toMap(Function.identity(), Function.identity())); Map<McbbsModpackManifest.File, McbbsModpackManifest.File> localFiles = manifest.getFiles().stream().collect(Collectors.toMap(Function.identity(), Function.identity()));
@@ -172,7 +172,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
manifest = remoteManifest.setFiles(newFiles); manifest = remoteManifest.setFiles(newFiles);
return executor.all(tasks.stream().filter(Objects::nonNull).collect(Collectors.toList())); return executor.all(tasks.stream().filter(Objects::nonNull).collect(Collectors.toList()));
})).thenAcceptAsync(wrapConsumer(unused1 -> { })).thenAcceptAsync(wrapConsumer(unused1 -> {
Path manifestFile = repository.getModpackConfiguration(version).toPath(); Path manifestFile = repository.getModpackConfiguration(version);
JsonUtils.writeToJsonFile(manifestFile, JsonUtils.writeToJsonFile(manifestFile,
new ModpackConfiguration<>(manifest, this.configuration.getType(), this.manifest.getName(), this.manifest.getVersion(), new ModpackConfiguration<>(manifest, this.configuration.getType(), this.manifest.getName(), this.manifest.getVersion(),
this.manifest.getFiles().stream() this.manifest.getFiles().stream()
@@ -274,7 +274,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
@Nullable @Nullable
private Path getFilePath(McbbsModpackManifest.File file) { private Path getFilePath(McbbsModpackManifest.File file) {
if (file instanceof McbbsModpackManifest.AddonFile) { if (file instanceof McbbsModpackManifest.AddonFile) {
return modManager.getRepository().getRunDirectory(modManager.getVersion()).toPath().resolve(((McbbsModpackManifest.AddonFile) file).getPath()); return modManager.getRepository().getRunDirectory(modManager.getVersion()).resolve(((McbbsModpackManifest.AddonFile) file).getPath());
} else if (file instanceof McbbsModpackManifest.CurseFile) { } else if (file instanceof McbbsModpackManifest.CurseFile) {
String fileName = ((McbbsModpackManifest.CurseFile) file).getFileName(); String fileName = ((McbbsModpackManifest.CurseFile) file).getFileName();
if (fileName == null) return null; if (fileName == null) return null;

View File

@@ -67,7 +67,7 @@ public class McbbsModpackExportTask extends Task<Void> {
blackList.add(version + ".json"); blackList.add(version + ".json");
LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker"); LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker");
try (Zipper zip = new Zipper(modpackFile.toPath())) { try (Zipper zip = new Zipper(modpackFile.toPath())) {
Path runDirectory = repository.getRunDirectory(version).toPath(); Path runDirectory = repository.getRunDirectory(version);
List<McbbsModpackManifest.File> files = new ArrayList<>(); List<McbbsModpackManifest.File> files = new ArrayList<>();
zip.putDirectory(runDirectory, "overrides", path -> { zip.putDirectory(runDirectory, "overrides", path -> {
if (Modpack.acceptFile(path, blackList, info.getWhitelist())) { if (Modpack.acceptFile(path, blackList, info.getWhitelist())) {

View File

@@ -31,6 +31,8 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -56,10 +58,10 @@ public class McbbsModpackLocalInstallTask extends Task<Void> {
this.manifest = manifest; this.manifest = manifest;
this.name = name; this.name = name;
this.repository = dependencyManager.getGameRepository(); this.repository = dependencyManager.getGameRepository();
File run = repository.getRunDirectory(name); Path run = repository.getRunDirectory(name);
File json = repository.getModpackConfiguration(name); Path json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists()) if (repository.hasVersion(name) && Files.notExists(json))
throw new IllegalArgumentException("Version " + name + " already exists."); throw new IllegalArgumentException("Version " + name + " already exists.");
this.update = repository.hasVersion(name); this.update = repository.hasVersion(name);
@@ -77,16 +79,16 @@ public class McbbsModpackLocalInstallTask extends Task<Void> {
ModpackConfiguration<McbbsModpackManifest> config = null; ModpackConfiguration<McbbsModpackManifest> config = null;
try { try {
if (json.exists()) { if (Files.exists(json)) {
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(McbbsModpackManifest.class)); config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(McbbsModpackManifest.class));
if (!McbbsModpackProvider.INSTANCE.getName().equals(config.getType())) if (!McbbsModpackProvider.INSTANCE.getName().equals(config.getType()))
throw new IllegalArgumentException("Version " + name + " is not a Mcbbs modpack. Cannot update this version."); throw new IllegalArgumentException("Version " + name + " is not a Mcbbs modpack. Cannot update this version.");
} }
} catch (JsonParseException | IOException ignore) { } catch (JsonParseException | IOException ignore) {
} }
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList("/overrides"), any -> true, config).withStage("hmcl.modpack")); dependents.add(new ModpackInstallTask<>(zipFile, run.toFile(), modpack.getEncoding(), Collections.singletonList("/overrides"), any -> true, config).withStage("hmcl.modpack"));
instanceTask = new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList("/overrides"), manifest, McbbsModpackProvider.INSTANCE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name)); instanceTask = new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList("/overrides"), manifest, McbbsModpackProvider.INSTANCE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name).toFile());
dependents.add(instanceTask.withStage("hmcl.modpack")); dependents.add(instanceTask.withStage("hmcl.modpack"));
} }

View File

@@ -25,8 +25,9 @@ import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.JsonUtils;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -46,8 +47,8 @@ public class McbbsModpackRemoteInstallTask extends Task<Void> {
this.repository = dependencyManager.getGameRepository(); this.repository = dependencyManager.getGameRepository();
this.manifest = manifest; this.manifest = manifest;
File json = repository.getModpackConfiguration(name); Path json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists()) if (repository.hasVersion(name) && Files.notExists(json))
throw new IllegalArgumentException("Version " + name + " already exists."); throw new IllegalArgumentException("Version " + name + " already exists.");
GameBuilder builder = dependencyManager.gameBuilder().name(name); GameBuilder builder = dependencyManager.gameBuilder().name(name);
@@ -63,8 +64,8 @@ public class McbbsModpackRemoteInstallTask extends Task<Void> {
ModpackConfiguration<McbbsModpackManifest> config; ModpackConfiguration<McbbsModpackManifest> config;
try { try {
if (json.exists()) { if (Files.exists(json)) {
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(McbbsModpackManifest.class)); config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(McbbsModpackManifest.class));
if (!MODPACK_TYPE.equals(config.getType())) if (!MODPACK_TYPE.equals(config.getType()))
throw new IllegalArgumentException("Version " + name + " is not a Mcbbs modpack. Cannot update this version."); throw new IllegalArgumentException("Version " + name + " is not a Mcbbs modpack. Cannot update this version.");

View File

@@ -79,7 +79,7 @@ public class ModrinthCompletionTask extends Task<Void> {
if (manifest == null) if (manifest == null)
try { try {
Path manifestFile = repository.getVersionRoot(version).toPath().resolve("modrinth.index.json"); Path manifestFile = repository.getVersionRoot(version).resolve("modrinth.index.json");
if (Files.exists(manifestFile)) if (Files.exists(manifestFile))
this.manifest = JsonUtils.fromJsonFile(manifestFile, ModrinthManifest.class); this.manifest = JsonUtils.fromJsonFile(manifestFile, ModrinthManifest.class);
} catch (Exception e) { } catch (Exception e) {
@@ -104,7 +104,7 @@ public class ModrinthCompletionTask extends Task<Void> {
if (manifest == null) if (manifest == null)
return; return;
Path runDirectory = repository.getRunDirectory(version).toPath().toAbsolutePath().normalize(); Path runDirectory = FileUtils.toAbsolute(repository.getRunDirectory(version));
Path modsDirectory = runDirectory.resolve("mods"); Path modsDirectory = runDirectory.resolve("mods");
for (ModrinthManifest.File file : manifest.getFiles()) { for (ModrinthManifest.File file : manifest.getFiles()) {

View File

@@ -51,10 +51,10 @@ public class ModrinthInstallTask extends Task<Void> {
this.manifest = manifest; this.manifest = manifest;
this.name = name; this.name = name;
this.repository = dependencyManager.getGameRepository(); this.repository = dependencyManager.getGameRepository();
this.run = repository.getRunDirectory(name); this.run = repository.getRunDirectory(name).toFile();
File json = repository.getModpackConfiguration(name); Path json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists()) if (repository.hasVersion(name) && Files.notExists(json))
throw new IllegalArgumentException("Version " + name + " already exists."); throw new IllegalArgumentException("Version " + name + " already exists.");
GameBuilder builder = dependencyManager.gameBuilder().name(name).gameVersion(manifest.getGameVersion()); GameBuilder builder = dependencyManager.gameBuilder().name(name).gameVersion(manifest.getGameVersion());
@@ -91,8 +91,8 @@ public class ModrinthInstallTask extends Task<Void> {
ModpackConfiguration<ModrinthManifest> config = null; ModpackConfiguration<ModrinthManifest> config = null;
try { try {
if (json.exists()) { if (Files.exists(json)) {
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(ModrinthManifest.class)); config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(ModrinthManifest.class));
if (!ModrinthModpackProvider.INSTANCE.getName().equals(config.getType())) if (!ModrinthModpackProvider.INSTANCE.getName().equals(config.getType()))
throw new IllegalArgumentException("Version " + name + " is not a Modrinth modpack. Cannot update this version."); throw new IllegalArgumentException("Version " + name + " is not a Modrinth modpack. Cannot update this version.");
@@ -103,7 +103,7 @@ public class ModrinthInstallTask extends Task<Void> {
this.config = config; this.config = config;
List<String> subDirectories = Arrays.asList("/client-overrides", "/overrides"); List<String> subDirectories = Arrays.asList("/client-overrides", "/overrides");
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), subDirectories, any -> true, config).withStage("hmcl.modpack")); dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), subDirectories, any -> true, config).withStage("hmcl.modpack"));
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), subDirectories, manifest, ModrinthModpackProvider.INSTANCE, manifest.getName(), manifest.getVersionId(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack")); dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), subDirectories, manifest, ModrinthModpackProvider.INSTANCE, manifest.getName(), manifest.getVersionId(), repository.getModpackConfiguration(name).toFile()).withStage("hmcl.modpack"));
dependencies.add(new ModrinthCompletionTask(dependencyManager, name, manifest)); dependencies.add(new ModrinthCompletionTask(dependencyManager, name, manifest));
} }
@@ -131,7 +131,7 @@ public class ModrinthInstallTask extends Task<Void> {
} }
} }
Path root = repository.getVersionRoot(name).toPath(); Path root = repository.getVersionRoot(name);
Files.createDirectories(root); Files.createDirectories(root);
JsonUtils.writeToJsonFile(root.resolve("modrinth.index.json"), manifest); JsonUtils.writeToJsonFile(root.resolve("modrinth.index.json"), manifest);
} }

View File

@@ -108,7 +108,7 @@ public class ModrinthModpackExportTask extends Task<Void> {
blackList.add(version + ".json"); blackList.add(version + ".json");
LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker"); LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker");
try (Zipper zip = new Zipper(modpackFile.toPath())) { try (Zipper zip = new Zipper(modpackFile.toPath())) {
Path runDirectory = repository.getRunDirectory(version).toPath(); Path runDirectory = repository.getRunDirectory(version);
List<ModrinthManifest.File> files = new ArrayList<>(); List<ModrinthManifest.File> files = new ArrayList<>();
Set<String> filesInManifest = new HashSet<>(); Set<String> filesInManifest = new HashSet<>();

View File

@@ -69,7 +69,7 @@ public class MultiMCModpackExportTask extends Task<Void> {
blackList.add(versionId + ".json"); blackList.add(versionId + ".json");
LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker"); LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker");
try (Zipper zip = new Zipper(output.toPath())) { try (Zipper zip = new Zipper(output.toPath())) {
zip.putDirectory(repository.getRunDirectory(versionId).toPath(), ".minecraft", path -> Modpack.acceptFile(path, blackList, whitelist)); zip.putDirectory(repository.getRunDirectory(versionId), ".minecraft", path -> Modpack.acceptFile(path, blackList, whitelist));
String gameVersion = repository.getGameVersion(versionId) String gameVersion = repository.getGameVersion(versionId)
.orElseThrow(() -> new IOException("Cannot parse the version of " + versionId)); .orElseThrow(() -> new IOException("Cannot parse the version of " + versionId));

View File

@@ -94,8 +94,8 @@ public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.R
this.dependencyManager = dependencyManager; this.dependencyManager = dependencyManager;
this.repository = dependencyManager.getGameRepository(); this.repository = dependencyManager.getGameRepository();
File json = repository.getModpackConfiguration(name); Path json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists()) if (repository.hasVersion(name) && Files.notExists(json))
throw new IllegalArgumentException("Version " + name + " already exists."); throw new IllegalArgumentException("Version " + name + " already exists.");
onDone().register(event -> { onDone().register(event -> {
@@ -113,13 +113,13 @@ public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.R
public void preExecute() throws Exception { public void preExecute() throws Exception {
// Stage #0: General Setup // Stage #0: General Setup
{ {
File run = repository.getRunDirectory(name); Path run = repository.getRunDirectory(name);
File json = repository.getModpackConfiguration(name); Path json = repository.getModpackConfiguration(name);
ModpackConfiguration<MultiMCInstanceConfiguration> config = null; ModpackConfiguration<MultiMCInstanceConfiguration> config = null;
try { try {
if (json.exists()) { if (Files.exists(json)) {
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(MultiMCInstanceConfiguration.class)); config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(MultiMCInstanceConfiguration.class));
if (!MultiMCModpackProvider.INSTANCE.getName().equals(config.getType())) if (!MultiMCModpackProvider.INSTANCE.getName().equals(config.getType()))
throw new IllegalArgumentException("Version " + name + " is not a MultiMC modpack. Cannot update this version."); throw new IllegalArgumentException("Version " + name + " is not a MultiMC modpack. Cannot update this version.");
@@ -133,8 +133,8 @@ public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.R
} }
// TODO: Optimize unbearably slow ModpackInstallTask // TODO: Optimize unbearably slow ModpackInstallTask
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList(mcDirectory), any -> true, config).withStage("hmcl.modpack")); dependents.add(new ModpackInstallTask<>(zipFile, run.toFile(), modpack.getEncoding(), Collections.singletonList(mcDirectory), any -> true, config).withStage("hmcl.modpack"));
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList(mcDirectory), manifest, MultiMCModpackProvider.INSTANCE, manifest.getName(), null, repository.getModpackConfiguration(name)).withStage("hmcl.modpack")); dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList(mcDirectory), manifest, MultiMCModpackProvider.INSTANCE, manifest.getName(), null, repository.getModpackConfiguration(name).toFile()).withStage("hmcl.modpack"));
} }
// Stage #1: Load all related Json-Patch from meta maven or local mod pack. // Stage #1: Load all related Json-Patch from meta maven or local mod pack.
@@ -253,7 +253,7 @@ public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.R
Path libraries = root.resolve("libraries"); Path libraries = root.resolve("libraries");
if (Files.exists(libraries)) if (Files.exists(libraries))
FileUtils.copyDirectory(libraries, repository.getVersionRoot(name).toPath().resolve("libraries")); FileUtils.copyDirectory(libraries, repository.getVersionRoot(name).resolve("libraries"));
for (Library library : artifact.getVersion().getLibraries()) { for (Library library : artifact.getVersion().getLibraries()) {
if ("local".equals(library.getHint())) { if ("local".equals(library.getHint())) {
@@ -261,15 +261,15 @@ public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.R
Retain them will facilitate compatibility, as some embedded libraries may check where their JAR is. Retain them will facilitate compatibility, as some embedded libraries may check where their JAR is.
Meanwhile, potential compatibility issue with other launcher which never supports these fields might occur. Meanwhile, potential compatibility issue with other launcher which never supports these fields might occur.
Here, we make the file stored twice, to keep maximum compatibility. */ Here, we make the file stored twice, to keep maximum compatibility. */
Path from = repository.getLibraryFile(artifact.getVersion(), library).toPath(); Path from = repository.getLibraryFile(artifact.getVersion(), library);
Path target = repository.getLibraryFile(artifact.getVersion(), library.withoutCommunityFields()).toPath(); Path target = repository.getLibraryFile(artifact.getVersion(), library.withoutCommunityFields());
Files.createDirectories(target.getParent()); Files.createDirectories(target.getParent());
Files.copy(from, target, StandardCopyOption.REPLACE_EXISTING); Files.copy(from, target, StandardCopyOption.REPLACE_EXISTING);
} }
} }
try (InputStream input = MaintainTask.class.getResourceAsStream("/assets/game/HMCLMultiMCBootstrap-1.0.jar")) { try (InputStream input = MaintainTask.class.getResourceAsStream("/assets/game/HMCLMultiMCBootstrap-1.0.jar")) {
Path libraryPath = repository.getLibraryFile(artifact.getVersion(), MultiMCInstancePatch.BOOTSTRAP_LIBRARY).toPath(); Path libraryPath = repository.getLibraryFile(artifact.getVersion(), MultiMCInstancePatch.BOOTSTRAP_LIBRARY);
Files.createDirectories(libraryPath.getParent()); Files.createDirectories(libraryPath.getParent());
Files.copy(Objects.requireNonNull(input, "Bundled HMCLMultiMCBootstrap is missing."), libraryPath, StandardCopyOption.REPLACE_EXISTING); Files.copy(Objects.requireNonNull(input, "Bundled HMCLMultiMCBootstrap is missing."), libraryPath, StandardCopyOption.REPLACE_EXISTING);
@@ -279,7 +279,7 @@ public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.R
if (iconKey != null) { if (iconKey != null) {
Path iconFile = root.resolve(iconKey + ".png"); Path iconFile = root.resolve(iconKey + ".png");
if (Files.exists(iconFile)) { if (Files.exists(iconFile)) {
FileUtils.copyFile(iconFile, repository.getVersionRoot(name).toPath().resolve("icon.png")); FileUtils.copyFile(iconFile, repository.getVersionRoot(name).resolve("icon.png"));
} }
} }
} }
@@ -339,7 +339,7 @@ public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.R
Path root = getRootPath(fs).resolve("jarmods"); Path root = getRootPath(fs).resolve("jarmods");
try (FileSystem mc = CompressingUtils.writable( try (FileSystem mc = CompressingUtils.writable(
repository.getVersionRoot(name).toPath().resolve(name + ".jar") repository.getVersionRoot(name).resolve(name + ".jar")
).setAutoDetectEncoding(true).build()) { ).setAutoDetectEncoding(true).build()) {
for (String fileName : files) { for (String fileName : files) {
try (FileSystem jm = CompressingUtils.readonly(root.resolve(fileName)).setAutoDetectEncoding(true).build()) { try (FileSystem jm = CompressingUtils.readonly(root.resolve(fileName)).setAutoDetectEncoding(true).build()) {

View File

@@ -30,7 +30,6 @@ import org.jackhuang.hmcl.util.DigestUtils;
import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.JsonUtils;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@@ -61,9 +60,9 @@ public class ServerModpackCompletionTask extends Task<Void> {
if (manifest == null) { if (manifest == null) {
try { try {
File manifestFile = repository.getModpackConfiguration(version); Path manifestFile = repository.getModpackConfiguration(version);
if (manifestFile.exists()) { if (Files.exists(manifestFile)) {
this.manifest = JsonUtils.fromJsonFile(manifestFile.toPath(), ModpackConfiguration.typeOf(ServerModpackManifest.class)); this.manifest = JsonUtils.fromJsonFile(manifestFile, ModpackConfiguration.typeOf(ServerModpackManifest.class));
} }
} catch (Exception e) { } catch (Exception e) {
LOG.warning("Unable to read Server modpack manifest.json", e); LOG.warning("Unable to read Server modpack manifest.json", e);
@@ -121,7 +120,7 @@ public class ServerModpackCompletionTask extends Task<Void> {
dependencies.add(builder.buildAsync()); dependencies.add(builder.buildAsync());
} }
Path rootPath = repository.getVersionRoot(version).toPath().toAbsolutePath().normalize(); Path rootPath = repository.getVersionRoot(version).toAbsolutePath().normalize();
Map<String, ModpackConfiguration.FileInformation> files = manifest.getManifest().getFiles().stream() Map<String, ModpackConfiguration.FileInformation> files = manifest.getManifest().getFiles().stream()
.collect(Collectors.toMap(ModpackConfiguration.FileInformation::getPath, .collect(Collectors.toMap(ModpackConfiguration.FileInformation::getPath,
Function.identity())); Function.identity()));
@@ -129,7 +128,7 @@ public class ServerModpackCompletionTask extends Task<Void> {
Set<String> remoteFiles = remoteManifest.getFiles().stream().map(ModpackConfiguration.FileInformation::getPath) Set<String> remoteFiles = remoteManifest.getFiles().stream().map(ModpackConfiguration.FileInformation::getPath)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
Path runDirectory = repository.getRunDirectory(version).toPath().toAbsolutePath().normalize(); Path runDirectory = repository.getRunDirectory(version).toAbsolutePath().normalize();
Path modsDirectory = runDirectory.resolve("mods"); Path modsDirectory = runDirectory.resolve("mods");
int total = 0; int total = 0;
@@ -189,7 +188,7 @@ public class ServerModpackCompletionTask extends Task<Void> {
@Override @Override
public void postExecute() throws Exception { public void postExecute() throws Exception {
if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return; if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return;
Path manifestFile = repository.getModpackConfiguration(version).toPath(); Path manifestFile = repository.getModpackConfiguration(version);
Files.createDirectories(manifestFile.getParent()); Files.createDirectories(manifestFile.getParent());
JsonUtils.writeToJsonFile(manifestFile, new ModpackConfiguration<>(remoteManifest, this.manifest.getType(), this.manifest.getName(), this.manifest.getVersion(), remoteManifest.getFiles())); JsonUtils.writeToJsonFile(manifestFile, new ModpackConfiguration<>(remoteManifest, this.manifest.getType(), this.manifest.getName(), this.manifest.getVersion(), remoteManifest.getFiles()));
} }

View File

@@ -63,7 +63,7 @@ public class ServerModpackExportTask extends Task<Void> {
blackList.add(versionId + ".json"); blackList.add(versionId + ".json");
LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker"); LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker");
try (Zipper zip = new Zipper(modpackFile.toPath())) { try (Zipper zip = new Zipper(modpackFile.toPath())) {
Path runDirectory = repository.getRunDirectory(versionId).toPath(); Path runDirectory = repository.getRunDirectory(versionId);
List<ModpackConfiguration.FileInformation> files = new ArrayList<>(); List<ModpackConfiguration.FileInformation> files = new ArrayList<>();
zip.putDirectory(runDirectory, "overrides", path -> { zip.putDirectory(runDirectory, "overrides", path -> {
if (Modpack.acceptFile(path, blackList, exportInfo.getWhitelist())) { if (Modpack.acceptFile(path, blackList, exportInfo.getWhitelist())) {

View File

@@ -30,6 +30,8 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -50,10 +52,10 @@ public class ServerModpackLocalInstallTask extends Task<Void> {
this.manifest = manifest; this.manifest = manifest;
this.name = name; this.name = name;
this.repository = dependencyManager.getGameRepository(); this.repository = dependencyManager.getGameRepository();
File run = repository.getRunDirectory(name); Path run = repository.getRunDirectory(name);
File json = repository.getModpackConfiguration(name); Path json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists()) if (repository.hasVersion(name) && Files.notExists(json))
throw new IllegalArgumentException("Version " + name + " already exists."); throw new IllegalArgumentException("Version " + name + " already exists.");
GameBuilder builder = dependencyManager.gameBuilder().name(name); GameBuilder builder = dependencyManager.gameBuilder().name(name);
@@ -69,16 +71,16 @@ public class ServerModpackLocalInstallTask extends Task<Void> {
ModpackConfiguration<ServerModpackManifest> config = null; ModpackConfiguration<ServerModpackManifest> config = null;
try { try {
if (json.exists()) { if (Files.exists(json)) {
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(ServerModpackManifest.class)); config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(ServerModpackManifest.class));
if (!ServerModpackProvider.INSTANCE.getName().equals(config.getType())) if (!ServerModpackProvider.INSTANCE.getName().equals(config.getType()))
throw new IllegalArgumentException("Version " + name + " is not a Server modpack. Cannot update this version."); throw new IllegalArgumentException("Version " + name + " is not a Server modpack. Cannot update this version.");
} }
} catch (JsonParseException | IOException ignore) { } catch (JsonParseException | IOException ignore) {
} }
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList("/overrides"), any -> true, config).withStage("hmcl.modpack")); dependents.add(new ModpackInstallTask<>(zipFile, run.toFile(), modpack.getEncoding(), Collections.singletonList("/overrides"), any -> true, config).withStage("hmcl.modpack"));
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList("/overrides"), manifest, ServerModpackProvider.INSTANCE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack")); dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList("/overrides"), manifest, ServerModpackProvider.INSTANCE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name).toFile()).withStage("hmcl.modpack"));
} }
@Override @Override

View File

@@ -25,8 +25,9 @@ import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.gson.JsonUtils;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@@ -46,8 +47,8 @@ public class ServerModpackRemoteInstallTask extends Task<Void> {
this.repository = dependencyManager.getGameRepository(); this.repository = dependencyManager.getGameRepository();
this.manifest = manifest; this.manifest = manifest;
File json = repository.getModpackConfiguration(name); Path json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists()) if (repository.hasVersion(name) && Files.notExists(json))
throw new IllegalArgumentException("Version " + name + " already exists."); throw new IllegalArgumentException("Version " + name + " already exists.");
GameBuilder builder = dependencyManager.gameBuilder().name(name); GameBuilder builder = dependencyManager.gameBuilder().name(name);
@@ -63,8 +64,8 @@ public class ServerModpackRemoteInstallTask extends Task<Void> {
ModpackConfiguration<ServerModpackManifest> config; ModpackConfiguration<ServerModpackManifest> config;
try { try {
if (json.exists()) { if (Files.exists(json)) {
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(ServerModpackManifest.class)); config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(ServerModpackManifest.class));
if (!MODPACK_TYPE.equals(config.getType())) if (!MODPACK_TYPE.equals(config.getType()))
throw new IllegalArgumentException("Version " + name + " is not a Server modpack. Cannot update this version."); throw new IllegalArgumentException("Version " + name + " is not a Server modpack. Cannot update this version.");

View File

@@ -109,6 +109,14 @@ public final class FileUtils {
return fileName != null ? fileName.toString() : ""; return fileName != null ? fileName.toString() : "";
} }
public static Path toAbsolute(Path path) {
return path.toAbsolutePath().normalize();
}
public static String getAbsolutePath(Path path) {
return path.toAbsolutePath().normalize().toString();
}
// https://learn.microsoft.com/biztalk/core/restrictions-when-configuring-the-file-adapter // https://learn.microsoft.com/biztalk/core/restrictions-when-configuring-the-file-adapter
private static final Set<String> INVALID_WINDOWS_RESOURCE_BASE_NAMES = Set.of( private static final Set<String> INVALID_WINDOWS_RESOURCE_BASE_NAMES = Set.of(
"aux", "con", "nul", "prn", "clock$", "aux", "con", "nul", "prn", "clock$",
@@ -187,6 +195,18 @@ public final class FileUtils {
return true; return true;
} }
/// Safely get the file size. Returns `0` if the file does not exist or the size cannot be obtained.
public static long size(Path file) {
try {
return Files.size(file);
} catch (NoSuchFileException ignored) {
return 0L;
} catch (IOException e) {
LOG.warning("Failed to get file size of " + file, e);
return 0L;
}
}
public static String readTextMaybeNativeEncoding(Path file) throws IOException { public static String readTextMaybeNativeEncoding(Path file) throws IOException {
byte[] bytes = Files.readAllBytes(file); byte[] bytes = Files.readAllBytes(file);