将 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())
return;
Path runDir = repository.getRunDirectory(version.getId()).toPath();
Path runDir = repository.getRunDirectory(version.getId());
Path optionsFile = runDir.resolve("options.txt");
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.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
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.Pair.pair;
public class HMCLGameRepository extends DefaultGameRepository {
public final class HMCLGameRepository extends DefaultGameRepository {
private final Profile profile;
// local version settings
@@ -67,7 +66,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
public final EventManager<Event> onVersionIconChanged = new EventManager<>();
public HMCLGameRepository(Profile profile, File baseDirectory) {
public HMCLGameRepository(Profile profile, Path baseDirectory) {
super(baseDirectory);
this.profile = profile;
}
@@ -86,7 +85,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
}
@Override
public File getRunDirectory(String id) {
public Path getRunDirectory(String id) {
switch (getGameDirectoryType(id)) {
case VERSION_FOLDER:
return getVersionRoot(id);
@@ -94,7 +93,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
return super.getRunDirectory(id);
case CUSTOM:
try {
return Path.of(getVersionSetting(id).getGameDir()).toFile();
return Path.of(getVersionSetting(id).getGameDir());
} catch (InvalidPathException ignored) {
return getVersionRoot(id);
}
@@ -122,7 +121,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
});
try {
Path file = getBaseDirectory().toPath().resolve("launcher_profiles.json");
Path file = getBaseDirectory().resolve("launcher_profiles.json");
if (!Files.exists(file) && !versions.isEmpty()) {
Files.createDirectories(file.getParent());
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);
refreshVersionsAsync().start();
}
@@ -143,13 +142,13 @@ public class HMCLGameRepository extends DefaultGameRepository {
}
public void clean(String id) throws IOException {
clean(getBaseDirectory().toPath());
clean(getRunDirectory(id).toPath());
clean(getBaseDirectory());
clean(getRunDirectory(id));
}
public void duplicateVersion(String srcId, String dstId, boolean copySaves) throws IOException {
Path srcDir = getVersionRoot(srcId).toPath();
Path dstDir = getVersionRoot(dstId).toPath();
Path srcDir = getVersionRoot(srcId);
Path dstDir = getVersionRoot(dstId);
Version fromVersion = getVersion(srcId);
@@ -183,22 +182,22 @@ public class HMCLGameRepository extends DefaultGameRepository {
VersionSetting newVersionSetting = initLocalVersionSetting(dstId, oldVersionSetting);
saveVersionSetting(dstId);
File srcGameDir = getRunDirectory(srcId);
File dstGameDir = getRunDirectory(dstId);
Path srcGameDir = getRunDirectory(srcId);
Path dstGameDir = getRunDirectory(dstId);
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) {
return new File(getVersionRoot(id), "hmclversion.cfg");
private Path getLocalVersionSettingFile(String id) {
return getVersionRoot(id).resolve("hmclversion.cfg");
}
private void loadLocalVersionSetting(String id) {
File file = getLocalVersionSettingFile(id);
if (file.exists())
Path file = getLocalVersionSettingFile(id);
if (Files.exists(file))
try {
VersionSetting versionSetting = GSON.fromJson(Files.readString(file.toPath()), VersionSetting.class);
VersionSetting versionSetting = JsonUtils.fromJsonFile(file, VersionSetting.class);
initLocalVersionSetting(id, versionSetting);
} catch (Exception ex) {
// 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;
}
public Optional<File> getVersionIconFile(String id) {
File root = getVersionRoot(id);
public Optional<Path> getVersionIconFile(String id) {
Path root = getVersionRoot(id);
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
File file = new File(root, "icon." + extension);
if (file.exists()) {
Path file = root.resolve("icon." + extension);
if (Files.exists(file)) {
return Optional.of(file);
}
}
@@ -274,21 +273,26 @@ public class HMCLGameRepository extends DefaultGameRepository {
return Optional.empty();
}
public void setVersionIconFile(String id, File iconFile) throws IOException {
String ext = FileUtils.getExtension(iconFile.getName()).toLowerCase(Locale.ROOT);
public void setVersionIconFile(String id, Path iconFile) throws IOException {
String ext = FileUtils.getExtension(iconFile).toLowerCase(Locale.ROOT);
if (!FXUtils.IMAGE_EXTENSIONS.contains(ext)) {
throw new IllegalArgumentException("Unsupported icon file: " + ext);
}
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) {
File root = getVersionRoot(id);
Path root = getVersionRoot(id);
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) {
Version version = getVersion(id).resolve(this);
Optional<File> iconFile = getVersionIconFile(id);
Optional<Path> iconFile = getVersionIconFile(id);
if (iconFile.isPresent()) {
try {
return FXUtils.loadImage(iconFile.get().toPath());
return FXUtils.loadImage(iconFile.get());
} catch (Exception 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) {
if (!localVersionSettings.containsKey(id))
return;
Path file = getLocalVersionSettingFile(id).toPath().toAbsolutePath().normalize();
Path file = getLocalVersionSettingFile(id).toAbsolutePath().normalize();
try {
Files.createDirectories(file.getParent());
} catch (IOException e) {
@@ -373,7 +377,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
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);
LaunchOptions.Builder builder = new LaunchOptions.Builder()
@@ -430,15 +434,15 @@ public class HMCLGameRepository extends DefaultGameRepository {
}
}
File json = getModpackConfiguration(version);
if (json.exists()) {
Path json = getModpackConfiguration(version);
if (Files.exists(json)) {
try {
String jsonText = Files.readString(json.toPath());
String jsonText = Files.readString(json);
ModpackConfiguration<?> modpackConfiguration = JsonUtils.GSON.fromJson(jsonText, ModpackConfiguration.class);
ModpackProvider provider = ModpackHelper.getProviderByType(modpackConfiguration.getType());
if (provider != null) provider.injectLaunchOptions(jsonText, builder);
} 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
public File getModpackConfiguration(String version) {
return new File(getVersionRoot(version), "modpack.cfg");
public Path getModpackConfiguration(String version) {
return getVersionRoot(version).resolve("modpack.cfg");
}
public void markVersionAsModpack(String id) {
@@ -463,16 +467,24 @@ public class HMCLGameRepository extends DefaultGameRepository {
public void markVersionLaunchedAbnormally(String id) {
try {
Files.createFile(getVersionRoot(id).toPath().resolve(".abnormal"));
Files.createFile(getVersionRoot(id).resolve(".abnormal"));
} catch (IOException ignored) {
}
}
public boolean unmarkVersionLaunchedAbnormally(String id) {
File file = new File(getVersionRoot(id), ".abnormal");
boolean result = file.isFile();
file.delete();
return result;
Path file = getVersionRoot(id).resolve(".abnormal");
if (Files.isRegularFile(file)) {
try {
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()

View File

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

View File

@@ -55,6 +55,7 @@ import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.nio.file.AccessDeniedException;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
@@ -150,7 +151,7 @@ public final class LauncherHelper {
dependencyManager.checkGameCompletionAsync(version.get(), integrityCheck),
Task.composeAsync(() -> {
try {
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion).toFile());
ModpackProvider provider = ModpackHelper.getProviderByType(configuration.getType());
if (provider == null) return null;
else return provider.createCompletionTask(dependencyManager, selectedVersion);
@@ -164,16 +165,16 @@ public final class LauncherHelper {
Library lib = NativePatcher.getWindowsMesaLoader(java, renderer, OperatingSystem.SYSTEM_VERSION);
if (lib == null)
return null;
File file = dependencyManager.getGameRepository().getLibraryFile(version.get(), lib);
if (file.getAbsolutePath().indexOf('=') >= 0) {
Path file = dependencyManager.getGameRepository().getLibraryFile(version.get(), lib);
if (file.toAbsolutePath().toString().indexOf('=') >= 0) {
LOG.warning("Invalid character '=' in the libraries directory path, unable to attach software renderer loader");
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)) {
return new LibraryDownloadTask(dependencyManager, file.toPath(), lib)
return new LibraryDownloadTask(dependencyManager, file, lib)
.thenRunAsync(() -> javaAgents.add(agent));
} else {
javaAgents.add(agent);
@@ -189,7 +190,7 @@ public final class LauncherHelper {
.thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in"))
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
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));

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) {
Path runDirectory = gameRepository.getRunDirectory(versionId).toPath();
Path baseDirectory = gameRepository.getBaseDirectory().toPath();
Path runDirectory = gameRepository.getRunDirectory(versionId);
Path baseDirectory = gameRepository.getBaseDirectory();
List<String> versions = new ArrayList<>();
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) {
this.name = new SimpleStringProperty(this, "name", name);
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.selectedVersion.set(selectedVersion);
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());
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<>());
}), 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)) {
return pair(new HashSet<CrashReportAnalyzer.Result>(), new HashSet<String>());
}
@@ -379,7 +379,7 @@ public class GameCrashWindow extends Stage {
TwoLineListItem gameDir = new TwoLineListItem();
gameDir.getStyleClass().setAll("two-line-item-second-large");
gameDir.setTitle(i18n("game.directory"));
gameDir.setSubtitle(launchOptions.getGameDir().getAbsolutePath());
gameDir.setSubtitle(launchOptions.getGameDir().toAbsolutePath().toString());
FXUtils.installFastTooltip(gameDir, i18n("game.directory"));
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) {
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) -> {
if (!FileUtils.isNameValid(result)) {

View File

@@ -95,9 +95,9 @@ public class ModpackInstallWizardProvider implements WizardProvider {
}
try {
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 {
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) {
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;
JFXTreeView<String> treeView = new JFXTreeView<>();
rootNode = getTreeItem(profile.getRepository().getRunDirectory(version), "minecraft");
rootNode = getTreeItem(profile.getRepository().getRunDirectory(version).toFile(), "minecraft");
treeView.setRoot(rootNode);
treeView.setSelectionModel(new NoneMultipleSelectionModel<>());
this.setCenter(treeView);

View File

@@ -245,12 +245,12 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
String nativesDirName = "natives-" + Platform.SYSTEM_PLATFORM;
if (versionId == null) {
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"),
nativesDirName
);
} else {
return profile.getRepository().getVersionRoot(versionId).toPath()
return profile.getRepository().getVersionRoot(versionId)
.toAbsolutePath().normalize()
.resolve(nativesDirName)
.toString();

View File

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

View File

@@ -78,7 +78,7 @@ public class VersionIconDialog extends DialogPane {
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
if (selectedFile != null) {
try {
profile.getRepository().setVersionIconFile(versionId, selectedFile);
profile.getRepository().setVersionIconFile(versionId, selectedFile.toPath());
if (vs != null) {
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.util.io.FileUtils;
import java.io.File;
import java.util.Optional;
import java.util.function.Supplier;
@@ -158,7 +157,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
}
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() {
@@ -166,14 +165,14 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
}
private void clearLibraries() {
FileUtils.deleteDirectoryQuietly(getProfile().getRepository().getBaseDirectory().toPath().resolve("libraries"));
FileUtils.deleteDirectoryQuietly(getProfile().getRepository().getBaseDirectory().resolve("libraries"));
}
private void clearAssets() {
HMCLGameRepository baseDirectory = getProfile().getRepository();
FileUtils.deleteDirectoryQuietly(baseDirectory.getBaseDirectory().toPath().resolve("assets"));
FileUtils.deleteDirectoryQuietly(baseDirectory.getBaseDirectory().resolve("assets"));
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.versioning.GameVersionNumber;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -635,7 +634,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
versionSetting.javaVersionProperty().addListener(javaListener);
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()));
lastVersionSetting = versionSetting;

View File

@@ -143,7 +143,7 @@ public final class Versions {
}
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) {
@@ -193,8 +193,8 @@ public final class Versions {
ensureSelectedAccount(account -> {
GameRepository repository = profile.getRepository();
FileChooser chooser = new FileChooser();
if (repository.getRunDirectory(id).isDirectory())
chooser.setInitialDirectory(repository.getRunDirectory(id));
if (Files.isDirectory(repository.getRunDirectory(id)))
chooser.setInitialDirectory(repository.getRunDirectory(id).toFile());
chooser.setTitle(i18n("version.launch_script.save"));
chooser.getExtensionFilters().add(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
? 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.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
@@ -49,11 +49,13 @@ public class GameCrashWindowTest {
CountDownLatch latch = new CountDownLatch(1);
FXUtils.runInFX(() -> {
Path workingPath = Path.of(System.getProperty("user.dir"));
GameCrashWindow window = new GameCrashWindow(process, ProcessListener.ExitType.APPLICATION_ERROR, null,
new ClassicVersion(),
new LaunchOptions.Builder()
.setJava(new JavaRuntime(Paths.get("."), new JavaInfo(Platform.SYSTEM_PLATFORM, "16", null), false, false))
.setGameDir(new File("."))
.setJava(new JavaRuntime(workingPath, new JavaInfo(Platform.SYSTEM_PLATFORM, "16", null), false, false))
.setGameDir(workingPath)
.create(),
Arrays.stream(logs.split("\\n"))
.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.Version;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
@@ -79,11 +80,11 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
public Task<?> checkGameCompletionAsync(Version version, boolean integrityCheck) {
return Task.allOf(
Task.composeAsync(() -> {
File versionJar = repository.getVersionJar(version);
if (!versionJar.exists() || versionJar.length() == 0)
return new GameDownloadTask(this, null, version);
else
return null;
Path versionJar = repository.getVersionJar(version);
return Files.notExists(versionJar) || FileUtils.size(versionJar) == 0L
? new GameDownloadTask(this, null, version)
: null;
}).thenComposeAsync(checkPatchCompletionAsync(version, integrityCheck)),
new GameAssetDownloadTask(this, version, GameAssetDownloadTask.DOWNLOAD_INDEX_IF_NECESSARY, integrityCheck)
.setSignificance(Task.TaskSignificance.MODERATE),
@@ -134,7 +135,7 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
if (GameLibrariesTask.shouldDownloadLibrary(repository, version, installer, integrityCheck)) {
tasks.add(installLibraryAsync(gameVersion, original, "optifine", optifinePatchVersion));
} 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 -> {
builder.addJvmArgument("-Dhmcl.transformer.candidates=${library_directory}/" + library.getPath());
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")) {
Files.createDirectories(libraryPath.getParent());
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}.
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.
// 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);
if (library.is("optifine", "OptiFine")) {
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);
// OptiFine should be loaded after Forge in classpath.
// 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 {
dependencies.add(new FileDownloadTask(
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())
);
}

View File

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

View File

@@ -72,7 +72,7 @@ public class ForgeOldInstallTask extends Task<Version> {
// unpack the universal jar in the installer file.
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());
ZipEntry forgeEntry = zipFile.getEntry(installProfile.getInstall().getFilePath());

View File

@@ -53,7 +53,7 @@ public final class GameDownloadTask extends Task<Void> {
@Override
public void execute() {
Path jar = dependencyManager.getGameRepository().getVersionJar(version).toPath();
Path jar = dependencyManager.getGameRepository().getVersionJar(version);
var task = new FileDownloadTask(
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) {
Path file = gameRepository.getLibraryFile(version, library).toPath();
Path file = gameRepository.getLibraryFile(version, library);
if (!Files.isRegularFile(file)) return true;
if (!integrityCheck) {
@@ -142,8 +142,7 @@ public final class GameLibrariesTask extends Task<Void> {
&& gameRepository instanceof DefaultGameRepository defaultGameRepository) {
List<FMLLib> fmlLibs = getFMLLibs(library.getVersion());
if (fmlLibs != null) {
Path libDir = defaultGameRepository.getBaseDirectory().toPath()
.resolve("lib")
Path libDir = defaultGameRepository.getBaseDirectory().resolve("lib")
.toAbsolutePath().normalize();
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) {
String forgeVersion = LibraryAnalyzer.analyze(version, "1.20.4")
.getVersion(LibraryAnalyzer.LibraryType.FORGE)

View File

@@ -62,7 +62,7 @@ public final class GameVerificationFixTask extends Task<Void> {
@Override
public void execute() throws IOException {
Path jar = dependencyManager.getGameRepository().getVersionJar(version).toPath();
Path jar = dependencyManager.getGameRepository().getVersionJar(version);
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, gameVersion);
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
public void execute() throws Exception {
Path json = repository.getVersionJson(version.getId()).toPath().toAbsolutePath();
Path json = repository.getVersionJson(version.getId()).toAbsolutePath();
Files.createDirectories(json.getParent());
JsonUtils.writeToJsonFile(json, version);
}

View File

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

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

View File

@@ -59,7 +59,7 @@ public final class QuiltAPIInstallTask extends Task<Version> {
public void execute() throws IOException {
dependencies.add(new FileDownloadTask(
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())
);
}

View File

@@ -17,16 +17,15 @@
*/
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.util.Arrays;
/**
* The Minecraft version for 1.5.x and earlier.
*
* @author huangyuhui
*/
public class ClassicVersion extends Version {
/// The Minecraft version for 1.5.x and earlier.
///
/// @author huangyuhui
public final class ClassicVersion extends Version {
public ClassicVersion() {
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);
}
private static class ClassicLibrary extends Library {
private static final class ClassicLibrary extends Library {
public ClassicLibrary(String name) {
super(new Artifact("", "", ""), null,
new LibrariesDownloadInfo(new LibraryDownloadInfo("bin/" + name + ".jar"), null),
@@ -44,11 +42,11 @@ public class ClassicVersion extends Version {
}
}
public static boolean hasClassicVersion(File baseDirectory) {
File bin = new File(baseDirectory, "bin");
return bin.exists()
&& new File(bin, "lwjgl.jar").exists()
&& new File(bin, "jinput.jar").exists()
&& new File(bin, "lwjgl_util.jar").exists();
public static boolean hasClassicVersion(Path baseDirectory) {
Path bin = baseDirectory.resolve("bin");
return Files.isDirectory(bin)
&& Files.exists(bin.resolve("lwjgl.jar"))
&& Files.exists(bin.resolve("jinput.jar"))
&& 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.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
@@ -50,19 +50,19 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
*/
public class DefaultGameRepository implements GameRepository {
private File baseDirectory;
private Path baseDirectory;
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;
}
public File getBaseDirectory() {
public Path getBaseDirectory() {
return baseDirectory;
}
public void setBaseDirectory(File baseDirectory) {
public void setBaseDirectory(Path baseDirectory) {
this.baseDirectory = baseDirectory;
}
@@ -89,25 +89,25 @@ public class DefaultGameRepository implements GameRepository {
}
@Override
public File getLibrariesDirectory(Version version) {
return new File(getBaseDirectory(), "libraries");
public Path getLibrariesDirectory(Version version) {
return getBaseDirectory().resolve("libraries");
}
@Override
public File getLibraryFile(Version version, Library lib) {
public Path getLibraryFile(Version version, Library lib) {
if ("local".equals(lib.getHint())) {
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) {
return artifact.getPath(getBaseDirectory().toPath().resolve("libraries"));
return artifact.getPath(getBaseDirectory().resolve("libraries"));
}
public GameDirectoryType getGameDirectoryType(String id) {
@@ -115,22 +115,19 @@ public class DefaultGameRepository implements GameRepository {
}
@Override
public File getRunDirectory(String id) {
switch (getGameDirectoryType(id)) {
case VERSION_FOLDER:
return getVersionRoot(id);
case ROOT_FOLDER:
return getBaseDirectory();
default:
throw new IllegalStateException();
}
public Path getRunDirectory(String id) {
return switch (getGameDirectoryType(id)) {
case VERSION_FOLDER -> getVersionRoot(id);
case ROOT_FOLDER -> getBaseDirectory();
default -> throw new IllegalStateException();
};
}
@Override
public File getVersionJar(Version version) {
public Path getVersionJar(Version version) {
Version v = version.resolve(this);
String id = Optional.ofNullable(v.getJar()).orElse(v.getId());
return new File(getVersionRoot(id), id + ".jar");
return getVersionRoot(id).resolve(id + ".jar");
}
@Override
@@ -140,33 +137,33 @@ public class DefaultGameRepository implements GameRepository {
// be consistent.
return gameVersions.computeIfAbsent(getVersionJar(version), versionJar -> {
Optional<String> gameVersion = GameVersion.minecraftVersion(versionJar);
if (!gameVersion.isPresent()) {
LOG.warning("Cannot find out game version of " + version.getId() + ", primary jar: " + versionJar.toString() + ", jar exists: " + versionJar.exists());
if (gameVersion.isEmpty()) {
LOG.warning("Cannot find out game version of " + version.getId() + ", primary jar: " + versionJar.toString() + ", jar exists: " + Files.exists(versionJar));
}
return gameVersion;
});
}
@Override
public File getNativeDirectory(String id, Platform platform) {
return new File(getVersionRoot(id), "natives-" + platform);
public Path getNativeDirectory(String id, Platform platform) {
return getVersionRoot(id).resolve("natives-" + platform);
}
@Override
public File getVersionRoot(String id) {
return new File(getBaseDirectory(), "versions/" + id);
public Path getVersionRoot(String id) {
return getBaseDirectory().resolve("versions/" + id);
}
public File getVersionJson(String id) {
return new File(getVersionRoot(id), id + ".json");
public Path getVersionJson(String id) {
return getVersionRoot(id).resolve(id + ".json");
}
public Version readVersionJson(String id) throws IOException, JsonParseException {
return readVersionJson(getVersionJson(id));
}
public Version readVersionJson(File file) throws IOException, JsonParseException {
String jsonText = Files.readString(file.toPath());
public Version readVersionJson(Path file) throws IOException, JsonParseException {
String jsonText = Files.readString(file);
try {
// Try TLauncher version json format
return JsonUtils.fromNonNullJson(jsonText, TLauncherVersion.class).toVersion();
@@ -179,7 +176,7 @@ public class DefaultGameRepository implements GameRepository {
} 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");
}
@@ -190,8 +187,8 @@ public class DefaultGameRepository implements GameRepository {
try {
Version fromVersion = getVersion(from);
Path fromDir = getVersionRoot(from).toPath();
Path toDir = getVersionRoot(to).toPath();
Path fromDir = getVersionRoot(from);
Path toDir = getVersionRoot(to);
Files.move(fromDir, toDir);
Path fromJson = toDir.resolve(from + ".json");
@@ -219,7 +216,7 @@ public class DefaultGameRepository implements GameRepository {
// fix inheritsFrom of versions that inherits from version [from].
for (Version version : getVersions()) {
if (from.equals(version.getInheritsFrom())) {
Path targetPath = getVersionJson(version.getId()).toPath();
Path targetPath = getVersionJson(version.getId());
Files.createDirectories(targetPath.getParent());
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)
return false;
if (!versions.containsKey(id))
return FileUtils.deleteDirectoryQuietly(getVersionRoot(id).toPath());
File file = getVersionRoot(id);
if (!file.exists())
return FileUtils.deleteDirectoryQuietly(getVersionRoot(id));
Path file = getVersionRoot(id);
if (Files.notExists(file))
return true;
// test if no file in this version directory is occupied.
File removedFile = new File(file.getAbsoluteFile().getParentFile(), file.getName() + "_removed");
if (!file.renameTo(removedFile))
Path removedFile = file.toAbsolutePath().resolveSibling(FileUtils.getName(file) + "_removed");
try {
Files.move(file, removedFile, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
LOG.warning("Failed to rename file " + file, e);
return false;
}
try {
versions.remove(id);
if (FileUtils.moveToTrash(removedFile.toPath())) {
if (FileUtils.moveToTrash(removedFile)) {
return true;
}
// 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 {
Files.delete(path);
} 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.
try {
FileUtils.deleteDirectory(removedFile.toPath());
FileUtils.deleteDirectory(removedFile);
} catch (IOException e) {
LOG.warning("Unable to remove version folder: " + file, e);
}
@@ -283,28 +284,36 @@ public class DefaultGameRepository implements GameRepository {
SimpleVersionProvider provider = new SimpleVersionProvider();
File[] files = new File(getBaseDirectory(), "versions").listFiles();
if (files != null)
Arrays.stream(files).parallel().filter(File::isDirectory).flatMap(dir -> {
String id = dir.getName();
File json = new File(dir, id + ".json");
Path versionsDir = getBaseDirectory().resolve("versions");
if (Files.isDirectory(versionsDir)) {
try (Stream<Path> stream = Files.list(versionsDir)) {
stream.parallel().filter(Files::isDirectory).flatMap(dir -> {
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,
// we will find the only json and rename it to correct name.
if (!json.exists()) {
List<Path> jsons = FileUtils.listFilesByExtension(dir.toPath(), "json");
if (Files.notExists(json)) {
List<Path> jsons = FileUtils.listFilesByExtension(dir, "json");
if (jsons.size() == 1) {
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();
}
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);
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();
@@ -317,7 +326,7 @@ public class DefaultGameRepository implements GameRepository {
} 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)
if (EventBus.EVENT_BUS.fireEvent(new GameJsonParseFailedEvent(this, json.toFile(), id)) != Event.Result.ALLOW)
return Stream.empty();
try {
@@ -332,8 +341,8 @@ public class DefaultGameRepository implements GameRepository {
try {
String from = id;
String to = version.getId();
Path fromDir = getVersionRoot(from).toPath();
Path toDir = getVersionRoot(to).toPath();
Path fromDir = getVersionRoot(from);
Path toDir = getVersionRoot(to);
Files.move(fromDir, toDir);
Path fromJson = toDir.resolve(from + ".json");
@@ -360,6 +369,10 @@ public class DefaultGameRepository implements GameRepository {
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()) {
try {
@@ -407,7 +420,7 @@ public class DefaultGameRepository implements GameRepository {
@Override
public Path getAssetDirectory(String version, String assetId) {
return getBaseDirectory().toPath().resolve("assets");
return getBaseDirectory().resolve("assets");
}
@Override
@@ -456,7 +469,7 @@ public class DefaultGameRepository implements GameRepository {
return assetsDir;
if (index.isVirtual()) {
Path resourcesDir = getRunDirectory(version).toPath().resolve("resources");
Path resourcesDir = getRunDirectory(version).resolve("resources");
int cnt = 0;
int tot = index.getObjects().size();
@@ -499,8 +512,8 @@ public class DefaultGameRepository implements GameRepository {
return versions != null;
}
public File getModpackConfiguration(String version) {
return new File(getVersionRoot(version), "modpack.json");
public Path getModpackConfiguration(String version) {
return getVersionRoot(version).resolve("modpack.json");
}
/**
@@ -514,13 +527,13 @@ public class DefaultGameRepository implements GameRepository {
@Nullable
public ModpackConfiguration<?> readModpackConfiguration(String version) throws IOException, VersionNotFoundException {
if (!hasVersion(version)) throw new VersionNotFoundException(version);
File file = getModpackConfiguration(version);
if (!file.exists()) return null;
return JsonUtils.fromJsonFile(file.toPath(), ModpackConfiguration.class);
Path file = getModpackConfiguration(version);
if (Files.notExists(file)) return null;
return JsonUtils.fromJsonFile(file, ModpackConfiguration.class);
}
public boolean isModpack(String version) {
return getModpackConfiguration(version).exists();
return Files.exists(getModpackConfiguration(version));
}
public ModManager getModManager(String version) {
@@ -528,15 +541,15 @@ public class DefaultGameRepository implements GameRepository {
}
public Path getSavesDirectory(String id) {
return getRunDirectory(id).toPath().resolve("saves");
return getRunDirectory(id).resolve("saves");
}
public Path getBackupsDirectory(String id) {
return getRunDirectory(id).toPath().resolve("backups");
return getRunDirectory(id).resolve("backups");
}
public Path getSchematicsDirectory(String id) {
return getRunDirectory(id).toPath().resolve("schematics");
return getRunDirectory(id).resolve("schematics");
}
@Override

View File

@@ -18,10 +18,11 @@
package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.platform.Platform;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.LinkedHashSet;
@@ -30,7 +31,7 @@ import java.util.Set;
/**
* Supports operations on versioning.
*
* <p>
* Note that game repository will not do any operations which need connection with Internet, if do,
* see {@link org.jackhuang.hmcl.download.DependencyManager}
*
@@ -79,7 +80,7 @@ public interface GameRepository extends VersionProvider {
/**
* Load version list.
*
* <p>
* This method should be called before launching a version.
* A time-costly operation.
* 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.
* 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.
*
* @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.
@@ -114,11 +115,11 @@ public interface GameRepository extends VersionProvider {
* @param lib the library, {@link Version#getLibraries()}
* @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.
*
* <p>
* You'd better return a unique directory.
* 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}
@@ -128,7 +129,7 @@ public interface GameRepository extends VersionProvider {
* @param platform the platform of native libraries
* @return the native directory
*/
File getNativeDirectory(String id, Platform platform);
Path getNativeDirectory(String id, Platform platform);
/**
* Get minecraft jar
@@ -136,11 +137,11 @@ public interface GameRepository extends VersionProvider {
* @param version resolvedVersion
* @return the minecraft jar
*/
File getVersionJar(Version version);
Path getVersionJar(Version version);
/**
* Detect game version.
*
* <p>
* This method is time-consuming, but the result will be cached.
* Consider running this job in IO scheduler.
*
@@ -151,7 +152,7 @@ public interface GameRepository extends VersionProvider {
/**
* Detect game version.
*
* <p>
* This method is time-consuming, but the result will be cached.
* Consider running this job in IO scheduler.
*
@@ -168,7 +169,7 @@ public interface GameRepository extends VersionProvider {
* @param version version id
* @return the minecraft jar
*/
default File getVersionJar(String version) throws VersionNotFoundException {
default Path getVersionJar(String version) throws VersionNotFoundException {
return getVersionJar(getVersion(version).resolve(this));
}
@@ -254,9 +255,9 @@ public interface GameRepository extends VersionProvider {
Set<String> classpath = new LinkedHashSet<>();
for (Library library : version.getLibraries())
if (library.appliesToCurrentEnvironment() && !library.isNative()) {
File f = getLibraryFile(version, library);
if (f.exists() && f.isFile())
classpath.add(f.getAbsolutePath());
Path f = getLibraryFile(version, library);
if (Files.isRegularFile(f))
classpath.add(FileUtils.getAbsolutePath(f));
}
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.StringConstant;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -89,11 +90,11 @@ final class GameVersion {
return Optional.empty();
}
public static Optional<String> minecraftVersion(File file) {
if (file == null || !file.exists() || !file.isFile() || !file.canRead())
public static Optional<String> minecraftVersion(Path file) {
if (file == null || !Files.isRegularFile(file))
return Optional.empty();
try (ZipFile gameJar = new ZipFile(file)) {
try (var gameJar = new ZipFile(file.toFile())) {
ZipEntry versionJson = gameJar.getEntry("version.json");
if (versionJson != null) {
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.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.Serializable;
import java.net.Proxy;
import java.nio.file.Path;
import java.util.*;
/**
@@ -31,7 +31,7 @@ import java.util.*;
*/
public class LaunchOptions implements Serializable {
private File gameDir;
private Path gameDir;
private JavaRuntime java;
private String versionName;
private String versionType;
@@ -68,7 +68,7 @@ public class LaunchOptions implements Serializable {
/**
* The game directory
*/
public File getGameDir() {
public Path getGameDir() {
return gameDir;
}
@@ -314,7 +314,7 @@ public class LaunchOptions implements Serializable {
return options.javaAgents;
}
public Builder setGameDir(File gameDir) {
public Builder setGameDir(Path gameDir) {
options.gameDir = gameDir;
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.LibraryDownloadInfo;
import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.gson.JsonSerializable;
import java.util.List;
import java.util.Map;
@Immutable
public class TLauncherLibrary {
@JsonSerializable
public final class TLauncherLibrary {
@SerializedName("name")
private final Artifact name;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -68,10 +68,10 @@ public final class CurseInstallTask extends Task<Void> {
this.manifest = manifest;
this.name = name;
this.repository = dependencyManager.getGameRepository();
this.run = repository.getRunDirectory(name);
this.run = repository.getRunDirectory(name).toFile();
File json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists())
Path json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && Files.notExists(json))
throw new IllegalArgumentException("Version " + name + " already exists.");
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;
try {
if (json.exists()) {
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(CurseManifest.class));
if (Files.exists(json)) {
config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(CurseManifest.class));
if (!CurseModpackProvider.INSTANCE.getName().equals(config.getType()))
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;
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));
}
@@ -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);
JsonUtils.writeToJsonFile(root.resolve("manifest.json"), manifest);
}

View File

@@ -73,7 +73,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
this.repository = dependencyManager.getGameRepository();
this.modManager = repository.getModManager(version);
this.version = version;
this.configurationFile = repository.getModpackConfiguration(version);
this.configurationFile = repository.getModpackConfiguration(version).toFile();
this.configuration = configuration;
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);
}
Path rootPath = repository.getVersionRoot(version).toPath();
Path rootPath = repository.getVersionRoot(version);
Files.createDirectories(rootPath);
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);
return executor.all(tasks.stream().filter(Objects::nonNull).collect(Collectors.toList()));
})).thenAcceptAsync(wrapConsumer(unused1 -> {
Path manifestFile = repository.getModpackConfiguration(version).toPath();
Path manifestFile = repository.getModpackConfiguration(version);
JsonUtils.writeToJsonFile(manifestFile,
new ModpackConfiguration<>(manifest, this.configuration.getType(), this.manifest.getName(), this.manifest.getVersion(),
this.manifest.getFiles().stream()
@@ -274,7 +274,7 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
@Nullable
private Path getFilePath(McbbsModpackManifest.File file) {
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) {
String fileName = ((McbbsModpackManifest.CurseFile) file).getFileName();
if (fileName == null) return null;

View File

@@ -67,7 +67,7 @@ public class McbbsModpackExportTask extends Task<Void> {
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");
try (Zipper zip = new Zipper(modpackFile.toPath())) {
Path runDirectory = repository.getRunDirectory(version).toPath();
Path runDirectory = repository.getRunDirectory(version);
List<McbbsModpackManifest.File> files = new ArrayList<>();
zip.putDirectory(runDirectory, "overrides", path -> {
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.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -56,10 +58,10 @@ public class McbbsModpackLocalInstallTask extends Task<Void> {
this.manifest = manifest;
this.name = name;
this.repository = dependencyManager.getGameRepository();
File run = repository.getRunDirectory(name);
Path run = repository.getRunDirectory(name);
File json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists())
Path json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && Files.notExists(json))
throw new IllegalArgumentException("Version " + name + " already exists.");
this.update = repository.hasVersion(name);
@@ -77,16 +79,16 @@ public class McbbsModpackLocalInstallTask extends Task<Void> {
ModpackConfiguration<McbbsModpackManifest> config = null;
try {
if (json.exists()) {
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(McbbsModpackManifest.class));
if (Files.exists(json)) {
config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(McbbsModpackManifest.class));
if (!McbbsModpackProvider.INSTANCE.getName().equals(config.getType()))
throw new IllegalArgumentException("Version " + name + " is not a Mcbbs modpack. Cannot update this version.");
}
} catch (JsonParseException | IOException ignore) {
}
dependents.add(new ModpackInstallTask<>(zipFile, run, 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));
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).toFile());
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.util.gson.JsonUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -46,8 +47,8 @@ public class McbbsModpackRemoteInstallTask extends Task<Void> {
this.repository = dependencyManager.getGameRepository();
this.manifest = manifest;
File json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists())
Path json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && Files.notExists(json))
throw new IllegalArgumentException("Version " + name + " already exists.");
GameBuilder builder = dependencyManager.gameBuilder().name(name);
@@ -63,8 +64,8 @@ public class McbbsModpackRemoteInstallTask extends Task<Void> {
ModpackConfiguration<McbbsModpackManifest> config;
try {
if (json.exists()) {
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(McbbsModpackManifest.class));
if (Files.exists(json)) {
config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(McbbsModpackManifest.class));
if (!MODPACK_TYPE.equals(config.getType()))
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)
try {
Path manifestFile = repository.getVersionRoot(version).toPath().resolve("modrinth.index.json");
Path manifestFile = repository.getVersionRoot(version).resolve("modrinth.index.json");
if (Files.exists(manifestFile))
this.manifest = JsonUtils.fromJsonFile(manifestFile, ModrinthManifest.class);
} catch (Exception e) {
@@ -104,7 +104,7 @@ public class ModrinthCompletionTask extends Task<Void> {
if (manifest == null)
return;
Path runDirectory = repository.getRunDirectory(version).toPath().toAbsolutePath().normalize();
Path runDirectory = FileUtils.toAbsolute(repository.getRunDirectory(version));
Path modsDirectory = runDirectory.resolve("mods");
for (ModrinthManifest.File file : manifest.getFiles()) {

View File

@@ -51,10 +51,10 @@ public class ModrinthInstallTask extends Task<Void> {
this.manifest = manifest;
this.name = name;
this.repository = dependencyManager.getGameRepository();
this.run = repository.getRunDirectory(name);
this.run = repository.getRunDirectory(name).toFile();
File json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists())
Path json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && Files.notExists(json))
throw new IllegalArgumentException("Version " + name + " already exists.");
GameBuilder builder = dependencyManager.gameBuilder().name(name).gameVersion(manifest.getGameVersion());
@@ -91,8 +91,8 @@ public class ModrinthInstallTask extends Task<Void> {
ModpackConfiguration<ModrinthManifest> config = null;
try {
if (json.exists()) {
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(ModrinthManifest.class));
if (Files.exists(json)) {
config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(ModrinthManifest.class));
if (!ModrinthModpackProvider.INSTANCE.getName().equals(config.getType()))
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;
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 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));
}
@@ -131,7 +131,7 @@ public class ModrinthInstallTask extends Task<Void> {
}
}
Path root = repository.getVersionRoot(name).toPath();
Path root = repository.getVersionRoot(name);
Files.createDirectories(root);
JsonUtils.writeToJsonFile(root.resolve("modrinth.index.json"), manifest);
}

View File

@@ -108,7 +108,7 @@ public class ModrinthModpackExportTask extends Task<Void> {
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");
try (Zipper zip = new Zipper(modpackFile.toPath())) {
Path runDirectory = repository.getRunDirectory(version).toPath();
Path runDirectory = repository.getRunDirectory(version);
List<ModrinthManifest.File> files = new ArrayList<>();
Set<String> filesInManifest = new HashSet<>();

View File

@@ -69,7 +69,7 @@ public class MultiMCModpackExportTask extends Task<Void> {
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");
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)
.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.repository = dependencyManager.getGameRepository();
File json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists())
Path json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && Files.notExists(json))
throw new IllegalArgumentException("Version " + name + " already exists.");
onDone().register(event -> {
@@ -113,13 +113,13 @@ public final class MultiMCModpackInstallTask extends Task<MultiMCInstancePatch.R
public void preExecute() throws Exception {
// Stage #0: General Setup
{
File run = repository.getRunDirectory(name);
File json = repository.getModpackConfiguration(name);
Path run = repository.getRunDirectory(name);
Path json = repository.getModpackConfiguration(name);
ModpackConfiguration<MultiMCInstanceConfiguration> config = null;
try {
if (json.exists()) {
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(MultiMCInstanceConfiguration.class));
if (Files.exists(json)) {
config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(MultiMCInstanceConfiguration.class));
if (!MultiMCModpackProvider.INSTANCE.getName().equals(config.getType()))
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
dependents.add(new ModpackInstallTask<>(zipFile, run, 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 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).toFile()).withStage("hmcl.modpack"));
}
// 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");
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()) {
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.
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. */
Path from = repository.getLibraryFile(artifact.getVersion(), library).toPath();
Path target = repository.getLibraryFile(artifact.getVersion(), library.withoutCommunityFields()).toPath();
Path from = repository.getLibraryFile(artifact.getVersion(), library);
Path target = repository.getLibraryFile(artifact.getVersion(), library.withoutCommunityFields());
Files.createDirectories(target.getParent());
Files.copy(from, target, StandardCopyOption.REPLACE_EXISTING);
}
}
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.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) {
Path iconFile = root.resolve(iconKey + ".png");
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");
try (FileSystem mc = CompressingUtils.writable(
repository.getVersionRoot(name).toPath().resolve(name + ".jar")
repository.getVersionRoot(name).resolve(name + ".jar")
).setAutoDetectEncoding(true).build()) {
for (String fileName : files) {
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.gson.JsonUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -61,9 +60,9 @@ public class ServerModpackCompletionTask extends Task<Void> {
if (manifest == null) {
try {
File manifestFile = repository.getModpackConfiguration(version);
if (manifestFile.exists()) {
this.manifest = JsonUtils.fromJsonFile(manifestFile.toPath(), ModpackConfiguration.typeOf(ServerModpackManifest.class));
Path manifestFile = repository.getModpackConfiguration(version);
if (Files.exists(manifestFile)) {
this.manifest = JsonUtils.fromJsonFile(manifestFile, ModpackConfiguration.typeOf(ServerModpackManifest.class));
}
} catch (Exception 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());
}
Path rootPath = repository.getVersionRoot(version).toPath().toAbsolutePath().normalize();
Path rootPath = repository.getVersionRoot(version).toAbsolutePath().normalize();
Map<String, ModpackConfiguration.FileInformation> files = manifest.getManifest().getFiles().stream()
.collect(Collectors.toMap(ModpackConfiguration.FileInformation::getPath,
Function.identity()));
@@ -129,7 +128,7 @@ public class ServerModpackCompletionTask extends Task<Void> {
Set<String> remoteFiles = remoteManifest.getFiles().stream().map(ModpackConfiguration.FileInformation::getPath)
.collect(Collectors.toSet());
Path runDirectory = repository.getRunDirectory(version).toPath().toAbsolutePath().normalize();
Path runDirectory = repository.getRunDirectory(version).toAbsolutePath().normalize();
Path modsDirectory = runDirectory.resolve("mods");
int total = 0;
@@ -189,7 +188,7 @@ public class ServerModpackCompletionTask extends Task<Void> {
@Override
public void postExecute() throws Exception {
if (manifest == null || StringUtils.isBlank(manifest.getManifest().getFileApi())) return;
Path manifestFile = repository.getModpackConfiguration(version).toPath();
Path manifestFile = repository.getModpackConfiguration(version);
Files.createDirectories(manifestFile.getParent());
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");
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())) {
Path runDirectory = repository.getRunDirectory(versionId).toPath();
Path runDirectory = repository.getRunDirectory(versionId);
List<ModpackConfiguration.FileInformation> files = new ArrayList<>();
zip.putDirectory(runDirectory, "overrides", path -> {
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.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -50,10 +52,10 @@ public class ServerModpackLocalInstallTask extends Task<Void> {
this.manifest = manifest;
this.name = name;
this.repository = dependencyManager.getGameRepository();
File run = repository.getRunDirectory(name);
Path run = repository.getRunDirectory(name);
File json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists())
Path json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && Files.notExists(json))
throw new IllegalArgumentException("Version " + name + " already exists.");
GameBuilder builder = dependencyManager.gameBuilder().name(name);
@@ -69,16 +71,16 @@ public class ServerModpackLocalInstallTask extends Task<Void> {
ModpackConfiguration<ServerModpackManifest> config = null;
try {
if (json.exists()) {
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(ServerModpackManifest.class));
if (Files.exists(json)) {
config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(ServerModpackManifest.class));
if (!ServerModpackProvider.INSTANCE.getName().equals(config.getType()))
throw new IllegalArgumentException("Version " + name + " is not a Server modpack. Cannot update this version.");
}
} catch (JsonParseException | IOException ignore) {
}
dependents.add(new ModpackInstallTask<>(zipFile, run, 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 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).toFile()).withStage("hmcl.modpack"));
}
@Override

View File

@@ -25,8 +25,9 @@ import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -46,8 +47,8 @@ public class ServerModpackRemoteInstallTask extends Task<Void> {
this.repository = dependencyManager.getGameRepository();
this.manifest = manifest;
File json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists())
Path json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && Files.notExists(json))
throw new IllegalArgumentException("Version " + name + " already exists.");
GameBuilder builder = dependencyManager.gameBuilder().name(name);
@@ -63,8 +64,8 @@ public class ServerModpackRemoteInstallTask extends Task<Void> {
ModpackConfiguration<ServerModpackManifest> config;
try {
if (json.exists()) {
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(ServerModpackManifest.class));
if (Files.exists(json)) {
config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(ServerModpackManifest.class));
if (!MODPACK_TYPE.equals(config.getType()))
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() : "";
}
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
private static final Set<String> INVALID_WINDOWS_RESOURCE_BASE_NAMES = Set.of(
"aux", "con", "nul", "prn", "clock$",
@@ -187,6 +195,18 @@ public final class FileUtils {
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 {
byte[] bytes = Files.readAllBytes(file);