使用工作路径下的 .hmcl 文件夹存放所有配置文件 (#3875)

This commit is contained in:
Glavo 2025-04-30 14:50:51 +08:00 committed by GitHub
parent dc5ebcf64d
commit 9a37d412a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 95 additions and 58 deletions

1
.gitignore vendored
View File

@ -52,3 +52,4 @@ minecraft-exported-crash-info*
# test
/hmcl.json
/.hmcl.json
/.hmcl/

View File

@ -100,7 +100,7 @@ public final class Launcher extends Application {
return;
}
if (Metadata.HMCL_DIRECTORY.toString().indexOf('=') >= 0) {
if (Metadata.HMCL_CURRENT_DIRECTORY.toString().indexOf('=') >= 0) {
Main.showWarningAndContinue(i18n("fatal.illegal_char"));
}
@ -186,8 +186,10 @@ public final class Launcher extends Application {
ArrayList<String> files = new ArrayList<>();
files.add(ConfigHolder.configLocation().toString());
if (Files.exists(Metadata.HMCL_DIRECTORY))
files.add(Metadata.HMCL_DIRECTORY.toString());
if (Files.exists(Metadata.HMCL_GLOBAL_DIRECTORY))
files.add(Metadata.HMCL_GLOBAL_DIRECTORY.toString());
if (Files.exists(Metadata.HMCL_CURRENT_DIRECTORY))
files.add(Metadata.HMCL_CURRENT_DIRECTORY.toString());
Path mcDir = Paths.get(".minecraft").toAbsolutePath().normalize();
if (Files.exists(mcDir))
@ -238,8 +240,9 @@ public final class Launcher extends Application {
LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
LOG.info("Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor"));
LOG.info("Java Home: " + System.getProperty("java.home"));
LOG.info("Current Directory: " + System.getProperty("user.dir"));
LOG.info("HMCL Directory: " + Metadata.HMCL_DIRECTORY);
LOG.info("Current Directory: " + Metadata.CURRENT_DIRECTORY);
LOG.info("HMCL Global Directory: " + Metadata.HMCL_GLOBAL_DIRECTORY);
LOG.info("HMCL Current Directory: " + Metadata.HMCL_CURRENT_DIRECTORY);
LOG.info("HMCL Jar Path: " + Lang.requireNonNullElse(JarUtils.thisJarPath(), "Not Found"));
LOG.info("HMCL Log File: " + Lang.requireNonNullElse(LOG.getLogFile(), "In Memory"));
LOG.info("Memory: " + Runtime.getRuntime().maxMemory() / 1024 / 1024 + "MB");

View File

@ -58,7 +58,8 @@ public final class Main {
System.getProperties().putIfAbsent("javafx.autoproxy.disable", "true");
System.getProperties().putIfAbsent("http.agent", "HMCL/" + Metadata.VERSION);
LOG.start(Metadata.HMCL_DIRECTORY.resolve("logs"));
createHMCLCurrentDirectory();
LOG.start(Metadata.HMCL_CURRENT_DIRECTORY.resolve("logs"));
checkDirectoryPath();
@ -82,6 +83,24 @@ public final class Main {
System.exit(exitCode);
}
private static void createHMCLCurrentDirectory() {
if (!Files.isDirectory(Metadata.HMCL_CURRENT_DIRECTORY)) {
try {
Files.createDirectories(Metadata.HMCL_CURRENT_DIRECTORY);
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
try {
Files.setAttribute(Metadata.HMCL_CURRENT_DIRECTORY, "dos:hidden", true);
} catch (IOException e) {
LOG.warning("Failed to set hidden attribute of " + Metadata.HMCL_CURRENT_DIRECTORY, e);
}
}
} catch (IOException e) {
e.printStackTrace(System.err);
showErrorAndExit(i18n("fatal.create_hmcl_current_directory_failure", Metadata.HMCL_CURRENT_DIRECTORY));
}
}
}
private static void initIcon() {
java.awt.Image image = java.awt.Toolkit.getDefaultToolkit().getImage(Main.class.getResource("/assets/img/icon-mac.png"));
AwtUtils.setAppleIcon(image);

View File

@ -28,7 +28,8 @@ import java.nio.file.Paths;
* Stores metadata about this application.
*/
public final class Metadata {
private Metadata() {}
private Metadata() {
}
public static final String NAME = "HMCL";
public static final String FULL_NAME = "Hello Minecraft! Launcher";
@ -51,8 +52,11 @@ public final class Metadata {
public static final String BUILD_CHANNEL = JarUtils.getManifestAttribute("Build-Channel", "nightly");
public static final String GITHUB_SHA = JarUtils.getManifestAttribute("GitHub-SHA", null);
public static final Path CURRENT_DIRECTORY = Paths.get(System.getProperty("user.dir")).toAbsolutePath().normalize();
public static final Path MINECRAFT_DIRECTORY = OperatingSystem.getWorkingDirectory("minecraft");
public static final Path HMCL_DIRECTORY;
public static final Path HMCL_GLOBAL_DIRECTORY;
public static final Path HMCL_CURRENT_DIRECTORY;
public static final Path DEPENDENCIES_DIRECTORY;
static {
String hmclHome = System.getProperty("hmcl.home");
@ -60,16 +64,22 @@ public final class Metadata {
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) {
String xdgData = System.getenv("XDG_DATA_HOME");
if (StringUtils.isNotBlank(xdgData)) {
HMCL_DIRECTORY = Paths.get(xdgData, "hmcl").toAbsolutePath();
HMCL_GLOBAL_DIRECTORY = Paths.get(xdgData, "hmcl").toAbsolutePath().normalize();
} else {
HMCL_DIRECTORY = Paths.get(System.getProperty("user.home", "."), ".local", "share", "hmcl").toAbsolutePath();
HMCL_GLOBAL_DIRECTORY = Paths.get(System.getProperty("user.home"), ".local", "share", "hmcl").toAbsolutePath().normalize();
}
} else {
HMCL_DIRECTORY = OperatingSystem.getWorkingDirectory("hmcl");
HMCL_GLOBAL_DIRECTORY = OperatingSystem.getWorkingDirectory("hmcl");
}
} else {
HMCL_DIRECTORY = Paths.get(hmclHome).toAbsolutePath().normalize();
HMCL_GLOBAL_DIRECTORY = Paths.get(hmclHome).toAbsolutePath().normalize();
}
String hmclCurrentDir = System.getProperty("hmcl.dir");
HMCL_CURRENT_DIRECTORY = hmclCurrentDir != null
? Paths.get(hmclCurrentDir).toAbsolutePath().normalize()
: CURRENT_DIRECTORY.resolve(".hmcl");
DEPENDENCIES_DIRECTORY = HMCL_CURRENT_DIRECTORY.resolve("dependencies");
}
public static boolean isStable() {

View File

@ -87,7 +87,7 @@ public final class TexturesLoader {
}
private static final ThreadPoolExecutor POOL = threadPool("TexturesDownload", true, 2, 10, TimeUnit.SECONDS);
private static final Path TEXTURES_DIR = Metadata.HMCL_DIRECTORY.resolve("skins");
private static final Path TEXTURES_DIR = Metadata.HMCL_GLOBAL_DIRECTORY.resolve("skins");
private static Path getTexturePath(Texture texture) {
String url = texture.getUrl();

View File

@ -58,7 +58,7 @@ public final class JavaManager {
private JavaManager() {
}
public static final HMCLJavaRepository REPOSITORY = new HMCLJavaRepository(Metadata.HMCL_DIRECTORY.resolve("java"));
public static final HMCLJavaRepository REPOSITORY = new HMCLJavaRepository(Metadata.HMCL_GLOBAL_DIRECTORY.resolve("java"));
public static String getMojangJavaPlatform(Platform platform) {
if (platform.getOperatingSystem() == OperatingSystem.WINDOWS) {

View File

@ -175,7 +175,7 @@ public final class Accounts {
}
private static void loadGlobalAccountStorages() {
Path globalAccountsFile = Metadata.HMCL_DIRECTORY.resolve("accounts.json");
Path globalAccountsFile = Metadata.HMCL_GLOBAL_DIRECTORY.resolve("accounts.json");
if (Files.exists(globalAccountsFile)) {
try (Reader reader = Files.newBufferedReader(globalAccountsFile)) {
globalAccountStorages.setAll(Config.CONFIG_GSON.fromJson(reader, listTypeOf(mapTypeOf(Object.class, Object.class))));
@ -388,7 +388,7 @@ public final class Accounts {
String authlibinjectorLocation = System.getProperty("hmcl.authlibinjector.location");
if (authlibinjectorLocation == null) {
return new AuthlibInjectorDownloader(
Metadata.HMCL_DIRECTORY.resolve("authlib-injector.jar"),
Metadata.DEPENDENCIES_DIRECTORY.resolve("authlib-injector.jar"),
DownloadProviders::getDownloadProvider) {
@Override
public Optional<AuthlibInjectorArtifactInfo> getArtifactInfoImmediately() {
@ -397,7 +397,7 @@ public final class Accounts {
return local;
}
// search authlib-injector.jar in current directory, it's used as a fallback
return parseArtifact(Paths.get("authlib-injector.jar"));
return parseArtifact(Metadata.CURRENT_DIRECTORY.resolve("authlib-injector.jar"));
}
};
} else {

View File

@ -39,7 +39,7 @@ public final class ConfigHolder {
public static final String CONFIG_FILENAME = "hmcl.json";
public static final String CONFIG_FILENAME_LINUX = ".hmcl.json";
public static final Path GLOBAL_CONFIG_PATH = Metadata.HMCL_DIRECTORY.resolve("config.json");
public static final Path GLOBAL_CONFIG_PATH = Metadata.HMCL_GLOBAL_DIRECTORY.resolve("config.json");
private static Path configLocation;
private static Config configInstance;
@ -93,19 +93,9 @@ public final class ConfigHolder {
LOG.setLogRetention(globalConfig().getLogRetention());
Settings.init();
if (newlyCreated) {
if (newlyCreated)
saveConfigSync();
// hide the config file on windows
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
try {
Files.setAttribute(configLocation, "dos:hidden", true);
} catch (IOException e) {
LOG.warning("Failed to set hidden attribute of " + configLocation, e);
}
}
}
if (!Files.isWritable(configLocation)) {
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
&& configLocation.getFileSystem() == FileSystems.getDefault()
@ -121,12 +111,14 @@ public final class ConfigHolder {
}
private static Path locateConfig() {
Path exePath = Paths.get("").toAbsolutePath();
Path defaultConfigFile = Metadata.HMCL_CURRENT_DIRECTORY.resolve(CONFIG_FILENAME);
if (Files.isRegularFile(defaultConfigFile))
return defaultConfigFile;
try {
Path jarPath = JarUtils.thisJarPath();
if (jarPath != null && Files.isRegularFile(jarPath) && Files.isWritable(jarPath)) {
jarPath = jarPath.getParent();
exePath = jarPath;
Path config = jarPath.resolve(CONFIG_FILENAME);
if (Files.isRegularFile(config))
@ -149,7 +141,7 @@ public final class ConfigHolder {
return dotConfig;
// create new
return exePath.resolve(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? CONFIG_FILENAME : CONFIG_FILENAME_LINUX);
return defaultConfigFile;
}
private static Config loadConfig() throws IOException {

View File

@ -37,7 +37,6 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.Locale;
import java.util.Objects;
@ -59,26 +58,11 @@ public class Theme {
Color.web("#B71C1C") // red
};
private static final Lazy<Font> FONT = new Lazy<>(() -> {
private static Font tryLoadDefaultFont(Path dir) {
String[] fileNames = {"font.ttf", "font.otf", "font.woff"};
for (String fileName : fileNames) {
Path path = Paths.get(fileName).toAbsolutePath();
if (Files.isRegularFile(path)) {
try {
Font font = Font.loadFont(path.toUri().toURL().toExternalForm(), 0);
if (font != null) {
return font;
}
} catch (MalformedURLException ignored) {
}
LOG.warning("Failed to load font " + path);
}
}
for (String fileName : fileNames) {
Path path = Metadata.HMCL_DIRECTORY.resolve(fileName);
Path path = dir.resolve(fileName);
if (Files.isRegularFile(path)) {
try {
Font font = Font.loadFont(path.toUri().toURL().toExternalForm(), 0);
@ -93,6 +77,18 @@ public class Theme {
}
return null;
}
private static final Lazy<Font> FONT = new Lazy<>(() -> {
Font font = tryLoadDefaultFont(Metadata.HMCL_CURRENT_DIRECTORY);
if (font != null)
return font;
font = tryLoadDefaultFont(Metadata.CURRENT_DIRECTORY);
if (font != null)
return font;
return tryLoadDefaultFont(Metadata.HMCL_GLOBAL_DIRECTORY);
});
public static Theme getTheme() {

View File

@ -40,6 +40,7 @@ import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
import org.jackhuang.hmcl.task.Schedulers;
@ -235,12 +236,22 @@ public class DecoratorController {
* Load background image from bg/, background.png, background.jpg, background.gif
*/
private Image loadDefaultBackgroundImage() {
Image image = randomImageIn(Paths.get("bg"));
Image image = randomImageIn(Metadata.HMCL_CURRENT_DIRECTORY.resolve("background"));
if (image != null)
return image;
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
image = tryLoadImage(Paths.get("background." + extension));
image = tryLoadImage(Metadata.HMCL_CURRENT_DIRECTORY.resolve("background." + extension));
if (image != null)
return image;
}
image = randomImageIn(Metadata.CURRENT_DIRECTORY.resolve("bg"));
if (image != null)
return image;
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
image = tryLoadImage(Metadata.CURRENT_DIRECTORY.resolve("background." + extension));
if (image != null)
return image;
}

View File

@ -30,7 +30,6 @@ import javafx.scene.layout.*;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.scene.text.TextFlow;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
@ -46,6 +45,7 @@ import java.util.Arrays;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public abstract class SettingsView extends StackPane {
protected final JFXComboBox<SupportedLocale> cboLanguage;
@ -195,6 +195,8 @@ public abstract class SettingsView extends StackPane {
JFXButton openLogFolderButton = new JFXButton(i18n("settings.launcher.launcher_log.reveal"));
openLogFolderButton.setOnAction(e -> openLogFolder());
openLogFolderButton.getStyleClass().add("jfx-button-border");
if (LOG.getLogFile() == null)
openLogFolderButton.setDisable(true);
JFXButton logButton = new JFXButton(i18n("settings.launcher.launcher_log.export"));
logButton.setOnAction(e -> onExportLogs());
@ -216,7 +218,7 @@ public abstract class SettingsView extends StackPane {
}
public void openLogFolder() {
FXUtils.openFolder(Metadata.HMCL_DIRECTORY.resolve("logs").toFile());
FXUtils.openFolder(LOG.getLogFile().getParent().toFile());
}
protected abstract void onUpdate();

View File

@ -252,7 +252,7 @@ public final class UpdateHandler {
private static boolean isFirstLaunchAfterUpgrade() {
Path currentPath = JarUtils.thisJarPath();
if (currentPath != null) {
Path updated = Metadata.HMCL_DIRECTORY.resolve("HMCL-" + Metadata.VERSION + ".jar");
Path updated = Metadata.HMCL_GLOBAL_DIRECTORY.resolve("HMCL-" + Metadata.VERSION + ".jar");
if (currentPath.equals(updated.toAbsolutePath())) {
return true;
}
@ -261,7 +261,7 @@ public final class UpdateHandler {
}
private static void breakForceUpdateFeature() {
Path hmclVersionJson = Metadata.HMCL_DIRECTORY.resolve("hmclver.json");
Path hmclVersionJson = Metadata.HMCL_GLOBAL_DIRECTORY.resolve("hmclver.json");
if (Files.isRegularFile(hmclVersionJson)) {
try {
Map<?, ?> content = new Gson().fromJson(FileUtils.readText(hmclVersionJson), Map.class);

View File

@ -42,6 +42,7 @@
package org.jackhuang.hmcl.util;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.ui.SwingUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
@ -66,7 +67,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toSet;
import static org.jackhuang.hmcl.Metadata.HMCL_DIRECTORY;
import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf;
import static org.jackhuang.hmcl.util.gson.JsonUtils.mapTypeOf;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
@ -102,7 +102,7 @@ public final class SelfDependencyPatcher {
private static final class DependencyDescriptor {
private static final String DEPENDENCIES_LIST_FILE = "/assets/openjfx-dependencies.json";
private static final Path DEPENDENCIES_DIR_PATH = HMCL_DIRECTORY.resolve("dependencies").resolve(Platform.getPlatform().toString()).resolve("openjfx");
private static final Path DEPENDENCIES_DIR_PATH = Metadata.DEPENDENCIES_DIRECTORY.resolve(Platform.getPlatform().toString()).resolve("openjfx");
static List<DependencyDescriptor> readDependencies() {
//noinspection ConstantConditions

View File

@ -365,6 +365,7 @@ extension.png=Image File
extension.ps1=Windows PowerShell Script
extension.sh=Shell Script
fatal.create_hmcl_current_directory_failure=Hello Minecraft! Launcher cannot create the HMCL directory (%s). Please move HMCL to another location and reopen it.
fatal.javafx.incompatible=Missing JavaFX environment.\n\
Hello Minecraft! Launcher cannot automatically install JavaFX on Java <11.\n\
Please update your Java to version 11 or later.

View File

@ -363,6 +363,7 @@ extension.png=圖片檔案
extension.ps1=PowerShell 指令碼
extension.sh=Bash 指令碼
fatal.create_hmcl_current_directory_failure=Hello Minecraft! Launcher 無法創建 HMCL 資料夾(%s請將 HMCL 移動至其他位置再啟動。
fatal.javafx.incompatible=缺少 JavaFX 執行環境。\nHMCL 無法在低於 Java 11 的 Java 環境上自行補全 JavaFX 執行環境,請更新到 Java 11 或更高版本。
fatal.javafx.incomplete=JavaFX 執行環境不完整,請嘗試更換你的 Java 或者重新安裝 OpenJFX。
fatal.javafx.missing=缺少 JavaFX 執行環境,請使用包含 OpenJFX 的 Java 執行環境開啟 Hello Minecraft! Launcher。

View File

@ -372,6 +372,7 @@ extension.png=图片文件
extension.ps1=PowerShell 脚本
extension.sh=Bash 脚本
fatal.create_hmcl_current_directory_failure=Hello Minecraft! Launcher 无法创建 HMCL 文件夹(%s请将 HMCL 移动至其他位置再启动。\n如遇到问题你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
fatal.javafx.incompatible=缺少 JavaFX 运行环境。\nHello Minecraft! Launcher 无法在低于 Java 11 的 Java 环境上自行补全 JavaFX 运行环境,请更新到 Java 11 或更高版本。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
fatal.javafx.incomplete=JavaFX 运行环境不完整,请尝试更换你的 Java 或者重新安装 OpenJFX。
fatal.javafx.missing=缺少 JavaFX 运行环境,请使用包含 OpenJFX 的 Java 运行环境启动 Hello Minecraft! Launcher。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。