diff --git a/.gitignore b/.gitignore
index c6d98d1..ba7d9e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,98 +1,48 @@
-# ---> JetBrains
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
-# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-
-# User-specific stuff
-.idea/**/workspace.xml
-.idea/**/tasks.xml
-.idea/**/usage.statistics.xml
-.idea/**/dictionaries
-.idea/**/shelf
-
-# AWS User-specific
-.idea/**/aws.xml
-
-# Generated files
-.idea/**/contentModel.xml
-
-# Sensitive or high-churn files
-.idea/**/dataSources/
-.idea/**/dataSources.ids
-.idea/**/dataSources.local.xml
-.idea/**/sqlDataSources.xml
-.idea/**/dynamic.xml
-.idea/**/uiDesigner.xml
-.idea/**/dbnavigator.xml
-
-# Gradle
-.idea/**/gradle.xml
-.idea/**/libraries
-
-# Gradle and Maven with auto-import
-# When using Gradle or Maven with auto-import, you should exclude module files,
-# since they will be recreated, and may cause churn. Uncomment if using
-# auto-import.
-# .idea/artifacts
-# .idea/compiler.xml
-# .idea/jarRepositories.xml
-# .idea/modules.xml
-# .idea/*.iml
-# .idea/modules
-# *.iml
-# *.ipr
-
-# CMake
-cmake-build-*/
-
-# Mongo Explorer plugin
-.idea/**/mongoSettings.xml
-
-# File-based project format
-*.iws
-
-# IntelliJ
-out/
-
-# mpeltonen/sbt-idea plugin
-.idea_modules/
-
-# JIRA plugin
-atlassian-ide-plugin.xml
-
-# Cursive Clojure plugin
-.idea/replstate.xml
-
-# SonarLint plugin
-.idea/sonarlint/
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-com_crashlytics_export_strings.xml
-crashlytics.properties
-crashlytics-build.properties
-fabric.properties
-
-# Editor-based Rest Client
-.idea/httpRequests
-
-# Android studio 3.1+ serialized cache file
-.idea/caches/build_file_checksums.ser
-
-# ---> Maven
target/
-pom.xml.tag
-pom.xml.releaseBackup
-pom.xml.versionsBackup
-pom.xml.next
-release.properties
-dependency-reduced-pom.xml
-buildNumber.properties
-.mvn/timing.properties
-# https://github.com/takari/maven-wrapper#usage-without-binary-jar
-.mvn/wrapper/maven-wrapper.jar
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
-# Eclipse m2e generated files
-# Eclipse Core
-.project
-# JDT-specific (Eclipse Java Development Tools)
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
+
+/.minecraft
+/.physics_mod_cache
+/config
+/CustomSkinLoader
+/etc
+/run
+/logs
+/jre*
+/ForeverMC.yaml
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..5f337ac
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,4 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+/developer-tools.xml
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..106fade
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..6d42c56
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..bada8b5
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..91820a7
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,99 @@
+
+
+ 4.0.0
+
+ cn.forevermc.launcher
+ ForeverMC
+ 2.3.0
+
+
+ 21
+ 21
+ UTF-8
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.7.1
+
+
+
+ cn.forevermc.launcher.ForeverMC
+
+
+
+ jar-with-dependencies
+
+
+
+
+ package
+
+ single
+
+
+
+
+
+
+
+
+
+ jitpack.io
+ https://jitpack.io
+
+
+ maven2
+ https://repo.maven.apache.org/maven2/
+
+
+
+
+
+ com.imyeyu.fx.ui
+ timi-fx-ui
+ 0.0.1
+
+
+ com.imyeyu.inject
+ timi-inject
+ 0.0.1
+
+
+ org.openjfx
+ javafx-media
+ 21.0.2
+
+
+ com.imyeyu.compress
+ timi-compress
+ 0.0.1
+
+
+ com.imyeyu.network
+ timi-network
+ 0.0.1
+
+
+ com.google.code.gson
+ gson
+ 2.10.1
+
+
+ org.projectlombok
+ lombok
+ 1.18.34
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.10.2
+ test
+
+
+
\ No newline at end of file
diff --git a/src/main/java/cn/forevermc/launcher/ForeverMC.java b/src/main/java/cn/forevermc/launcher/ForeverMC.java
new file mode 100644
index 0000000..a103a39
--- /dev/null
+++ b/src/main/java/cn/forevermc/launcher/ForeverMC.java
@@ -0,0 +1,63 @@
+package cn.forevermc.launcher;
+
+import cn.forevermc.launcher.bean.Config;
+import cn.forevermc.launcher.ctrl.Main;
+import javafx.application.Application;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import com.imyeyu.config.ConfigLoader;
+import com.imyeyu.fx.config.BindingsConfig;
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.inject.InjectApp;
+import com.imyeyu.inject.annotation.TimiInjectApplication;
+import com.imyeyu.java.TimiJava;
+import com.imyeyu.lang.multi.ResourcesMultilingual;
+
+import java.util.UUID;
+
+/**
+ * Forever Minecraft 启动器
+ *
+ * @author 夜雨
+ * @since 2022-11-18 15:36
+ */
+@Slf4j
+@TimiInjectApplication
+public class ForeverMC {
+
+ public static final String VERSION = "2.2.3";
+
+ @Getter
+ private static Config config;
+
+ @Getter
+ private static ConfigLoader configLoader;
+
+ @Getter
+ private static InjectApp injectApp;
+
+ public static void main(String[] args) {
+ injectApp = new InjectApp(ForeverMC.class);
+ try {
+ {
+ configLoader = BindingsConfig.build("ForeverMC.yaml", Config.class);
+ config = configLoader.load();
+ if (TimiJava.isEmpty(config.getLauncher().getClientId().get())) {
+ config.getLauncher().getClientId().set(UUID.randomUUID().toString());
+ }
+
+ ResourcesMultilingual multilingual = TimiFXUI.MULTILINGUAL;
+ multilingual.addAll("lang/timi-fx-ui/%s.lang");
+ multilingual.addAll("lang/%s.lang");
+ multilingual.setActivated(config.getMain().getLanguage().get());
+
+ // 禁止系统 DPI 缩放
+ System.setProperty("prism.allowhidpi", "false");
+ System.setProperty("glass.win.minHiDPI", "1");
+ }
+ Application.launch(Main.class);
+ } catch (Exception e) {
+ log.error("fatal error", e);
+ }
+ }
+}
diff --git a/src/main/java/cn/forevermc/launcher/bean/APISetting.java b/src/main/java/cn/forevermc/launcher/bean/APISetting.java
new file mode 100644
index 0000000..55f6383
--- /dev/null
+++ b/src/main/java/cn/forevermc/launcher/bean/APISetting.java
@@ -0,0 +1,45 @@
+package cn.forevermc.launcher.bean;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author 夜雨
+ * @since 2024-04-29 20:05
+ */
+@Data
+public class APISetting {
+
+ public enum Key {
+
+ /** 闪烁标语 */
+ FMC_SPLASHES,
+
+ /** 启动器背景 */
+ FMC_BG,
+
+ FMC_BGM,
+ }
+
+ private DynamicList bg;
+
+ private DynamicList bgm;
+
+ private DynamicList splashes;
+
+ /**
+ *
+ * @author 夜雨
+ * @since 2022-12-01 15:59
+ */
+ @Data
+ public static class DynamicList {
+
+ /** 激活的标语 */
+ private String active;
+
+ /** 标语列表 */
+ private List list;
+ }
+}
diff --git a/src/main/java/cn/forevermc/launcher/bean/ComponentSize.java b/src/main/java/cn/forevermc/launcher/bean/ComponentSize.java
new file mode 100644
index 0000000..5bb0d82
--- /dev/null
+++ b/src/main/java/cn/forevermc/launcher/bean/ComponentSize.java
@@ -0,0 +1,22 @@
+package cn.forevermc.launcher.bean;
+
+/**
+ * 组件大小
+ *
+ * @author 夜雨
+ * @since 2023-06-09 16:43
+ */
+public class ComponentSize {
+
+ /** 小号 */
+ public static final double SMALL = 150;
+
+ /** 正常 */
+ public static final double NORMAL = 200;
+
+ /** 中等 */
+ public static final double MEDIUM = 300;
+
+ /** 大号 */
+ public static final double LARGE = 400;
+}
diff --git a/src/main/java/cn/forevermc/launcher/bean/Config.java b/src/main/java/cn/forevermc/launcher/bean/Config.java
new file mode 100644
index 0000000..01fb5b1
--- /dev/null
+++ b/src/main/java/cn/forevermc/launcher/bean/Config.java
@@ -0,0 +1,94 @@
+package cn.forevermc.launcher.bean;
+
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.StringProperty;
+import lombok.Data;
+import com.imyeyu.java.bean.Language;
+
+/**
+ * 配置
+ *
+ * @author 夜雨
+ * @since 2024-04-18 17:55
+ */
+@Data
+public class Config {
+
+ /** 主配置 */
+ private Main main;
+
+ /** 玩家配置 */
+ private Player player;
+
+ /** 启动配置 */
+ private Launcher launcher;
+
+ /**
+ * 主配置
+ *
+ * @author 夜雨
+ * @since 2024-04-18 18:00
+ */
+ @Data
+ public static class Main {
+
+ /** 启动器语言 */
+ private ObjectProperty language;
+
+ /** 背景音乐音量,[0, 100] */
+ private DoubleProperty bgmVolume;
+
+ /** 音效音量,[0, 100] */
+ private DoubleProperty soundVolume;
+
+ /** 游戏下载源 */
+ private ObjectProperty gameDownloadSource;
+
+ /** 运行时下载源 */
+ private ObjectProperty runtimeDownloadSource;
+ }
+
+ /**
+ * 玩家配置
+ *
+ * @author 夜雨
+ * @since 2024-04-18 18:00
+ */
+ @Data
+ public static class Player {
+
+ /** 登录名 */
+ private StringProperty name;
+
+ /** 密码 */
+ private StringProperty password;
+ }
+
+ /**
+ * 启动器配置
+ *
+ * @author 夜雨
+ * @since 2024-04-18 17:59
+ */
+ @Data
+ public static class Launcher {
+
+ /** 最大内存 */
+ private DoubleProperty memory;
+
+ /** 启动游戏 */
+ private StringProperty game;
+
+ /** 客户端 ID */
+ private StringProperty clientId;
+
+ /** true 为自动启动 */
+ private BooleanProperty autoStartup;
+
+ /** 多线程下载游戏 */
+ private IntegerProperty multiDownload;
+ }
+}
diff --git a/src/main/java/cn/forevermc/launcher/bean/FabricAPI.java b/src/main/java/cn/forevermc/launcher/bean/FabricAPI.java
new file mode 100644
index 0000000..d121ffa
--- /dev/null
+++ b/src/main/java/cn/forevermc/launcher/bean/FabricAPI.java
@@ -0,0 +1,21 @@
+package cn.forevermc.launcher.bean;
+
+import lombok.Data;
+
+/**
+ *
+ *
+ * @author 夜雨
+ * @since 2024-06-11 19:29
+ */
+@Data
+public class FabricAPI {
+
+ private String name;
+
+ private String fabricVer;
+
+ private String minecraftVer;
+
+ private String mongoId;
+}
diff --git a/src/main/java/cn/forevermc/launcher/bean/FileDownload.java b/src/main/java/cn/forevermc/launcher/bean/FileDownload.java
new file mode 100644
index 0000000..3c9b830
--- /dev/null
+++ b/src/main/java/cn/forevermc/launcher/bean/FileDownload.java
@@ -0,0 +1,80 @@
+package cn.forevermc.launcher.bean;
+
+import cn.forevermc.launcher.util.Path;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.ReadOnlyBooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleDoubleProperty;
+import lombok.Data;
+import com.imyeyu.io.IO;
+import com.imyeyu.utils.Digest;
+
+import java.io.File;
+
+/**
+ * 文件下载对象
+ *
+ * @author 夜雨
+ * @since 2022-11-21 11:22
+ */
+@Data
+public class FileDownload {
+
+ /** 本地文件对象 */
+ private File file;
+
+ /** 大小 */
+ private Long size;
+
+ /** 请求 URL */
+ private String url;
+
+ /** 哈希 */
+ private String hash;
+
+ /** 显示名称 */
+ private String displayName;
+
+ /** 进度 */
+ private DoubleProperty progress;
+
+ /** true 为下载错误 */
+ private BooleanProperty isError;
+
+ /** 重试次数 */
+ private int retry = 0;
+
+ public FileDownload(File file) {
+ this.file = file;
+ this.displayName = file.getAbsolutePath().substring(file.getAbsolutePath().indexOf(Path.P_ROOT));
+
+ progress = new SimpleDoubleProperty(0);
+ isError = new SimpleBooleanProperty(false);
+ }
+
+ /** @return true 为存在且 HASH 匹配 */
+ public boolean exist() throws Exception {
+ return file.exists() && hash.equals(Digest.sha1(IO.toBytes(file)));
+ }
+
+ /** @return 文件名 */
+ public String getFileName() {
+ return file.getName();
+ }
+
+ /** @return 进度监听 */
+ public DoubleProperty progressProperty() {
+ return progress;
+ }
+
+ /** @param isError true 为下载错误 */
+ public void setError(boolean isError) {
+ this.isError.set(isError);
+ }
+
+ /** @return 错误监听,只读 */
+ public ReadOnlyBooleanProperty errorProperty() {
+ return isError;
+ }
+}
diff --git a/src/main/java/cn/forevermc/launcher/bean/Game.java b/src/main/java/cn/forevermc/launcher/bean/Game.java
new file mode 100644
index 0000000..95c73d1
--- /dev/null
+++ b/src/main/java/cn/forevermc/launcher/bean/Game.java
@@ -0,0 +1,350 @@
+package cn.forevermc.launcher.bean;
+
+import com.google.gson.JsonObject;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.StringProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.Getter;
+import com.imyeyu.fx.ui.TimiFXUI;
+import com.imyeyu.java.TimiJava;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 游戏
+ *
+ * @author 夜雨
+ * @since 2021-11-26 11:13
+ */
+@Data
+public class Game {
+
+ /**
+ * 游戏版本类型
+ *
+ * @author 夜雨
+ * @since 2022-11-21 14:54
+ */
+ public enum Type {
+
+ /** 快照 */
+ SNAPSHOT,
+
+ /** 正式版 */
+ RELEASE,
+
+ /** 旧的测试版 */
+ OLD_BETA,
+
+ /** 旧的实验版 */
+ OLD_ALPHA
+ }
+
+ // ---------- 版本属性,这些属性在解析前就有效 ----------
+
+ /** 显示名称 */
+ private String name;
+
+ /** 版本路径(如:.minecraft/versions/1.20) */
+ private File path;
+
+ /** JAR 文件 */
+ private File jar;
+
+ /** JSON 文件 */
+ private File json;
+
+ /** yaml 文件(ForeverMC 针对此整合包的配置) */
+ private File yaml;
+
+ /** JSON 文件的根节点 */
+ private JsonObject jsonRoot;
+
+ /** 发布时间 */
+ private Long releaseAt;
+
+ /** true 为原版 */
+ private boolean isOriginal;
+
+ /** true 为 Fabric 版本 */
+ private boolean isFabric;
+
+ /** true 为上古版本 */
+ private boolean isLegacy;
+
+ /** 版本设置 */
+ private Option option;
+
+ // ---------- 版本 JSON 数据,这些属性在解析后才有效 ----------
+
+ /** ID(版本号,可能会被修改) */
+ private String id;
+
+ /** 游戏版本类型 */
+ private Type type;
+
+ /** 元数据 URL */
+ private String url;
+
+ /** 版本元数据 */
+ private MetaData metaData;
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+
+ Game game = (Game) object;
+ return Objects.equals(path, game.path);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name != null ? name.hashCode() : 0;
+ result = 31 * result + (path != null ? path.hashCode() : 0);
+ return result;
+ }
+
+ /**
+ * 版本元数据,通过 {@link cn.forevermc.launcher.service.GameService#readMetaData(Game)} 解析后有效
+ *
+ * @author 夜雨
+ * @since 2022-11-21 11:28
+ */
+ @Data
+ public static class MetaData {
+
+ /**
+ * 真实版本号,目前仅在正式版有效
+ *
+ * @author 夜雨
+ * @since 2023-06-17 16:11
+ */
+ @Getter
+ public static class RealVersion {
+
+ /** 版本号匹配正则 */
+ private static final Pattern PATTERN = Pattern.compile("(\\d)\\.(\\d+)\\.?(\\d+)?");
+
+ /** 版本号 */
+ private String value;
+
+ /** 根版本 */
+ private int root = 0;
+
+ /** 主版本 */
+ private int main = 0;
+
+ /** BUG 修正版本 */
+ private int bug = 0;
+
+ /**
+ * 设置版本号,通过此方法自动解析详细版本号
+ *
+ * @param value 版本号
+ */
+ public void setValue(String value) {
+ this.value = value;
+ Matcher matcher = PATTERN.matcher(value);
+ if (matcher.find()) {
+ root = Integer.parseInt(matcher.group(1));
+ main = Integer.parseInt(matcher.group(2));
+ if (TimiJava.isNotEmpty(matcher.group(3))) {
+ bug = Integer.parseInt(matcher.group(3));
+ }
+ }
+ }
+
+ /** @return true 为支持 Fabric */
+ public boolean isSupportFabric() {
+ return main < 14;
+ }
+
+ /** @return true 为更现代的样式 */
+ public boolean isModernStyle() {
+ return 19 < main;
+ }
+ }
+
+ /** 版本号(可能因模组加载器而改变) */
+ private String version;
+
+ /** 真实的 Minecraft 版本号 */
+ private RealVersion realVersion;
+
+ /** 标题 */
+ private String title;
+
+ /** 启动类 */
+ private String mainClass;
+
+ /** 需要 Java 版本 */
+ private String javaVersion;
+
+ /** 游戏参数模板 */
+ private String argsGame;
+
+ /** JVM 参数模板 */
+ private String argsJVM;
+
+ /** 启动核心地址 */
+ private String coreURL;
+
+ /** 启动核心 SHA1 */
+ private String coreSHA1;
+
+ /** 核心大小 */
+ private Long coreSize;
+
+ /** 资源版本 */
+ private String assetsVersion;
+
+ /** 资源列表 */
+ private List assetList;
+
+ /** 依赖列表 */
+ private List librarieList;
+
+ /** 需下载的文件列表(解析结果,包括资源和依赖文件) */
+ private List downloadList;
+
+ /**
+ * 资源
+ *
+ * @author 夜雨
+ * @since 2022-11-21 11:04
+ */
+ @Data
+ public static class Assets {
+
+ /** 哈希值 */
+ private String hash;
+
+ /** 下载 URL */
+ private String url;
+
+ /** 大小 */
+ private Long size;
+ }
+
+ /**
+ * 依赖
+ *
+ * @author 夜雨
+ * @since 2022-11-21 11:04
+ */
+ @Data
+ public static class Libraries {
+
+ /** 哈希值 */
+ private String sha1;
+
+ /** 相对路径 */
+ private String path;
+
+ /** 下载 URL */
+ private String url;
+
+ /** 文件大小 */
+ private Long size;
+
+ /** true 为 JNA 依赖 */
+ private boolean isNatives;
+
+ /** @return true 为非 JNA 依赖 */
+ public boolean isNotNatives() {
+ return !isNatives;
+ }
+ }
+ }
+
+ /**
+ * 版本设置
+ *
+ * @author 夜雨
+ * @since 2022-12-09 20:31
+ */
+ @Data
+ public static class Option implements Serializable {
+
+ /**
+ * 登录方式
+ *
+ * @author 夜雨
+ * @since 2022-12-05 23:05
+ */
+ @Getter
+ @AllArgsConstructor
+ public enum LoginType implements Serializable {
+
+ /** 离线 */
+ OFFLINE(TimiFXUI.MULTILINGUAL.text("offline"), TimiFXUI.MULTILINGUAL.textArgs("fmc.launcher.menu.account.type", TimiFXUI.MULTILINGUAL.text("offline"))),
+
+ /** ForeverMC */
+ FOREVER_MC("ForeverMC", TimiFXUI.MULTILINGUAL.textArgs("fmc.launcher.menu.account.type", TimiFXUI.MULTILINGUAL.text("fmc.launcher.menu.account.fmc"))),
+
+ /** 官方正版验证(未支持) */
+ MOJANG("Mojang", TimiFXUI.MULTILINGUAL.textArgs("fmc.launcher.menu.account.type", TimiFXUI.MULTILINGUAL.text("fmc.launcher.menu.account.mojang")));
+
+ /** 显示名称 */
+ final String name;
+
+ /** 提示 */
+ final String tips;
+ }
+
+ /**
+ * 可选规则
+ *
+ * @author 夜雨
+ * @since 2023-06-17 11:34
+ */
+ @Getter
+ @AllArgsConstructor
+ public enum Rules {
+
+ /** 体验模式 */
+ DEMO("is_demo_user"),
+
+ /** 自定义尺寸 */
+ CUSTOM_RESOLUTION("has_custom_resolution"),
+
+ /** 快速游戏日志 */
+ QUICK_PLAYS_SUPPORT("has_quick_plays_support"),
+
+ /** 快速单人游戏 */
+ QUICK_PLAY_SINGLEPLAYER("is_quick_play_singleplayer"),
+
+ /** 快速多人游戏 */
+ QUICK_PLAY_MULTIPLAYER("is_quick_play_multiplayer"),
+
+ /** 快速领域游戏 */
+ QUICK_PLAY_REALMS("is_quick_play_realms");
+
+ /** 名称 */
+ final String name;
+ }
+
+ /** 名称,从整合版版本包获取并储存 */
+ private StringProperty name;
+
+ /** 支持的登录方式 */
+ private ObjectProperty loginType;
+
+ /** 启动 Java */
+ private StringProperty java;
+
+ /** 服务器 */
+ private StringProperty server;
+
+ /** 是否自动连接 */
+ private BooleanProperty autoConnect;
+ }
+}
diff --git a/src/main/java/cn/forevermc/launcher/bean/GameDownloadSource.java b/src/main/java/cn/forevermc/launcher/bean/GameDownloadSource.java
new file mode 100644
index 0000000..2a65386
--- /dev/null
+++ b/src/main/java/cn/forevermc/launcher/bean/GameDownloadSource.java
@@ -0,0 +1,25 @@
+package cn.forevermc.launcher.bean;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author 夜雨
+ * @since 2023-06-20 15:21
+ */
+@Getter
+@AllArgsConstructor
+public enum GameDownloadSource {
+
+ /** 官方源 */
+ MOJANG("https://libraries.minecraft.net/", "https://resources.download.minecraft.net/"),
+
+ /** BMCLAPI */
+ BMCLAPI("https://bmclapi2.bangbang93.com/maven/", "https://bmclapi2.bangbang93.com/assets/");
+
+ /** 依赖 */
+ final String libraries;
+
+ /** 资源 */
+ final String resources;
+}
diff --git a/src/main/java/cn/forevermc/launcher/bean/LaunchGame.java b/src/main/java/cn/forevermc/launcher/bean/LaunchGame.java
new file mode 100644
index 0000000..4ed0172
--- /dev/null
+++ b/src/main/java/cn/forevermc/launcher/bean/LaunchGame.java
@@ -0,0 +1,167 @@
+package cn.forevermc.launcher.bean;
+
+import cn.forevermc.launcher.service.FMCLoginService;
+import cn.forevermc.launcher.service.GameService;
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.SimpleObjectProperty;
+import com.imyeyu.inject.annotation.Bean;
+import com.imyeyu.inject.annotation.Inject;
+import com.imyeyu.inject.annotation.InvokeForInjected;
+
+/**
+ * 启动游戏对象
+ *
+ * @author 夜雨
+ * @since 2024-07-20 19:24
+ */
+@Bean
+public class LaunchGame extends SimpleObjectProperty {
+
+ @Inject
+ private Config config;
+
+ @Inject
+ private GameService gameService;
+
+ @Inject
+ private FMCLoginService fmcLoginService;
+
+ /** true 为使用新标题 */
+ private final BooleanBinding newTitle;
+
+ /** true 为使用新 UI */
+ private final BooleanBinding newUI;
+
+ /** true 为远古版本 */
+ private final BooleanBinding legacy;
+
+ /** true 为官方原版 */
+ private final BooleanBinding original;
+
+ /** true 为使用 Fabric */
+ private final BooleanBinding fabric;
+
+ /** true 为支持 ForeverMC 登录 */
+ private final BooleanBinding supportFMCLogin;
+
+ public LaunchGame() {
+ newTitle = Bindings.createBooleanBinding(() -> {
+ if (get() == null) {
+ return false;
+ }
+ Game.MetaData.RealVersion realVersion = gameService.getRealVersion(get());
+ return 1 <= realVersion.getRoot() && 20 <= realVersion.getMain(); // 1.20 <= 使用新标题
+ }, this);
+ newUI = Bindings.createBooleanBinding(() -> {
+ if (get() == null) {
+ return false;
+ }
+ Game.MetaData.RealVersion realVersion = gameService.getRealVersion(get());
+ return 1 <= realVersion.getRoot() && 21 <= realVersion.getMain(); // 1.21 <= 使用新 UI
+ }, this);
+ legacy = Bindings.createBooleanBinding(() -> {
+ if (get() == null) {
+ return false;
+ }
+ return get().isLegacy();
+ }, this);
+ original = Bindings.createBooleanBinding(() -> {
+ if (get() == null) {
+ return false;
+ }
+ return get().isOriginal();
+ }, this);
+ fabric = Bindings.createBooleanBinding(() -> {
+ if (get() == null) {
+ return false;
+ }
+ return get().isFabric();
+ }, this);
+ supportFMCLogin = Bindings.createBooleanBinding(() -> {
+ if (fmcLoginService == null) {
+ return false;
+ }
+ return fmcLoginService.isSupportFMCLogin(get());
+ }, this, FMCLoginService.loginModMapProperty(), FMCLoginService.fabricAPIMapProperty());
+ }
+
+ @InvokeForInjected
+ private void injected() {
+ String defLaunch = config.getLauncher().getGame().get();
+ config.getLauncher().getGame().bind(Bindings.createStringBinding(() -> {
+ if (get() == null) {
+ return defLaunch;
+ }
+ return get().getName();
+ }, this));
+ }
+
+ public boolean isOffline() {
+ return get().getOption().getLoginType().get() == Game.Option.LoginType.OFFLINE;
+ }
+
+ public boolean isOnline() {
+ return !isOffline();
+ }
+
+ public boolean isFMCLogin() {
+ return get().getOption().getLoginType().get() == Game.Option.LoginType.FOREVER_MC;
+ }
+
+ public boolean isMojangLogin() {
+ return get().getOption().getLoginType().get() == Game.Option.LoginType.MOJANG;
+ }
+
+ public boolean isNewTitle() {
+ return newTitle.get();
+ }
+
+ public BooleanBinding newTitleProperty() {
+ return newTitle;
+ }
+
+ public boolean isNewUI() {
+ return newUI.get();
+ }
+
+ public BooleanBinding newUIProperty() {
+ return newUI;
+ }
+
+ public boolean isLegacy() {
+ return legacy.get();
+ }
+
+ public BooleanBinding legacyProperty() {
+ return legacy;
+ }
+
+ public boolean isOriginal() {
+ return original.get();
+ }
+
+ public BooleanBinding originalProperty() {
+ return original;
+ }
+
+ public boolean isFabric() {
+ return fabric.get();
+ }
+
+ public BooleanBinding fabricProperty() {
+ return fabric;
+ }
+
+ public boolean isSupportFMCLogin() {
+ return supportFMCLogin.get();
+ }
+
+ public boolean isNotSupportFMCLogin() {
+ return !supportFMCLogin.get();
+ }
+
+ public BooleanBinding supportFMCLoginProperty() {
+ return supportFMCLogin;
+ }
+}
diff --git a/src/main/java/cn/forevermc/launcher/bean/MCPopupTips.java b/src/main/java/cn/forevermc/launcher/bean/MCPopupTips.java
new file mode 100644
index 0000000..799bfd6
--- /dev/null
+++ b/src/main/java/cn/forevermc/launcher/bean/MCPopupTips.java
@@ -0,0 +1,32 @@
+package cn.forevermc.launcher.bean;
+
+import javafx.geometry.Insets;
+import javafx.scene.control.Label;
+import javafx.scene.layout.Background;
+import javafx.scene.layout.Border;
+import com.imyeyu.fx.ui.components.popup.tips.AbstractPopupTips;
+import com.imyeyu.fx.utils.BorderStroke;
+
+/**
+ * 弹出提示组件
+ *
+ * @author 夜雨
+ * @since 2023-06-08 17:12
+ */
+public class MCPopupTips extends AbstractPopupTips