Repository: HMCL-dev/HMCL (language: java)

Issue:
[Bug] 没有hmcl.json的HMCL会在不是正中央启动
### 平台 | Platform

Windows 8.1

### 问题描述 | Problem Description

没有hmcl.json的HMCL会在不是正中央启动
第二次启动后就是正中央，是正常的
如图：
![B{91%I38346D73%9L4J@XY9](https://github.com/HMCL-dev/HMCL/assets/63381531/191a6cba-d032-4627-b9cc-e63aaa397cee)
（上图为我直接启动后的位置，不是我拖动的）
顺带一提，虚拟机，分辨率小于1080p

### 启动器崩溃报告 / 启动器日志文件 | Launcher Crash Report / Launcher Log File

None


Discussion / hints:
附带视频：

https://github.com/HMCL-dev/HMCL/assets/63381531/13113aa8-feca-4f80-8518-43df972597e7


Fix patch:
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java
index 430d2f1ea1..9ff37425ef 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java
@@ -393,8 +393,8 @@ public DoubleProperty xProperty() {
         return x;
     }
 
-    public void setX(double height) {
-        this.x.set(height);
+    public void setX(double x) {
+        this.x.set(x);
     }
 
     public double getY() {
@@ -405,8 +405,8 @@ public DoubleProperty yProperty() {
         return y;
     }
 
-    public void setY(double height) {
-        this.y.set(height);
+    public void setY(double y) {
+        this.y.set(y);
     }
 
     public double getWidth() {
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigUpgrader.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigUpgrader.java
index be202785f5..1a034979c7 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigUpgrader.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigUpgrader.java
@@ -18,16 +18,11 @@
 package org.jackhuang.hmcl.setting;
 
 import com.google.gson.Gson;
-import org.jackhuang.hmcl.ui.Controllers;
-import org.jackhuang.hmcl.util.Lang;
-import org.jackhuang.hmcl.util.Pair;
 import org.jackhuang.hmcl.util.StringUtils;
 
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
-import java.util.function.BiConsumer;
 import java.util.logging.Level;
 
 import static org.jackhuang.hmcl.util.Lang.tryCast;
@@ -46,94 +41,66 @@ private ConfigUpgrader() {
      * @param rawContent   raw json content of the config settings without modification
      */
     static void upgradeConfig(Config deserialized, String rawContent) {
-        if (deserialized.getConfigVersion() == CURRENT_VERSION) {
+        int configVersion = deserialized.getConfigVersion();
+
+        if (configVersion == CURRENT_VERSION) {
             return;
         }
 
-        int configVersion = deserialized.getConfigVersion();
         if (configVersion > CURRENT_VERSION) {
             LOG.log(Level.WARNING, String.format("Current HMCL only support the configuration version up to %d. However, the version now is %d.", CURRENT_VERSION, configVersion));
+            deserialized.setConfigVersion(CURRENT_VERSION);
             return;
         }
 
         LOG.log(Level.INFO, String.format("Updating configuration from %d to %d.", configVersion, CURRENT_VERSION));
-        Map<?, ?> unmodifiableRawJson = Collections.unmodifiableMap(new Gson().<Map<?, ?>>fromJson(rawContent, Map.class));
-        for (Map.Entry<Integer, BiConsumer<Config, Map<?, ?>>> dfu : collectDFU()) {
-            if (configVersion < dfu.getKey()) {
-                dfu.getValue().accept(deserialized, unmodifiableRawJson);
-                configVersion = dfu.getKey();
-            }
-        }
-
-        deserialized.setConfigVersion(CURRENT_VERSION);
-    }
+        Map<?, ?> rawJson = Collections.unmodifiableMap(new Gson().<Map<?, ?>>fromJson(rawContent, Map.class));
 
-    /**
-     * <p>Initialize the dfu of HMCL. Feel free to use lambda as all the lambda here would not be initialized unless HMCL needs to update the configuration.
-     * For each item in this list, it should be a Map.Entry.</p>
-     *
-     * <p>The key should be a version number. All the configuration with a version number which is less than the specific one will be applied to this upgrader.</p>
-     * <p>The value should be the upgrader. The configuration which is waited to being processed, and the raw unmodifiable value of the json from the configuration file.</p>
-     * <p>The return value should a target version number of this item.</p>
-     *
-     * <p>The last item must return CURRENT_VERSION, as the config file should always being updated to the latest version.</p>
-     */
-    private static List<Map.Entry<Integer, BiConsumer<Config, Map<?, ?>>>> collectDFU() {
-        List<Map.Entry<Integer, BiConsumer<Config, Map<?, ?>>>> dfu = Lang.immutableListOf(
-                Pair.pair(1, (deserialized, rawJson) -> {
-                    // Upgrade configuration of HMCL 2.x: Convert OfflineAccounts whose stored uuid is important.
-                    tryCast(rawJson.get("auth"), Map.class).ifPresent(auth -> {
-                        tryCast(auth.get("offline"), Map.class).ifPresent(offline -> {
-                            String selected = rawJson.containsKey("selectedAccount") ? null
-                                    : tryCast(offline.get("IAuthenticator_UserName"), String.class).orElse(null);
+        if (configVersion < 1) {
+            // Upgrade configuration of HMCL 2.x: Convert OfflineAccounts whose stored uuid is important.
+            tryCast(rawJson.get("auth"), Map.class).ifPresent(auth -> {
+                tryCast(auth.get("offline"), Map.class).ifPresent(offline -> {
+                    String selected = rawJson.containsKey("selectedAccount") ? null
+                            : tryCast(offline.get("IAuthenticator_UserName"), String.class).orElse(null);
 
-                            tryCast(offline.get("uuidMap"), Map.class).ifPresent(uuidMap -> {
-                                ((Map<?, ?>) uuidMap).forEach((key, value) -> {
-                                    Map<Object, Object> storage = new HashMap<>();
-                                    storage.put("type", "offline");
-                                    storage.put("username", key);
-                                    storage.put("uuid", value);
-                                    if (key.equals(selected)) {
-                                        storage.put("selected", true);
-                                    }
-                                    deserialized.getAccountStorages().add(storage);
-                                });
-                            });
+                    tryCast(offline.get("uuidMap"), Map.class).ifPresent(uuidMap -> {
+                        ((Map<?, ?>) uuidMap).forEach((key, value) -> {
+                            Map<Object, Object> storage = new HashMap<>();
+                            storage.put("type", "offline");
+                            storage.put("username", key);
+                            storage.put("uuid", value);
+                            if (key.equals(selected)) {
+                                storage.put("selected", true);
+                            }
+                            deserialized.getAccountStorages().add(storage);
                         });
                     });
+                });
+            });
 
-                    // Upgrade configuration of HMCL earlier than 3.1.70
-                    if (!rawJson.containsKey("commonDirType"))
-                        deserialized.setCommonDirType(deserialized.getCommonDirectory().equals(Settings.getDefaultCommonDirectory()) ? EnumCommonDirectory.DEFAULT : EnumCommonDirectory.CUSTOM);
-                    if (!rawJson.containsKey("backgroundType"))
-                        deserialized.setBackgroundImageType(StringUtils.isNotBlank(deserialized.getBackgroundImage()) ? EnumBackgroundImage.CUSTOM : EnumBackgroundImage.DEFAULT);
-                    if (!rawJson.containsKey("hasProxy"))
-                        deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost()));
-                    if (!rawJson.containsKey("hasProxyAuth"))
-                        deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser()));
+            // Upgrade configuration of HMCL earlier than 3.1.70
+            if (!rawJson.containsKey("commonDirType"))
+                deserialized.setCommonDirType(deserialized.getCommonDirectory().equals(Settings.getDefaultCommonDirectory()) ? EnumCommonDirectory.DEFAULT : EnumCommonDirectory.CUSTOM);
+            if (!rawJson.containsKey("backgroundType"))
+                deserialized.setBackgroundImageType(StringUtils.isNotBlank(deserialized.getBackgroundImage()) ? EnumBackgroundImage.CUSTOM : EnumBackgroundImage.DEFAULT);
+            if (!rawJson.containsKey("hasProxy"))
+                deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost()));
+            if (!rawJson.containsKey("hasProxyAuth"))
+                deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser()));
 
-                    if (!rawJson.containsKey("downloadType")) {
-                        tryCast(rawJson.get("downloadtype"), Number.class)
-                                .map(Number::intValue)
-                                .ifPresent(id -> {
-                                    if (id == 0) {
-                                        deserialized.setDownloadType("mojang");
-                                    } else if (id == 1) {
-                                        deserialized.setDownloadType("bmclapi");
-                                    }
-                                });
-                    }
-                }),
-                Pair.pair(2, (deserialized, rawJson) -> {
-                    deserialized.setX(0.5D - deserialized.getWidth() / Controllers.SCREEN.getBounds().getWidth() / 2);
-                    deserialized.setY(0.5D - deserialized.getHeight() / Controllers.SCREEN.getBounds().getHeight() / 2);
-                })
-        );
-
-        if (dfu.get(dfu.size() - 1).getKey() != CURRENT_VERSION) {
-            throw new IllegalStateException("The last dfu must adapt all the config version below CURRENT_VERSION");
+            if (!rawJson.containsKey("downloadType")) {
+                tryCast(rawJson.get("downloadtype"), Number.class)
+                        .map(Number::intValue)
+                        .ifPresent(id -> {
+                            if (id == 0) {
+                                deserialized.setDownloadType("mojang");
+                            } else if (id == 1) {
+                                deserialized.setDownloadType("bmclapi");
+                            }
+                        });
+            }
         }
 
-        return dfu;
+        deserialized.setConfigVersion(CURRENT_VERSION);
     }
 }
diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java
index acedb629c8..5a4fe371df 100644
--- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java
+++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java
@@ -37,10 +37,7 @@
 import org.jackhuang.hmcl.Launcher;
 import org.jackhuang.hmcl.Metadata;
 import org.jackhuang.hmcl.game.ModpackHelper;
-import org.jackhuang.hmcl.setting.Accounts;
-import org.jackhuang.hmcl.setting.EnumCommonDirectory;
-import org.jackhuang.hmcl.setting.Profiles;
-import org.jackhuang.hmcl.setting.Theme;
+import org.jackhuang.hmcl.setting.*;
 import org.jackhuang.hmcl.task.Task;
 import org.jackhuang.hmcl.task.TaskExecutor;
 import org.jackhuang.hmcl.ui.account.AccountListPage;
@@ -68,6 +65,8 @@
 import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
 
 public final class Controllers {
+    public static final int MIN_WIDTH = 800 + 2 + 16; // bg width + border width*2 + shadow width*2
+    public static final int MIN_HEIGHT = 450 + 2 + 40 + 16; // bg height + border width*2 + toolbar height + shadow width*2
     public static final Screen SCREEN = Screen.getPrimary();
     private static InvalidationListener stageSizeChangeListener;
     private static DoubleProperty stageX = new SimpleDoubleProperty();
@@ -205,35 +204,37 @@ public static void initialize(Stage stage) {
 
         WeakInvalidationListener weakListener = new WeakInvalidationListener(stageSizeChangeListener);
 
-        double initHeight = config().getHeight();
-        double initWidth = config().getWidth();
-        double initX = config().getX() * SCREEN.getBounds().getWidth();
-        double initY = config().getY() * SCREEN.getBounds().getHeight();
+        double initWidth = Math.max(MIN_WIDTH, config().getWidth());
+        double initHeight = Math.max(MIN_HEIGHT, config().getHeight());
 
         {
+            double initX = config().getX() * SCREEN.getBounds().getWidth();
+            double initY = config().getY() * SCREEN.getBounds().getHeight();
+
             boolean invalid = true;
             double border = 20D;
             for (Screen screen : Screen.getScreens()) {
                 Rectangle2D bound = screen.getBounds();
 
-                if (bound.getMinX() + border <= initX + initWidth && initX <= bound.getMaxX() - border && bound.getMinY() + border <= initY + initHeight && initY <= bound.getMaxY() - border) {
+                if (bound.getMinX() + border <= initX + initWidth && initX <= bound.getMaxX() - border && bound.getMinY() + border <= initY && initY <= bound.getMaxY() - border) {
                     invalid = false;
                     break;
                 }
             }
 
             if (invalid) {
-                initX = (0.5D - initWidth / Controllers.SCREEN.getBounds().getWidth() / 2) * SCREEN.getBounds().getWidth();
-                initY = (0.5D - initHeight / Controllers.SCREEN.getBounds().getHeight() / 2) * SCREEN.getBounds().getHeight();
+                initX = (0.5D - initWidth / SCREEN.getBounds().getWidth() / 2) * SCREEN.getBounds().getWidth();
+                initY = (0.5D - initHeight / SCREEN.getBounds().getHeight() / 2) * SCREEN.getBounds().getHeight();
             }
+
+            stage.setX(initX);
+            stage.setY(initY);
+            stageX.set(initX);
+            stageY.set(initY);
         }
 
-        stage.setX(initX);
-        stage.setY(initY);
         stage.setHeight(initHeight);
         stage.setWidth(initWidth);
-        stageX.set(initX);
-        stageY.set(initY);
         stageHeight.set(initHeight);
         stageWidth.set(initWidth);
         stage.xProperty().addListener(weakListener);
@@ -255,8 +256,8 @@ public static void initialize(Stage stage) {
 
         scene = new Scene(decorator.getDecorator());
         scene.setFill(Color.TRANSPARENT);
-        stage.setMinHeight(450 + 2 + 40 + 16); // bg height + border width*2 + toolbar height + shadow width*2
-        stage.setMinWidth(800 + 2 + 16); // bg width + border width*2 + shadow width*2
+        stage.setMinWidth(MIN_WIDTH);
+        stage.setMinHeight(MIN_HEIGHT);
         decorator.getDecorator().prefWidthProperty().bind(scene.widthProperty());
         decorator.getDecorator().prefHeightProperty().bind(scene.heightProperty());
         scene.getStylesheets().setAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily()));


Test patch: