Compare commits

..
57 Commits
Author SHA1 Message Date
Burning_TNT 15c1042991 Fix #3086 修复模组下载界面版本推荐功能 (#3089)
* Fix #3086

* Update
2024-05-31 23:12:33 +08:00
Burning_TNT 1275e38995 Fix #3006: 全局游戏设置中版本隔离开启后的显示的路径的版本号为”null“ (#3028) 2024-05-31 23:10:02 +08:00
Glavo 312a2e0cde 更新 Linux ARM64 支持 (#3097) 2024-05-31 23:06:38 +08:00
Burning_TNT 35ce95857a Fix #3026: resources 目录应该跟随游戏目录 (#3027) 2024-05-31 22:48:19 +08:00
Burning_TNT 6b78f56298 修复自动安装在 1.20.4 的严重问题 (#3023)
* Fix: OptiFine and Forge has compatible problem on 1.20.4 . Fix: Forge changes it's main class on 1.20.4 .

* Fix: HMCL cannot display the analyed versions installed by external launchers.

* Refuse to configure installable widgets when it's from external process.

* Add javadoc about LibraryStatus

* Adapt to the new change of Forge: https://github.com/MinecraftForge/MinecraftForge/commit/b8270cd9ff71678bd71f93938b35e5904b5f507a

* Re-arrange entries.

* Fix.
2024-05-31 22:02:56 +08:00
Glavo b43c8ceae5 移除 KOOK 入口 (#3092)
* 移除 KOOK 入口

* update
2024-05-31 21:41:41 +08:00
Burning_TNT 704d4e9cc0 Fix #3079 修复 NeoForge 整合包版本识别不正确的问题 (#3082)
* Fix #3079

* Fix.

* Fix: checkstyle.
2024-05-28 08:28:35 +08:00
CalbootandGlavo 96471e3a92 Fix #3071: 在 HMCL 被终结时停止日志线程 (#3081)
* update

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-05-28 08:17:55 +08:00
Burning_TNT af3719403d Support #3000: Support neoforge.mods.toml (#3031)
* Support #3000

* Fix #2371

* Rename references.

* Code cleanup.
2024-05-15 22:24:48 +08:00
Liyan Zhao e0764d6f0c support java from homebrew (#3035)
* fix: support java from homebrew

* fix: support java from homebrew

* fix: add ~/Library/Java/JavaVirtualMachine
2024-05-14 00:15:07 +08:00
zkitefly 908f47f75e Update Copyright (#3010) 2024-05-13 23:45:46 +08:00
Glavo a3af719087 支持自动下载 Java 21 (#3045) 2024-05-13 23:41:23 +08:00
zkitefly 7f13d6646b Update ExitWaiter.java (#3042) 2024-05-12 20:36:21 +08:00
Kieran Moy e8b4c38451 Add cinnamon desktop support (#3017) 2024-05-07 23:40:18 +08:00
Glavo 38ba6802a0 Bump Hub Mirror Action to 1.4 (#3034) 2024-05-07 23:36:28 +08:00
Glavo 381eead7b6 将 1.20.5 和 1.20.6 添加至 versions.txt (#3008)
* 更新 versions.txt

* update
2024-05-07 23:27:22 +08:00
3gf8jv4dv df953e5ea7 Fix NeoForge 2x icon not working (#2994) 2024-04-17 22:00:28 +08:00
Glavo 2fd2954a06 更新指向 XZ 主页的链接 (#2990) 2024-04-15 05:37:04 +08:00
Glavo a8bb5c1570 修复导入部分整合包失败的问题 (#2989)
* 修复导入部分整合包失败的问题

* update

* update
2024-04-15 04:02:24 +08:00
zkitefly a33377d1f2 add macos_failed_to_find_service_port_for_display (#2797)
* add macos_failed_to_find_service_port_for_display

日志来自 HMCL KOOK

* updata i18n

* Update I18N_zh.properties
2024-04-15 02:38:59 +08:00
zkiteflyandGlavo e515542add 还原 MCBBS 的鸣谢 (#2958)
* Update AboutPage.java

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-04-15 02:35:20 +08:00
zkitefly 48b6bd4ea1 add NEOFORGE_FOREST_OPTIFINE (#2976)
* add NEOFORGE_FOREST_OPTIFINE

* fix
2024-04-15 02:10:02 +08:00
Liyan Zhao 9cf23f69a6 Fix Microsoft authentication (#2986)
* fix: microsoft login #2979

* fix: typo
2024-04-15 02:07:47 +08:00
Glavo 4104ca082d 修复日志文件夹不存在时无法将日志写入文件的问题 (#2984) 2024-04-11 19:43:18 +08:00
Glavo 32f8ffad46 修复整合包下载失败提示窗标题未正确格式化的问题 (#2980)
* 修复 i18n 问题

* update

* update
2024-04-08 21:49:54 +08:00
Glavo a5e04a7b81 将 24w14potato 添加至 versions.txt (#2962)
* 将 24w14potato 添加至 versions.txt

* Update test
2024-04-02 13:55:52 +08:00
Glavo 57018bef47 重新实现日志记录 (#2951)
* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update
2024-03-28 12:16:52 +08:00
Glavo 0b65431044 清理残余 MCBBS 相关内容 (#2953)
* 清理残余 MCBBS 相关内容

* update
2024-03-25 21:44:27 +08:00
Glavo 991729b684 支持从当前路径加载字体 (#2950) 2024-03-24 19:59:38 +08:00
MythandGlavo 1605eb1775 fix: add locale to avoid wrong yggdrasil server name (#2521)
* fix: add locale to avoid wrong yggdrasil server name

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-03-23 14:19:16 +08:00
Glavo 94ccee0b76 优化 I18n (#2948)
* update

* update

* update

* update

* update
2024-03-23 14:11:52 +08:00
Glavo 8b94fe5b0b 更新模组和整合包信息 (#2947)
* update

* update
2024-03-23 11:31:09 +08:00
Glavo abec1abcff 更新默认皮肤 (#2943)
* 更新默认皮肤

* update

* update

* update

* update

* update

* update
2024-03-22 16:09:25 +08:00
Glavo 127938fa38 支持更多格式的版本图标 (#2942) 2024-03-21 20:01:21 +08:00
Glavo 1187ae4f04 Fix #2938: 修复 DefaultLauncher 会多次添加包装命令的问题 (#2939) 2024-03-16 11:08:17 +08:00
Glavo 8efdc536ee Fix #2934: 修复无法读取远古版本版本号的问题 (#2936)
* update

* update
2024-03-15 20:57:45 +08:00
Glavo 78f080b6cc Fix #2784: 离线账户选择 Steve/Alex 皮肤时强制使用本地 Yggdrasil 服务器 (#2933) 2024-03-15 13:39:50 +08:00
Glavo cc8bcfd924 Fix i18n (#2931)
* Fix i18n

* update
2024-03-15 12:56:29 +08:00
Glavo 806ca3f64a 更新 Modrinth 翻译 (#2929)
* update

* update

* update

* update

* update

* update
2024-03-15 12:24:43 +08:00
lyuxc_ 4a16fa13ee Update ModpackInfoPage.java (#2928) 2024-03-14 21:23:06 +08:00
Glavo b88d7c4fd2 修复 saveETagIndex 可能写入不全的问题 (#2926) 2024-03-14 19:24:30 +08:00
Burning_TNTandGlavo c0838468b1 Close #2911: 下载失败时打印重定向链 (#2912)
* Support #2911.

* update

* update

* update

* update

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-03-14 13:50:25 +08:00
Glavo 90cfe3d0b7 加载网络背景时设置超时 (#2856) 2024-03-13 16:25:55 +08:00
Burning_TNT baf1d22bc4 Fix bugs. (#2920)
* Always sort the results while searching in CurseForge.

* Fix bugs.
2024-03-12 22:14:32 +08:00
Glavo 7ca5744559 允许使用系统属性禁用系统代理 (#2921) 2024-03-12 21:44:08 +08:00
Glavo 57e728991b Fix #1938: 正确处理 map_to_resources (#2919)
* Fix #1938: 正确处理 map_to_resources

* fix checkstyle
2024-03-12 20:01:30 +08:00
Glavo e08ba4d421 不再尝试下载 .pack.xz 文件 (#2917)
* 不再尝试下载 .pack.xz 文件

* update
2024-03-11 20:21:17 +08:00
Burning_TNT ba91c60e95 Always sort the results while searching in CurseForge. (#2916) 2024-03-10 22:45:34 +08:00
389b93178e fix #2796 (#2804)
* fix https://github.com/HMCL-dev/HMCL/issues/2796

* 调整 game.crash.feedback 显示位置

* fix i18n

* 添加导出崩溃信息失败的弹窗

* updata

* 在崩溃窗口的日志窗口添加更多信息

* fix i18n

* Update HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties

Co-authored-by: 何杰豪 <hejiehao12@126.com>

* Update HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties

Co-authored-by: 何杰豪 <hejiehao12@126.com>

* Revert "在崩溃窗口的日志窗口添加更多信息"

This reverts commit e8ed43291e.

* updata

* add log

* updat log

* Update GameCrashWindow.java

* 添加更多日志输出

格式:Processing log file: /home/zkitefly/.minecraft/hs_err_pid20074.log, processStartTime: 1709952457315, lastModifiedTime: 1709952504054

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

---------

Co-authored-by: 何杰豪 <hejiehao12@126.com>
Co-authored-by: Glavo <zjx001202@gmail.com>
2024-03-10 22:02:56 +08:00
Glavo 37aaf153f8 更新文本和链接 (#2915)
* update

* update

* update

* update

* update

* update
2024-03-10 20:29:44 +08:00
ShulkerSakuraandGlavo 2249f4ee0e 在未知桌面环境禁用 moveToTrash (#2885)
* Added Optifine Icon

* Updated Optifine Icons

* Updated Optifine Icons

* Added new HMCL logo

* update

* Add ChromeOS Detection

* ChromeOS Detection

* Removed useless import

* Style Checked

* Add private constructor

* Style Check

* Simplification

* update

* update

* update

* update

* update

* update

---------

Co-authored-by: Glavo <zjx001202@gmail.com>
2024-03-09 20:38:48 +08:00
Glavo eadfff349a Revert "auth: Always check token.exp when launch game (#2642)" (#2908)
This reverts commit 8678c0dcf2.
2024-03-09 20:25:59 +08:00
Glavo cb4cd696a0 修复未初始化 fileDownloadProvider 的问题 (#2906) 2024-03-08 16:26:37 +08:00
Glavo c85c074eee 更改语言应在重启后生效 (#2905) 2024-03-08 15:04:40 +08:00
Glavo b26c478d4a 更新 I18N (#2904)
* 更新 I18N

* 更新 I18N
2024-03-08 14:21:05 +08:00
Glavo 2d83de7c31 简化 InvocationDispatcher (#2674)
* 优化 InvocationDispatcher

* update

* Rename type parameter

* update
2024-03-08 10:06:08 +08:00
Glavo 20d93dd085 清理 GlobalConfig (#2903) 2024-03-08 10:01:01 +08:00
212 changed files with 16638 additions and 8409 deletions

No files matched your search

+4 -4
View File
@@ -8,16 +8,16 @@ body:
attributes:
value: |
提交前请确认:
* 该问题确实是 **HMCL 的错误**,而**不是 Minecraft 非正常退出**,如果你的 Minecraft 非正常退出,请前往 [QQ 群](https://docs.hmcl.net/groups.html)/[KOOK 频道](https://kook.top/Kx7n3t)/[Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。
* 该问题确实是 **HMCL 的错误**,而**不是 Minecraft 非正常退出**,如果你的 Minecraft 非正常退出,请前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。
* 你的启动器版本是**最新的快照版本**,可以点击 [此处](https://github.com/burningtnt/HMCL-Snapshot-Update/raw/master/datas/HMCL-dev.jar) 下载最新快照版本。
如果你的问题并不属于上述两类,你可以选取另一种 Issue 类型,或者直接前往 [QQ 群](https://docs.hmcl.net/groups.html)/[KOOK 频道](https://kook.top/Kx7n3t)/[Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。
如果你的问题并不属于上述两类,你可以选取另一种 Issue 类型,或者直接前往 [QQ 群](https://docs.hmcl.net/groups.html)/[Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。
Before submitting, please confirm:
* The issue is indeed **an error of HMCL**, not **Minecraft abnormal exit**. If your Minecraft exits abnormally, please go to the [QQ group](https://docs.hmcl.net/groups.html) or [KOOK channel](https://kook.top/Kx7n3t) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help.
* The issue is indeed **an error of HMCL**, not **Minecraft abnormal exit**. If your Minecraft exits abnormally, please go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help.
* Your launcher version is **the latest snapshot version**. You can click [here](https://github.com/burningtnt/HMCL-Snapshot-Update/raw/master/datas/HMCL-dev.jar) to download the latest snapshot version.
If your issue does not fall into the above two categories, you can choose another type of issue or directly go to the [QQ group](https://docs.hmcl.net/groups.html) or [KOOK channel](https://kook.top/Kx7n3t) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help.
If your issue does not fall into the above two categories, you can choose another type of issue or directly go to the [QQ group](https://docs.hmcl.net/groups.html) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help.
- type: input
id: platform
attributes:
+3 -6
View File
@@ -1,14 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: KOOK 频道 | KOOK Channel
url: https://kook.top/Kx7n3t
about: Hello Minecraft! Launcher 的官方 KOOK 频道。| The official KOOK channel of Hello Minecraft! Laucher.
- name: Discord 频道 | Discord Channel
url: https://discord.gg/jVvC7HfM6U
about: Hello Minecraft! Launcher 的官方 Discord 频道。| The official Discord channel of Hello Minecraft! Launcher.
- name: QQ 群 | QQ Groups
url: https://docs.hmcl.net/groups.html
about: Hello Minecraft! Launcher 的官方 QQ 交流群。| The official QQ groups of Hello Minecraft! Laucher.
- name: Discord 频道 | Discord Channel
url: https://discord.gg/jVvC7HfM6U
about: Hello Minecraft! Launcher 的官方 Discord 频道。| The official Discord channel of Hello Minecraft! Launcher.
- name: 其他反馈 | Others
url: https://github.com/HMCL-dev/HMCL/discussions/new/choose
about: 通过 Discussions 反馈其他问题。| Report other problems in Discussions.
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Mirror GitHub to Gitee
uses: Yikun/hub-mirror-action@v1.3
uses: Yikun/hub-mirror-action@v1.4
with:
src: github/HMCL-dev
dst: gitee/huanghongxun
+1 -1
View File
@@ -118,7 +118,7 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("sha
manifest {
attributes(
"Created-By" to "Copyright(c) 2013-2023 huangyuhui.",
"Created-By" to "Copyright(c) 2013-2024 huangyuhui.",
"Main-Class" to "org.jackhuang.hmcl.Main",
"Multi-Release" to "true",
"Implementation-Version" to project.version,
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

+3 -33
View File
@@ -27,7 +27,7 @@ S = ';'
MOD_SEPARATOR = ','
CURSEFORGE_PATTERN1 = re.compile(
r'^/minecraft/(mc-mods|modpacks|customization|mc-addons|customization/configuration)/+(?P<modid>[\w-]+)(/(.*?))?$')
r'^/(minecraft|Minecraft|minecraft-bedrock)/(mc-mods|modpacks|customization|mc-addons|texture-packs|customization/configuration|addons)/+(?P<modid>[\w-]+)(/(.*?))?$')
CURSEFORGE_PATTERN2 = re.compile(
r'^/projects/(?P<modid>[\w-]+)(/(.*?))?$')
CURSEFORGE_PATTERN3 = re.compile(
@@ -58,30 +58,6 @@ def parseMcmod(url):
return match.group('modid')
return ''
MCBBS_HTML_PATTERN = re.compile(r'/+thread-(?P<modid>\d+)-(\d+)-(\d+).html')
MCBBS_PHP_PATTERN = re.compile(r'')
def parseMcbbs(url):
res = urlparse(url)
if res.scheme not in ['http', 'https']:
return ''
if res.netloc != 'www.mcbbs.net':
return ''
match = MCBBS_HTML_PATTERN.match(res.path)
if match:
return match.group('modid')
query = parse_qs(res.query)
if res.path == '/forum.php':
if 'mod' in query and 'tid' in query and query['mod'] == ['viewthread']:
return query['tid']
return ''
skip = [
'Minecraft',
'The Building Game'
@@ -111,12 +87,11 @@ if __name__ == '__main__':
exit(1)
black_lists = [
'Master Chef'
'Wood Converter'
]
curseforge_id = ''
mcmod_id = ''
mcbbs_id = ''
links = mod['links']['list']
if 'curseforge' in links and links['curseforge'] and sub_name not in black_lists and chinese_name not in black_lists:
for link in links['curseforge']:
@@ -131,11 +106,6 @@ if __name__ == '__main__':
if mcmod_id == '':
print('Error mcmod ' + chinese_name)
exit(1)
if 'mcbbs' in links and links['mcbbs']:
mcbbs_id = parseMcbbs(links['mcbbs'][0]['url'])
if mcbbs_id == '':
print('Error mcbbs ' + chinese_name)
exit(1)
mod_id = []
if 'modid' in mod and 'list' in mod['modid']:
@@ -148,6 +118,6 @@ if __name__ == '__main__':
mod_ids = MOD_SEPARATOR.join([str(id) for id in mod_id])
outfile.write(
f'{curseforge_id}{S}{mcmod_id}{S}{mcbbs_id}{S}{mod_ids}{S}{chinese_name}{S}{sub_name}{S}{abbr}\n')
f'{curseforge_id}{S}{mcmod_id}{S}{mod_ids}{S}{chinese_name}{S}{sub_name}{S}{abbr}\n')
print('Success!')
@@ -12,14 +12,10 @@ import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jetbrains.annotations.Nullable;
public class SkinCanvas extends Group {
public static final Image ALEX = FXUtils.newBuiltinImage("/assets/img/skin/alex.png");
public static final Image STEVE = FXUtils.newBuiltinImage("/assets/img/skin/steve.png");
public static final SkinCube ALEX_LARM = new SkinCube(3, 12, 4, 14F / 64F, 16F / 64F, 32F / 64F, 48F / 64F, 0F, true);
public static final SkinCube ALEX_RARM = new SkinCube(3, 12, 4, 14F / 64F, 16F / 64F, 40F / 64F, 16F / 64F, 0F, true);
@@ -152,10 +148,6 @@ public class SkinCanvas extends Group {
capeGroup.setVisible(hasCape);
}
public SkinCanvas(double preW, double preH) {
this(STEVE, preW, preH, true);
}
public SkinCanvas(Image skin, double preW, double preH, boolean msaa) {
this.skin = skin;
this.preW = preW;
@@ -25,9 +25,6 @@ import javafx.scene.control.ButtonType;
import javafx.scene.input.Clipboard;
import javafx.scene.input.DataFormat;
import javafx.stage.Stage;
import org.jackhuang.hmcl.auth.offline.Skin;
import org.jackhuang.hmcl.mod.RemoteMod;
import org.jackhuang.hmcl.mod.RemoteModRepository;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.setting.SambaException;
import org.jackhuang.hmcl.task.AsyncTaskExecutor;
@@ -53,13 +50,10 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.stream.Stream;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class Launcher extends Application {
@@ -71,14 +65,12 @@ public final class Launcher extends Application {
CookieHandler.setDefault(COOKIE_MANAGER);
register();
LOG.info("JavaFX Version: " + System.getProperty("javafx.runtime.version"));
try {
Object pipeline = Class.forName("com.sun.prism.GraphicsPipeline").getMethod("getPipeline").invoke(null);
LOG.info("Prism pipeline: " + (pipeline == null ? "null" : pipeline.getClass().getName()));
} catch (Throwable e) {
LOG.log(Level.WARNING, "Failed to get prism pipeline", e);
LOG.warning("Failed to get prism pipeline", e);
}
try {
@@ -87,7 +79,7 @@ public final class Launcher extends Application {
} catch (SambaException ignored) {
Main.showWarningAndContinue(i18n("fatal.samba"));
} catch (IOException e) {
LOG.log(Level.SEVERE, "Failed to load config", e);
LOG.error("Failed to load config", e);
checkConfigInTempDir();
checkConfigOwner();
Main.showErrorAndExit(i18n("fatal.config_loading_failure", ConfigHolder.configLocation().getParent()));
@@ -128,45 +120,6 @@ public final class Launcher extends Application {
}
}
private static void register() {
Skin.registerDefaultSkinLoader((type) -> {
switch (type) {
case ALEX:
return Skin.class.getResourceAsStream("/assets/img/skin/alex.png");
case ARI:
return Skin.class.getResourceAsStream("/assets/img/skin/ari.png");
case EFE:
return Skin.class.getResourceAsStream("/assets/img/skin/efe.png");
case KAI:
return Skin.class.getResourceAsStream("/assets/img/skin/kai.png");
case MAKENA:
return Skin.class.getResourceAsStream("/assets/img/skin/makena.png");
case NOOR:
return Skin.class.getResourceAsStream("/assets/img/skin/noor.png");
case STEVE:
return Skin.class.getResourceAsStream("/assets/img/skin/steve.png");
case SUNNY:
return Skin.class.getResourceAsStream("/assets/img/skin/sunny.png");
case ZURI:
return Skin.class.getResourceAsStream("/assets/img/skin/zuri.png");
default:
return null;
}
});
RemoteMod.registerEmptyRemoteMod(new RemoteMod("", "", i18n("mods.broken_dependency.title"), i18n("mods.broken_dependency.desc"), new ArrayList<>(), "", "/assets/img/icon@8x.png", new RemoteMod.IMod() {
@Override
public List<RemoteMod> loadDependencies(RemoteModRepository modRepository) throws IOException {
throw new IOException();
}
@Override
public Stream<RemoteMod.Version> loadVersions(RemoteModRepository modRepository) throws IOException {
throw new IOException();
}
}));
}
private static ButtonType showAlert(AlertType alertType, String contentText, ButtonType... buttons) {
return new Alert(alertType, contentText, buttons).showAndWait().orElse(null);
}
@@ -211,7 +164,7 @@ public final class Launcher extends Application {
private static void checkConfigInTempDir() {
if (ConfigHolder.isNewlyCreated() && isConfigInTempDir()
&& showAlert(AlertType.WARNING, i18n("fatal.config_in_temp_dir"), ButtonType.YES, ButtonType.NO) == ButtonType.NO) {
System.exit(0);
Main.exit(0);
}
}
@@ -224,7 +177,7 @@ public final class Launcher extends Application {
try {
owner = Files.getOwner(ConfigHolder.configLocation()).getName();
} catch (IOException ioe) {
LOG.log(Level.WARNING, "Failed to get file owner", ioe);
LOG.warning("Failed to get file owner", ioe);
return;
}
@@ -249,17 +202,18 @@ public final class Launcher extends Application {
Clipboard.getSystemClipboard()
.setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT, command));
}
System.exit(1);
Main.exit(1);
}
@Override
public void stop() throws Exception {
super.stop();
Controllers.onApplicationStop();
LOG.shutdown();
}
public static void main(String[] args) {
if (UpdateHandler.processArguments(args)) {
LOG.shutdown();
return;
}
@@ -277,6 +231,7 @@ public final class Launcher extends Application {
LOG.info("Current Directory: " + System.getProperty("user.dir"));
LOG.info("HMCL Directory: " + Metadata.HMCL_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");
LOG.info("Physical memory: " + OperatingSystem.TOTAL_MEMORY + " MB");
LOG.info("Metaspace: " + ManagementFactory.getMemoryPoolMXBeans().stream()
@@ -284,8 +239,10 @@ public final class Launcher extends Application {
.findAny()
.map(bean -> bean.getUsage().getUsed() / 1024 / 1024 + "MB")
.orElse("Unknown"));
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD())
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) {
LOG.info("XDG Session Type: " + System.getenv("XDG_SESSION_TYPE"));
LOG.info("XDG Current Desktop: " + System.getenv("XDG_CURRENT_DESKTOP"));
}
launch(Launcher.class, args);
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
+18 -15
View File
@@ -21,7 +21,6 @@ import javafx.application.Platform;
import javafx.scene.control.Alert;
import org.jackhuang.hmcl.ui.AwtUtils;
import org.jackhuang.hmcl.util.FractureiserDetector;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.SelfDependencyPatcher;
import org.jackhuang.hmcl.ui.SwingUtils;
import org.jackhuang.hmcl.util.platform.JavaVersion;
@@ -43,10 +42,9 @@ import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Collections;
import java.util.concurrent.CancellationException;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class Main {
@@ -55,10 +53,12 @@ public final class Main {
}
public static void main(String[] args) {
System.setProperty("java.net.useSystemProxies", "true");
System.setProperty("javafx.autoproxy.disable", "true");
System.getProperties().putIfAbsent("java.net.useSystemProxies", "true");
System.getProperties().putIfAbsent("javafx.autoproxy.disable", "true");
System.getProperties().putIfAbsent("http.agent", "HMCL/" + Metadata.VERSION);
LOG.start(Metadata.HMCL_DIRECTORY.resolve("logs"));
checkDirectoryPath();
if (JavaVersion.CURRENT_JAVA.getParsedVersion() < 9)
@@ -68,8 +68,6 @@ public final class Main {
if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
initIcon();
Logging.start(Metadata.HMCL_DIRECTORY.resolve("logs"));
checkJavaFX();
verifyJavaFX();
detectFractureiser();
@@ -77,6 +75,11 @@ public final class Main {
Launcher.main(args);
}
public static void exit(int exitCode) {
LOG.shutdown();
System.exit(exitCode);
}
private static void initIcon() {
java.awt.Image image = java.awt.Toolkit.getDefaultToolkit().getImage(Main.class.getResource("/assets/img/icon@8x.png"));
AwtUtils.setAppleIcon(image);
@@ -93,7 +96,7 @@ public final class Main {
private static void detectFractureiser() {
if (FractureiserDetector.detect()) {
LOG.log(Level.SEVERE, "Detected that this computer is infected by fractureiser");
LOG.error("Detected that this computer is infected by fractureiser");
showErrorAndExit(i18n("fatal.fractureiser"));
}
}
@@ -102,14 +105,14 @@ public final class Main {
try {
SelfDependencyPatcher.patch();
} catch (SelfDependencyPatcher.PatchException e) {
LOG.log(Level.SEVERE, "unable to patch JVM", e);
LOG.error("unable to patch JVM", e);
showErrorAndExit(i18n("fatal.javafx.missing"));
} catch (SelfDependencyPatcher.IncompatibleVersionException e) {
LOG.log(Level.SEVERE, "unable to patch JVM", e);
LOG.error("unable to patch JVM", e);
showErrorAndExit(i18n("fatal.javafx.incompatible"));
} catch (CancellationException e) {
LOG.log(Level.SEVERE, "User cancels downloading JavaFX", e);
System.exit(0);
LOG.error("User cancels downloading JavaFX", e);
exit(0);
}
}
@@ -136,13 +139,13 @@ public final class Main {
try {
if (Platform.isFxApplicationThread()) {
new Alert(Alert.AlertType.ERROR, message).showAndWait();
System.exit(1);
exit(1);
}
} catch (Throwable ignored) {
}
SwingUtils.showErrorDialog(message);
System.exit(1);
exit(1);
}
/**
@@ -192,7 +195,7 @@ public final class Main {
LOG.info("Added Lets Encrypt root certificates as additional trust");
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException |
KeyManagementException e) {
LOG.log(Level.SEVERE, "Failed to load lets encrypt certificate. Expect problems", e);
LOG.error("Failed to load lets encrypt certificate. Expect problems", e);
}
}
}
@@ -1,7 +1,6 @@
package org.jackhuang.hmcl.countly;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.platform.Architecture;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
@@ -13,6 +12,7 @@ import java.util.Map;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public class CrashReport {
@@ -59,7 +59,7 @@ public class CrashReport {
pair("_ram_current", getMemoryAvailable()),
pair("_ram_total", Runtime.getRuntime().maxMemory() / BYTES_IN_MB),
pair("_error", stackTrace),
pair("_logs", Logging.getLogs()),
pair("_logs", LOG.getLogs()),
pair("_name", throwable.getLocalizedMessage()),
pair("_nonfatal", nonFatal)
);
@@ -21,7 +21,6 @@ import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.launch.DefaultLauncher;
import org.jackhuang.hmcl.launch.ProcessListener;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.platform.ManagedProcess;
@@ -29,9 +28,9 @@ import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.File;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/**
* @author huangyuhui
@@ -72,7 +71,7 @@ public final class HMCLGameLauncher extends DefaultLauncher {
}
}
if (I18n.getCurrentLocale().getLocale() != Locale.CHINA) {
if (!I18n.isUseChinese()) {
return;
}
@@ -97,7 +96,7 @@ public final class HMCLGameLauncher extends DefaultLauncher {
try {
FileUtils.writeText(optionsFile, String.format("lang:%s\n", lang));
} catch (IOException e) {
Logging.LOG.log(Level.WARNING, "Unable to generate options.txt", e);
LOG.warning("Unable to generate options.txt", e);
}
}
}
@@ -50,12 +50,11 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.Pair.pair;
public class HMCLGameRepository extends DefaultGameRepository {
@@ -124,7 +123,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
if (!file.exists() && !versions.isEmpty())
FileUtils.writeText(file, PROFILE);
} catch (IOException ex) {
LOG.log(Level.WARNING, "Unable to create launcher_profiles.json, Forge/LiteLoader installer will not work.", ex);
LOG.warning("Unable to create launcher_profiles.json, Forge/LiteLoader installer will not work.", ex);
}
// https://github.com/HMCL-dev/HMCL/issues/938
@@ -259,8 +258,50 @@ public class HMCLGameRepository extends DefaultGameRepository {
return vs;
}
public File getVersionIconFile(String id) {
return new File(getVersionRoot(id), "icon.png");
public Optional<File> getVersionIconFile(String id) {
File root = getVersionRoot(id);
File iconFile = new File(root, "icon.png");
if (iconFile.exists()) {
return Optional.of(iconFile);
}
iconFile = new File(root, "icon.jpg");
if (iconFile.exists()) {
return Optional.of(iconFile);
}
iconFile = new File(root, "icon.bmp");
if (iconFile.exists()) {
return Optional.of(iconFile);
}
iconFile = new File(root, "icon.gif");
if (iconFile.exists()) {
return Optional.of(iconFile);
}
return Optional.empty();
}
public void setVersionIconFile(String id, File iconFile) throws IOException {
String ext = FileUtils.getExtension(iconFile).toLowerCase(Locale.ROOT);
if (!ext.equals("png") && !ext.equals("jpg") && !ext.equals("bmp") && !ext.equals("gif")) {
throw new IllegalArgumentException("Unsupported icon file: " + ext);
}
deleteIconFile(id);
FileUtils.copyFile(iconFile, new File(getVersionRoot(id), "icon." + ext));
}
public void deleteIconFile(String id) {
File root = getVersionRoot(id);
new File(root, "icon.png").delete();
new File(root, "icon.jpg").delete();
new File(root, "icon.bmp").delete();
new File(root, "icon.gif").delete();
}
public Image getVersionIconImage(String id) {
@@ -272,12 +313,12 @@ public class HMCLGameRepository extends DefaultGameRepository {
if (iconType == VersionIconType.DEFAULT) {
Version version = getVersion(id).resolve(this);
File iconFile = getVersionIconFile(id);
if (iconFile.exists()) {
try (InputStream inputStream = new FileInputStream(iconFile)) {
Optional<File> iconFile = getVersionIconFile(id);
if (iconFile.isPresent()) {
try (InputStream inputStream = new FileInputStream(iconFile.get())) {
return new Image(inputStream);
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to load version icon of " + id, e);
LOG.warning("Failed to load version icon of " + id, e);
}
}
@@ -316,7 +357,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
FileUtils.writeText(file, GSON.toJson(localVersionSettings.get(id)));
return true;
} catch (IOException e) {
LOG.log(Level.SEVERE, "Unable to save version setting of " + id, e);
LOG.error("Unable to save version setting of " + id, e);
return false;
}
}
@@ -60,13 +60,12 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.util.Lang.resolveException;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -112,7 +111,7 @@ public final class LauncherHelper {
public void launch() {
FXUtils.checkFxUserThread();
Logging.LOG.info("Launching game version: " + selectedVersion);
LOG.info("Launching game version: " + selectedVersion);
Controllers.dialog(launchingStepsPane);
launch0();
@@ -333,7 +332,8 @@ public final class LauncherHelper {
}
private static Task<JavaVersion> checkGameState(Profile profile, VersionSetting setting, Version version) {
GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(profile.getRepository().getGameVersion(version));
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, profile.getRepository().getGameVersion(version).orElse(null));
GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(analyzer.getVersion(LibraryAnalyzer.LibraryType.MINECRAFT));
if (setting.isNotCheckJVM()) {
return Task.composeAsync(() -> setting.getJavaVersion(gameVersion, version))
@@ -354,7 +354,9 @@ public final class LauncherHelper {
if (org.jackhuang.hmcl.util.platform.Platform.isCompatibleWithX86Java()) {
JavaVersionConstraint.VersionRanges range = JavaVersionConstraint.findSuitableJavaVersionRange(gameVersion, version);
if (range.getMandatory().contains(VersionNumber.asVersion("17.0.1"))) {
if (range.getMandatory().contains(VersionNumber.asVersion("21.0.3"))) {
targetJavaVersion = GameJavaVersion.JAVA_21;
} else if (range.getMandatory().contains(VersionNumber.asVersion("17.0.1"))) {
targetJavaVersion = GameJavaVersion.JAVA_17;
} else if (range.getMandatory().contains(VersionNumber.asVersion("16.0.1"))) {
targetJavaVersion = GameJavaVersion.JAVA_16;
@@ -388,7 +390,7 @@ public final class LauncherHelper {
future.complete(downloadedJavaVersion);
})
.exceptionally(throwable -> {
LOG.log(Level.WARNING, "Failed to download java", throwable);
LOG.warning("Failed to download java", throwable);
Controllers.confirm(i18n("launch.failed.no_accepted_java"), i18n("message.warning"), MessageType.WARNING, continueAction, () -> {
future.completeExceptionally(new CancellationException("No accepted java"));
});
@@ -417,7 +419,6 @@ public final class LauncherHelper {
JavaVersionConstraint violatedMandatoryConstraint = null;
List<JavaVersionConstraint> violatedSuggestedConstraints = null;
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, gameVersion.toString());
for (JavaVersionConstraint constraint : JavaVersionConstraint.ALL) {
if (constraint.appliesToVersion(gameVersion, version, javaVersion, analyzer)) {
if (!constraint.checkJava(gameVersion, version, javaVersion)) {
@@ -452,7 +453,7 @@ public final class LauncherHelper {
future.complete(downloadedJavaVersion);
}, Schedulers.javafx())
.whenCompleteAsync((result, throwable) -> {
LOG.log(Level.WARNING, "Failed to download java", throwable);
LOG.warning("Failed to download java", throwable);
breakAction.run();
}, Schedulers.javafx());
return Task.fromCompletableFuture(future);
@@ -466,6 +467,11 @@ public final class LauncherHelper {
() -> FXUtils.openLink(OPENJDK_DOWNLOAD_LINK), null);
breakAction.run();
return Task.fromCompletableFuture(future);
case VANILLA_JAVA_21:
Controllers.confirm(i18n("launch.advice.require_newer_java_version", gameVersion.toString(), 21), i18n("message.warning"),
() -> FXUtils.openLink(OPENJDK_DOWNLOAD_LINK), null);
breakAction.run();
return Task.fromCompletableFuture(future);
case VANILLA_JAVA_8:
Controllers.dialog(i18n("launch.advice.java8_1_13"), i18n("message.error"), MessageType.ERROR, breakAction);
return Task.fromCompletableFuture(future);
@@ -605,7 +611,7 @@ public final class LauncherHelper {
.thenAcceptAsync(future::complete)
.exceptionally(throwable -> {
Throwable resolvedException = resolveException(throwable);
LOG.log(Level.WARNING, "Failed to download java", throwable);
LOG.warning("Failed to download java", throwable);
if (!(resolvedException instanceof CancellationException)) {
Controllers.dialog(DownloadProviders.localizeErrorMessage(resolvedException), i18n("install.failed"));
}
@@ -644,11 +650,11 @@ public final class LauncherHelper {
try {
return Task.completed(account.logIn());
} catch (CredentialExpiredException e) {
LOG.log(Level.INFO, "Credential has expired", e);
LOG.info("Credential has expired", e);
return Task.completed(DialogController.logIn(account));
} catch (AuthenticationException e) {
LOG.log(Level.WARNING, "Authentication failed, try skipping refresh", e);
LOG.warning("Authentication failed, try skipping refresh", e);
CompletableFuture<Task<AuthInfo>> future = new CompletableFuture<>();
runInFX(() -> {
@@ -835,7 +841,7 @@ public final class LauncherHelper {
@Override
public void onExit(int exitCode, ExitType exitType) {
if (showLogs) {
Platform.runLater(() -> logWindow.logLine(String.format("[HMCL ProcessListener] Minecraft exit with code %d(0x%x).", exitCode, exitCode), Log4jLevel.INFO));
Platform.runLater(() -> logWindow.logLine(String.format("[HMCL ProcessListener] Minecraft exit with code %d(0x%x), type is %s.", exitCode, exitCode, exitType), Log4jLevel.INFO));
}
launchingLatch.countDown();
@@ -27,10 +27,9 @@ import org.jackhuang.hmcl.util.StringUtils;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Stream;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public abstract class LocalizedRemoteModRepository implements RemoteModRepository {
private static final int CONTAIN_CHINESE_WEIGHT = 10;
@@ -69,7 +68,7 @@ public abstract class LocalizedRemoteModRepository implements RemoteModRepositor
SearchResult searchResult = getBackedRemoteModRepository().search(gameVersion, category, pageOffset, pageSize, String.join(" ", englishSearchFiltersSet), getBackedRemoteModRepositorySortOrder(), sortOrder);
for (Iterator<RemoteMod> iterator = searchResult.getUnsortedResults().iterator(); iterator.hasNext(); ) {
if (chineseIndex > englishIndex) {
LOG.log(Level.WARNING, "Too many search results! Are the backed remote mod repository broken? Or are the API broken?");
LOG.warning("Too many search results! Are the backed remote mod repository broken? Or are the API broken?");
continue;
}
@@ -17,7 +17,7 @@
*/
package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.logging.Logger;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.Zipper;
@@ -35,9 +35,8 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class LogExporter {
private LogExporter() {
@@ -70,9 +69,9 @@ public final class LogExporter {
processLogs(runDirectory, "*.log", "runDirectory", zipper);
processLogs(runDirectory.resolve("crash-reports"), "*.txt", "crash-reports", zipper);
zipper.putTextFile(Logging.getLogs(), "hmcl.log");
zipper.putTextFile(LOG.getLogs(), "hmcl.log");
zipper.putTextFile(logs, "minecraft.log");
zipper.putTextFile(Logging.filterForbiddenToken(launchScript), OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "launch.bat" : "launch.sh");
zipper.putTextFile(Logger.filterForbiddenToken(launchScript), OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "launch.bat" : "launch.sh");
for (String id : versions) {
Path versionJson = baseDirectory.resolve("versions").resolve(id).resolve(id + ".json");
@@ -94,13 +93,17 @@ public final class LogExporter {
if (Files.isRegularFile(file)) {
FileTime time = Files.readAttributes(file, BasicFileAttributes.class).lastModifiedTime();
if (time.toMillis() >= processStartTime) {
String crashLog = Logging.filterForbiddenToken(FileUtils.readText(file, OperatingSystem.NATIVE_CHARSET));
zipper.putTextFile(crashLog, file.getFileName().toString());
try {
String crashLog = Logger.filterForbiddenToken(FileUtils.readText(file, OperatingSystem.NATIVE_CHARSET));
zipper.putTextFile(crashLog, file.getFileName().toString());
} catch (IOException e) {
LOG.warning("Failed to read log file: " + file, e);
}
}
}
}
} catch (Throwable e) {
LOG.log(Level.WARNING, "Failed to find any log on " + logDirectory, e);
LOG.warning("Failed to find any log on " + logDirectory, e);
}
}
}
@@ -23,7 +23,6 @@ import org.jackhuang.hmcl.auth.OAuth;
import org.jackhuang.hmcl.event.Event;
import org.jackhuang.hmcl.event.EventManager;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.IOUtils;
import org.jackhuang.hmcl.util.io.JarUtils;
@@ -34,10 +33,10 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class OAuthServer extends NanoHTTPD implements OAuth.Session {
@@ -80,7 +79,7 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session {
try {
session.parseBody(files);
} catch (IOException e) {
Logging.LOG.log(Level.WARNING, "Failed to read post data", e);
LOG.warning("Failed to read post data", e);
return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_HTML, "");
} catch (ResponseException re) {
return newFixedLengthResponse(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
@@ -97,7 +96,7 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session {
idToken = query.get("id_token");
future.complete(query.get("code"));
} else {
Logging.LOG.warning("Error: " + parameters);
LOG.warning("Error: " + parameters);
future.completeExceptionally(new AuthenticationException("failed to authenticate"));
}
@@ -106,7 +105,7 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session {
html = IOUtils.readFullyAsString(OAuthServer.class.getResourceAsStream("/assets/microsoft_auth.html"))
.replace("%close-page%", i18n("account.methods.microsoft.close_page"));
} catch (IOException e) {
Logging.LOG.log(Level.SEVERE, "Failed to load html");
LOG.error("Failed to load html", e);
return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_HTML, "");
}
thread(() -> {
@@ -114,7 +113,7 @@ public final class OAuthServer extends NanoHTTPD implements OAuth.Session {
Thread.sleep(1000);
stop();
} catch (InterruptedException e) {
Logging.LOG.log(Level.SEVERE, "Failed to sleep for 1 second");
LOG.error("Failed to sleep for 1 second");
}
});
return newFixedLengthResponse(Response.Status.OK, "text/html; charset=UTF-8", html);
@@ -52,13 +52,12 @@ import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
import static java.util.Objects.requireNonNull;
import static org.jackhuang.hmcl.util.Lang.threadPool;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/**
* @author yushijinhun
@@ -116,7 +115,7 @@ public final class TexturesLoader {
} catch (Exception e) {
if (Files.isRegularFile(file)) {
// concurrency conflict?
LOG.log(Level.WARNING, "Failed to download texture " + texture.getUrl() + ", but the file is available", e);
LOG.warning("Failed to download texture " + texture.getUrl() + ", but the file is available", e);
} else {
throw new IOException("Failed to download texture " + texture.getUrl());
}
@@ -140,30 +139,42 @@ public final class TexturesLoader {
// ====
// ==== Skins ====
private final static Map<TextureModel, LoadedTexture> DEFAULT_SKINS = new EnumMap<>(TextureModel.class);
private static final String[] DEFAULT_SKINS = {"alex", "ari", "efe", "kai", "makena", "noor", "steve", "sunny", "zuri"};
static {
loadDefaultSkin("/assets/img/skin/steve.png", TextureModel.STEVE);
loadDefaultSkin("/assets/img/skin/alex.png", TextureModel.ALEX);
public static Image getDefaultSkinImage() {
return FXUtils.newBuiltinImage("/assets/img/skin/wide/steve.png");
}
private static void loadDefaultSkin(String path, TextureModel model) {
DEFAULT_SKINS.put(model, new LoadedTexture(FXUtils.newBuiltinImage(path), singletonMap("model", model.modelName)));
public static LoadedTexture getDefaultSkin(UUID uuid) {
int idx = Math.floorMod(uuid.hashCode(), DEFAULT_SKINS.length * 2);
TextureModel model;
Image skin;
if (idx < DEFAULT_SKINS.length) {
model = TextureModel.SLIM;
skin = FXUtils.newBuiltinImage("/assets/img/skin/slim/" + DEFAULT_SKINS[idx] + ".png");
} else {
model = TextureModel.WIDE;
skin = FXUtils.newBuiltinImage("/assets/img/skin/wide/" + DEFAULT_SKINS[idx - DEFAULT_SKINS.length] + ".png");
}
return new LoadedTexture(skin, singletonMap("model", model.modelName));
}
public static LoadedTexture getDefaultSkin(TextureModel model) {
return DEFAULT_SKINS.get(model);
public static TextureModel getDefaultModel(UUID uuid) {
return TextureModel.WIDE.modelName.equals(getDefaultSkin(uuid).getMetadata().get("model"))
? TextureModel.WIDE
: TextureModel.SLIM;
}
public static ObjectBinding<LoadedTexture> skinBinding(YggdrasilService service, UUID uuid) {
LoadedTexture uuidFallback = getDefaultSkin(TextureModel.detectUUID(uuid));
LoadedTexture uuidFallback = getDefaultSkin(uuid);
return BindingMapping.of(service.getProfileRepository().binding(uuid))
.map(profile -> profile
.flatMap(it -> {
try {
return YggdrasilService.getTextures(it);
} catch (ServerResponseMalformedException e) {
LOG.log(Level.WARNING, "Failed to parse texture payload", e);
LOG.warning("Failed to parse texture payload", e);
return Optional.empty();
}
})
@@ -176,7 +187,7 @@ public final class TexturesLoader {
try {
return loadTexture(texture);
} catch (Throwable e) {
LOG.log(Level.WARNING, "Failed to load texture " + texture.getUrl() + ", using fallback texture", e);
LOG.warning("Failed to load texture " + texture.getUrl() + ", using fallback texture", e);
return uuidFallback;
}
}, POOL);
@@ -187,7 +198,7 @@ public final class TexturesLoader {
}
public static ObservableValue<LoadedTexture> skinBinding(Account account) {
LoadedTexture uuidFallback = getDefaultSkin(TextureModel.detectUUID(account.getUUID()));
LoadedTexture uuidFallback = getDefaultSkin(account.getUUID());
if (account instanceof OfflineAccount) {
OfflineAccount offlineAccount = (OfflineAccount) account;
@@ -200,7 +211,7 @@ public final class TexturesLoader {
if (skin != null) {
skin.load(username).setExecutor(POOL).whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception != null) {
LOG.log(Level.WARNING, "Failed to load texture", exception);
LOG.warning("Failed to load texture", exception);
} else if (result != null && result.getSkin() != null && result.getSkin().getImage() != null) {
Map<String, String> metadata;
if (result.getModel() != null) {
@@ -231,7 +242,7 @@ public final class TexturesLoader {
try {
return loadTexture(texture);
} catch (Throwable e) {
LOG.log(Level.WARNING, "Failed to load texture " + texture.getUrl() + ", using fallback texture", e);
LOG.warning("Failed to load texture " + texture.getUrl() + ", using fallback texture", e);
return uuidFallback;
}
}, POOL);
@@ -348,7 +359,7 @@ public final class TexturesLoader {
fxAvatarBinding(canvas, skinBinding(account));
else {
unbindAvatar(canvas);
drawAvatar(canvas, getDefaultSkin(TextureModel.detectUUID(account.getUUID())).image);
drawAvatar(canvas, getDefaultSkin(account.getUUID()).image);
}
}
@@ -47,7 +47,6 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.logging.Level;
import static java.util.stream.Collectors.toList;
import static javafx.collections.FXCollections.observableArrayList;
@@ -55,7 +54,7 @@ import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
import static org.jackhuang.hmcl.util.Lang.immutableListOf;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -72,7 +71,7 @@ public final class Accounts {
try {
((AuthlibInjectorDownloader) AUTHLIB_INJECTOR_DOWNLOADER).checkUpdate();
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to check update for authlib-injector", e);
LOG.warning("Failed to check update for authlib-injector", e);
}
});
}
@@ -178,7 +177,7 @@ public final class Accounts {
Config.CONFIG_GSON.fromJson(reader, new TypeToken<List<Map<Object, Object>>>() {
}.getType()));
} catch (Throwable e) {
LOG.log(Level.WARNING, "Failed to load global accounts", e);
LOG.warning("Failed to load global accounts", e);
}
}
@@ -190,7 +189,7 @@ public final class Accounts {
FileUtils.saveSafely(globalAccountsFile, json);
}
} catch (IOException e) {
LOG.log(Level.SEVERE, "Failed to save global accounts", e);
LOG.error("Failed to save global accounts", e);
}
}
});
@@ -209,7 +208,7 @@ public final class Accounts {
try {
return factory.fromStorage(storage);
} catch (Exception e) {
LOG.log(Level.WARNING, "Failed to load account: " + storage, e);
LOG.warning("Failed to load account: " + storage, e);
return null;
}
}
@@ -321,7 +320,7 @@ public final class Accounts {
try {
finalSelected.logIn();
} catch (Throwable e) {
LOG.log(Level.WARNING, "Failed to log " + finalSelected + " in", e);
LOG.warning("Failed to log " + finalSelected + " in", e);
}
});
}
@@ -335,7 +334,7 @@ public final class Accounts {
try {
server.fetchMetadataResponse();
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to fetch authlib-injector server metdata: " + server, e);
LOG.warning("Failed to fetch authlib-injector server metdata: " + server, e);
}
});
}
@@ -34,10 +34,9 @@ import java.nio.file.Paths;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Level;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class AuthlibInjectorServers implements Validation {
@@ -77,7 +76,7 @@ public final class AuthlibInjectorServers implements Validation {
String content = FileUtils.readText(configLocation);
configInstance = JsonUtils.GSON.fromJson(content, AuthlibInjectorServers.class);
} catch (IOException | JsonParseException e) {
LOG.log(Level.WARNING, "Malformed authlib-injectors.json", e);
LOG.warning("Malformed authlib-injectors.json", e);
return;
}
@@ -21,15 +21,16 @@ import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.util.InvocationDispatcher;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.JarUtils;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import java.io.IOException;
import java.nio.file.*;
import java.util.logging.Level;
import java.util.Locale;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class ConfigHolder {
@@ -79,7 +80,7 @@ public final class ConfigHolder {
configLocation = locateConfig();
LOG.log(Level.INFO, "Config location: " + configLocation);
LOG.info("Config location: " + configLocation);
configInstance = loadConfig();
configInstance.addListener(source -> markConfigDirty());
@@ -87,6 +88,9 @@ public final class ConfigHolder {
globalConfigInstance = loadGlobalConfig();
globalConfigInstance.addListener(source -> markGlobalConfigDirty());
Locale.setDefault(config().getLocalization().getLocale());
I18n.setLocale(configInstance.getLocalization());
LOG.setLogRetention(globalConfig().getLogRetention());
Settings.init();
if (newlyCreated) {
@@ -97,7 +101,7 @@ public final class ConfigHolder {
try {
Files.setAttribute(configLocation, "dos:hidden", true);
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to set hidden attribute of " + configLocation, e);
LOG.warning("Failed to set hidden attribute of " + configLocation, e);
}
}
}
@@ -157,7 +161,7 @@ public final class ConfigHolder {
ownerChanged = true;
}
} catch (IOException e1) {
LOG.log(Level.WARNING, "Failed to get owner");
LOG.warning("Failed to get owner");
}
try {
String content = FileUtils.readText(configLocation);
@@ -169,7 +173,7 @@ public final class ConfigHolder {
return deserialized;
}
} catch (JsonParseException e) {
LOG.log(Level.WARNING, "Malformed config.", e);
LOG.warning("Malformed config.", e);
}
}
@@ -182,7 +186,7 @@ public final class ConfigHolder {
try {
writeToConfig(content);
} catch (IOException e) {
LOG.log(Level.SEVERE, "Failed to save config", e);
LOG.error("Failed to save config", e);
}
});
@@ -214,7 +218,7 @@ public final class ConfigHolder {
return deserialized;
}
} catch (JsonParseException e) {
LOG.log(Level.WARNING, "Malformed config.", e);
LOG.warning("Malformed config.", e);
}
}
@@ -226,7 +230,7 @@ public final class ConfigHolder {
try {
writeToGlobalConfig(content);
} catch (IOException e) {
LOG.log(Level.SEVERE, "Failed to save config", e);
LOG.error("Failed to save config", e);
}
});
@@ -23,10 +23,9 @@ import org.jackhuang.hmcl.util.StringUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.Lang.tryCast;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
final class ConfigUpgrader {
private ConfigUpgrader() {
@@ -48,12 +47,12 @@ final class ConfigUpgrader {
}
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));
LOG.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));
LOG.info(String.format("Updating configuration from %d to %d.", configVersion, CURRENT_VERSION));
Map<?, ?> rawJson = Collections.unmodifiableMap(new Gson().<Map<?, ?>>fromJson(rawContent, Map.class));
if (configVersion < 1) {
@@ -55,7 +55,6 @@ public final class DownloadProviders {
private static final MojangDownloadProvider MOJANG;
private static final BMCLAPIDownloadProvider BMCLAPI;
// private static final BMCLAPIDownloadProvider MCBBS;
public static final String DEFAULT_PROVIDER_ID = "balanced";
public static final String DEFAULT_RAW_PROVIDER_ID = "bmclapi";
@@ -69,27 +68,19 @@ public final class DownloadProviders {
MOJANG = new MojangDownloadProvider();
BMCLAPI = new BMCLAPIDownloadProvider(bmclapiRoot);
// MCBBS = new BMCLAPIDownloadProvider("https://download.mcbbs.net");
rawProviders = mapOf(
pair("mojang", MOJANG),
pair("bmclapi", BMCLAPI)
// pair("mcbbs", MCBBS)
);
AdaptedDownloadProvider fileProvider = new AdaptedDownloadProvider();
fileProvider.setDownloadProviderCandidates(Arrays.asList(
// MCBBS,
BMCLAPI,
MOJANG));
BalancedDownloadProvider balanced = new BalancedDownloadProvider(
MOJANG,
// MCBBS,
BMCLAPI);
fileProvider.setDownloadProviderCandidates(Arrays.asList(BMCLAPI, MOJANG));
BalancedDownloadProvider balanced = new BalancedDownloadProvider(MOJANG, BMCLAPI);
providersById = mapOf(
pair("official", new AutoDownloadProvider(MOJANG, fileProvider)),
pair("balanced", new AutoDownloadProvider(balanced, fileProvider)),
pair("mirror", new AutoDownloadProvider(BMCLAPI /* MCBBS */, fileProvider)));
pair("mirror", new AutoDownloadProvider(BMCLAPI, fileProvider)));
observer = FXUtils.observeWeak(() -> {
FetchTask.setDownloadExecutorConcurrency(
@@ -108,12 +99,11 @@ public final class DownloadProviders {
.orElse(providersById.get(DEFAULT_PROVIDER_ID));
});
FXUtils.onChangeAndOperate(config().downloadTypeProperty(), downloadType -> {
if (!rawProviders.containsKey(downloadType)) {
config().setDownloadType(DEFAULT_RAW_PROVIDER_ID);
return;
}
if (!rawProviders.containsKey(config().getDownloadType())) {
config().setDownloadType(DEFAULT_RAW_PROVIDER_ID);
}
FXUtils.onChangeAndOperate(config().downloadTypeProperty(), downloadType -> {
DownloadProvider primary = Optional.ofNullable(rawProviders.get(downloadType))
.orElse(rawProviders.get(DEFAULT_RAW_PROVIDER_ID));
fileDownloadProvider.setDownloadProviderCandidates(
@@ -21,9 +21,7 @@ import com.google.gson.*;
import com.google.gson.annotations.JsonAdapter;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
@@ -33,7 +31,7 @@ import java.lang.reflect.Type;
import java.util.*;
@JsonAdapter(GlobalConfig.Serializer.class)
public class GlobalConfig implements Cloneable, Observable {
public final class GlobalConfig implements Cloneable, Observable {
@Nullable
public static GlobalConfig fromJson(String json) throws JsonParseException {
@@ -47,17 +45,15 @@ public class GlobalConfig implements Cloneable, Observable {
return instance;
}
private IntegerProperty agreementVersion = new SimpleIntegerProperty();
private final IntegerProperty agreementVersion = new SimpleIntegerProperty();
private IntegerProperty platformPromptVersion = new SimpleIntegerProperty();
private final IntegerProperty platformPromptVersion = new SimpleIntegerProperty();
private BooleanProperty multiplayerRelay = new SimpleBooleanProperty();
private IntegerProperty multiplayerAgreementVersion = new SimpleIntegerProperty(0);
private final IntegerProperty logRetention = new SimpleIntegerProperty();
private final Map<String, Object> unknownFields = new HashMap<>();
private transient ObservableHelper helper = new ObservableHelper(this);
private final transient ObservableHelper helper = new ObservableHelper(this);
public GlobalConfig() {
PropertyUtils.attachListener(this, helper);
@@ -106,37 +102,23 @@ public class GlobalConfig implements Cloneable, Observable {
this.platformPromptVersion.set(platformPromptVersion);
}
public boolean isMultiplayerRelay() {
return multiplayerRelay.get();
public int getLogRetention() {
return logRetention.get();
}
public BooleanProperty multiplayerRelayProperty() {
return multiplayerRelay;
public IntegerProperty logRetentionProperty() {
return logRetention;
}
public void setMultiplayerRelay(boolean multiplayerRelay) {
this.multiplayerRelay.set(multiplayerRelay);
public void setLogRetention(int logRetention) {
this.logRetention.set(logRetention);
}
public int getMultiplayerAgreementVersion() {
return multiplayerAgreementVersion.get();
}
public IntegerProperty multiplayerAgreementVersionProperty() {
return multiplayerAgreementVersion;
}
public void setMultiplayerAgreementVersion(int multiplayerAgreementVersion) {
this.multiplayerAgreementVersion.set(multiplayerAgreementVersion);
}
public static class Serializer implements JsonSerializer<GlobalConfig>, JsonDeserializer<GlobalConfig> {
public static final class Serializer implements JsonSerializer<GlobalConfig>, JsonDeserializer<GlobalConfig> {
private static final Set<String> knownFields = new HashSet<>(Arrays.asList(
"agreementVersion",
"platformPromptVersion",
"multiplayerToken",
"multiplayerRelay",
"multiplayerAgreementVersion"
"logRetention"
));
@Override
@@ -148,8 +130,7 @@ public class GlobalConfig implements Cloneable, Observable {
JsonObject jsonObject = new JsonObject();
jsonObject.add("agreementVersion", context.serialize(src.getAgreementVersion()));
jsonObject.add("platformPromptVersion", context.serialize(src.getPlatformPromptVersion()));
jsonObject.add("multiplayerRelay", context.serialize(src.isMultiplayerRelay()));
jsonObject.add("multiplayerAgreementVersion", context.serialize(src.getMultiplayerAgreementVersion()));
jsonObject.add("logRetention", context.serialize(src.getLogRetention()));
for (Map.Entry<String, Object> entry : src.unknownFields.entrySet()) {
jsonObject.add(entry.getKey(), context.serialize(entry.getValue()));
}
@@ -166,8 +147,7 @@ public class GlobalConfig implements Cloneable, Observable {
GlobalConfig config = new GlobalConfig();
config.setAgreementVersion(Optional.ofNullable(obj.get("agreementVersion")).map(JsonElement::getAsInt).orElse(0));
config.setPlatformPromptVersion(Optional.ofNullable(obj.get("platformPromptVersion")).map(JsonElement::getAsInt).orElse(0));
config.setMultiplayerRelay(Optional.ofNullable(obj.get("multiplayerRelay")).map(JsonElement::getAsBoolean).orElse(false));
config.setMultiplayerAgreementVersion(Optional.ofNullable(obj.get("multiplayerAgreementVersion")).map(JsonElement::getAsInt).orElse(0));
config.setLogRetention(Optional.ofNullable(obj.get("logRetention")).map(JsonElement::getAsInt).orElse(20));
for (Map.Entry<String, JsonElement> entry : obj.entrySet()) {
if (!knownFields.contains(entry.getKey())) {
@@ -1,164 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.setting;
import com.google.gson.annotations.SerializedName;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import org.jackhuang.hmcl.auth.OAuth;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
import org.jackhuang.hmcl.util.io.HttpRequest;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.util.UUID;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Pair.pair;
public final class HMCLAccounts {
private static final ObjectProperty<HMCLAccount> account = new SimpleObjectProperty<>();
private HMCLAccounts() {
}
public static HMCLAccount getAccount() {
return account.get();
}
public static ObjectProperty<HMCLAccount> accountProperty() {
return account;
}
public static void setAccount(HMCLAccount account) {
HMCLAccounts.account.set(account);
}
public static Task<Void> login() {
String nonce = UUIDTypeAdapter.fromUUID(UUID.randomUUID());
String scope = "openid offline_access";
return Task.supplyAsync(() -> {
OAuth.Session session = Accounts.OAUTH_CALLBACK.startServer();
Accounts.OAUTH_CALLBACK.openBrowser(NetworkUtils.withQuery(
"https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
mapOf(
pair("client_id", Accounts.OAUTH_CALLBACK.getClientId()),
pair("response_type", "id_token code"),
pair("response_mode", "form_post"),
pair("scope", scope),
pair("redirect_uri", session.getRedirectURI()),
pair("nonce", nonce)
)));
String code = session.waitFor();
// Authorization Code -> Token
String responseText = HttpRequest.POST("https://login.microsoftonline.com/common/oauth2/v2.0/token")
.form(mapOf(pair("client_id", Accounts.OAUTH_CALLBACK.getClientId()), pair("code", code),
pair("grant_type", "authorization_code"), pair("client_secret", Accounts.OAUTH_CALLBACK.getClientSecret()),
pair("redirect_uri", session.getRedirectURI()), pair("scope", scope)))
.getString();
OAuth.AuthorizationResponse response = JsonUtils.fromNonNullJson(responseText,
OAuth.AuthorizationResponse.class);
HMCLAccountProfile profile = HttpRequest.GET("https://hmcl.huangyuhui.net/api/user")
.header("Token-Type", response.tokenType)
.header("Access-Token", response.accessToken)
.header("Authorization-Provider", "microsoft")
.authorization("Bearer", session.getIdToken())
.getJson(HMCLAccountProfile.class);
return new HMCLAccount("microsoft", profile.nickname, profile.email, profile.role, session.getIdToken(), response.tokenType, response.accessToken, response.refreshToken);
}).thenAcceptAsync(Schedulers.javafx(), account -> {
setAccount(account);
});
}
public static class HMCLAccount implements HttpRequest.Authorization {
private final String provider;
private final String nickname;
private final String email;
private final String role;
private final String idToken;
private final String tokenType;
private final String accessToken;
private final String refreshToken;
public HMCLAccount(String provider, String nickname, String email, String role, String idToken, String tokenType, String accessToken, String refreshToken) {
this.provider = provider;
this.nickname = nickname;
this.email = email;
this.role = role;
this.idToken = idToken;
this.tokenType = tokenType;
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
public String getProvider() {
return provider;
}
public String getNickname() {
return nickname;
}
public String getEmail() {
return email;
}
public String getRole() {
return role;
}
public String getIdToken() {
return idToken;
}
@Override
public String getTokenType() {
return tokenType;
}
@Override
public String getAccessToken() {
return accessToken;
}
public String getRefreshToken() {
return refreshToken;
}
}
private static class HMCLAccountProfile {
@SerializedName("ID")
String id;
@SerializedName("Provider")
String provider;
@SerializedName("Email")
String email;
@SerializedName("NickName")
String nickname;
@SerializedName("Role")
String role;
}
}
@@ -29,7 +29,7 @@ import java.net.Proxy;
import java.net.Proxy.Type;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class ProxyManager {
private ProxyManager() {
@@ -24,8 +24,6 @@ import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.util.CacheRepository;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.util.Locale;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
public final class Settings {
@@ -47,9 +45,6 @@ public final class Settings {
}
private Settings() {
config().localizationProperty().addListener(unused -> updateSystemLocale());
updateSystemLocale();
DownloadProviders.init();
ProxyManager.init();
Accounts.init();
@@ -81,8 +76,4 @@ public final class Settings {
return null;
}
}
private static void updateSystemLocale() {
Locale.setDefault(config().getLocalization().getLocale());
}
}
@@ -23,23 +23,27 @@ import com.google.gson.stream.JsonWriter;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.IOUtils;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
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.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
@JsonAdapter(Theme.TypeAdapter.class)
public class Theme {
@@ -82,6 +86,30 @@ public class Theme {
return cssCharset;
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private static Optional<Font> font;
private static Optional<Font> tryLoadFont() {
//noinspection OptionalAssignedToNull
if (font != null) {
return font;
}
Path path = Paths.get("font.ttf");
if (!Files.isRegularFile(path)) {
path = Paths.get("font.otf");
}
if (Files.isRegularFile(path)) {
try {
return font = Optional.ofNullable(Font.loadFont(path.toAbsolutePath().toUri().toURL().toExternalForm(), 0));
} catch (MalformedURLException ignored) {
}
}
return font = Optional.empty();
}
public static Theme getTheme() {
Theme theme = config().getTheme();
return theme == null ? BLUE : theme;
@@ -121,9 +149,24 @@ public class Theme {
String css = "/assets/css/blue.css";
String fontFamily = System.getProperty("hmcl.font.override", overrideFontFamily);
String fontStyle = null;
if (fontFamily == null) {
Optional<Font> font = tryLoadFont();
if (font.isPresent()) {
fontFamily = font.get().getFamily();
fontStyle = font.get().getStyle();
}
}
if (fontFamily != null || !this.color.equalsIgnoreCase(BLUE.color)) {
Color textFill = getForegroundColor();
String fontCss = "";
if (fontFamily != null) {
fontCss = "-fx-font-family: \"" + fontFamily + "\";";
if (fontStyle != null && !fontStyle.isEmpty())
fontCss += " -fx-font-style: \"" + fontStyle + "\";";
}
try {
File temp = File.createTempFile("hmcl", ".css");
String themeText = IOUtils.readFullyAsString(Theme.class.getResourceAsStream("/assets/css/custom.css"))
@@ -134,12 +177,12 @@ public class Theme {
.replace("%base-rippler-color%", String.format("rgba(%d, %d, %d, 0.3)", (int) Math.ceil(paint.getRed() * 256), (int) Math.ceil(paint.getGreen() * 256), (int) Math.ceil(paint.getBlue() * 256)))
.replace("%disabled-font-color%", String.format("rgba(%d, %d, %d, 0.7)", (int) Math.ceil(textFill.getRed() * 256), (int) Math.ceil(textFill.getGreen() * 256), (int) Math.ceil(textFill.getBlue() * 256)))
.replace("%font-color%", getColorDisplayName(getForegroundColor()))
.replace("%font%", Optional.ofNullable(fontFamily).map(f -> "-fx-font-family: \"" + f + "\";").orElse(""));
.replace("%font%", fontCss);
FileUtils.writeText(temp, themeText, getCssCharset());
temp.deleteOnExit();
css = temp.toURI().toString();
} catch (IOException | NullPointerException e) {
Logging.LOG.log(Level.SEVERE, "Unable to create theme stylesheet. Fallback to blue theme.", e);
LOG.error("Unable to create theme stylesheet. Fallback to blue theme.", e);
}
}
@@ -61,6 +61,7 @@ import java.util.List;
import java.util.concurrent.CompletableFuture;
import static org.jackhuang.hmcl.setting.ConfigHolder.*;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class Controllers {
@@ -165,7 +166,7 @@ public final class Controllers {
}
public static void initialize(Stage stage) {
Logging.LOG.info("Start initializing application");
LOG.info("Start initializing application");
Controllers.stage = stage;
@@ -298,7 +299,7 @@ public final class Controllers {
});
JFXButton noButton = new JFXButton(i18n("launcher.agreement.decline"));
noButton.getStyleClass().add("dialog-cancel");
noButton.setOnAction(e -> System.exit(1));
noButton.setOnAction(e -> javafx.application.Platform.exit());
agreementPane.setActions(agreementLink, yesButton, noButton);
Controllers.dialog(agreementPane);
}
@@ -72,7 +72,7 @@ public class CrashWindow extends Stage {
FXUtils.setIcon(this);
setTitle(i18n("message.error"));
setOnCloseRequest(e -> System.exit(1));
setOnCloseRequest(e -> javafx.application.Platform.exit());
}
}
@@ -55,7 +55,6 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.animation.AnimationUtils;
import org.jackhuang.hmcl.ui.construct.JFXHyperlink;
import org.jackhuang.hmcl.util.Holder;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.ResourceNotFoundError;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.javafx.ExtendedProperties;
@@ -79,6 +78,7 @@ import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
@@ -87,12 +87,11 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.Lang.tryCast;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class FXUtils {
@@ -109,7 +108,7 @@ public final class FXUtils {
try {
Files.deleteIfExists(entry.getValue());
} catch (IOException e) {
LOG.log(Level.WARNING, String.format("Failed to delete cache file %s.", entry.getValue()), e);
LOG.warning(String.format("Failed to delete cache file %s.", entry.getValue()), e);
}
remoteImageCache.remove(entry.getKey());
}
@@ -336,7 +335,7 @@ public final class FXUtils {
Tooltip.class.getMethod("setHideDelay", Duration.class).invoke(tooltip, new Duration(closeDelay));
} catch (ReflectiveOperationException e2) {
e.addSuppressed(e2);
Logging.LOG.log(Level.SEVERE, "Cannot install tooltip", e);
LOG.error("Cannot install tooltip", e);
}
Tooltip.install(node, tooltip);
}
@@ -369,7 +368,7 @@ public final class FXUtils {
public static void openFolder(File file) {
if (!FileUtils.makeDirectory(file)) {
LOG.log(Level.SEVERE, "Unable to make directory " + file);
LOG.error("Unable to make directory " + file);
return;
}
@@ -396,7 +395,7 @@ public final class FXUtils {
else
LOG.warning("Open " + path + " failed with code " + exitCode);
} catch (Throwable e) {
LOG.log(Level.WARNING, "Unable to open " + path + " by executing " + openCommand, e);
LOG.warning("Unable to open " + path + " by executing " + openCommand, e);
}
}
@@ -404,7 +403,7 @@ public final class FXUtils {
try {
java.awt.Desktop.getDesktop().open(file);
} catch (Throwable e) {
LOG.log(Level.SEVERE, "Unable to open " + path + " by java.awt.Desktop.getDesktop()::open", e);
LOG.error("Unable to open " + path + " by java.awt.Desktop.getDesktop()::open", e);
}
});
}
@@ -431,7 +430,7 @@ public final class FXUtils {
else
LOG.warning("Show " + path + " in explorer failed with code " + exitCode);
} catch (Throwable e) {
LOG.log(Level.WARNING, "Unable to show " + path + " in explorer", e);
LOG.warning("Unable to show " + path + " in explorer", e);
}
// Fallback to open folder
@@ -468,7 +467,7 @@ public final class FXUtils {
Runtime.getRuntime().exec(new String[]{"rundll32.exe", "url.dll,FileProtocolHandler", link});
return;
} catch (Throwable e) {
LOG.log(Level.WARNING, "An exception occurred while calling rundll32", e);
LOG.warning("An exception occurred while calling rundll32", e);
}
}
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) {
@@ -480,7 +479,7 @@ public final class FXUtils {
}
} catch (Throwable ignored) {
}
Logging.LOG.log(Level.WARNING, "No known browser found");
LOG.warning("No known browser found");
}
}
try {
@@ -490,9 +489,9 @@ public final class FXUtils {
try {
Runtime.getRuntime().exec(new String[]{"/usr/bin/open", link});
} catch (IOException ex) {
Logging.LOG.log(Level.WARNING, "Unable to open link: " + link, ex);
LOG.warning("Unable to open link: " + link, ex);
}
Logging.LOG.log(Level.WARNING, "Failed to open link: " + link, e);
LOG.warning("Failed to open link: " + link, e);
}
});
}
@@ -508,7 +507,7 @@ public final class FXUtils {
stage.setTitle(title);
stage.showAndWait();
} catch (NoClassDefFoundError | UnsatisfiedLinkError e) {
LOG.log(Level.WARNING, "WebView is missing or initialization failed, use JEditorPane replaced", e);
LOG.warning("WebView is missing or initialization failed, use JEditorPane replaced", e);
SwingUtils.initLookAndFeel();
SwingUtilities.invokeLater(() -> {
@@ -760,7 +759,7 @@ public final class FXUtils {
try (InputStream inputStream = Files.newInputStream(currentPath)) {
return new Image(inputStream, requestedWidth, requestedHeight, preserveRatio, smooth);
} catch (IOException e) {
LOG.log(Level.WARNING, "An exception encountered while reading data from cached image file.", e);
LOG.warning("An exception encountered while reading data from cached image file.", e);
}
}
@@ -770,7 +769,7 @@ public final class FXUtils {
try {
Files.deleteIfExists(currentPath);
} catch (IOException e) {
LOG.log(Level.WARNING, "An exception encountered while deleting broken cached image file.", e);
LOG.warning("An exception encountered while deleting broken cached image file.", e);
}
}
@@ -943,7 +942,14 @@ public final class FXUtils {
if ("a".equals(element.getTagName())) {
String href = element.getAttribute("href");
JFXHyperlink hyperlink = new JFXHyperlink(element.getTextContent());
hyperlink.setOnAction(e -> hyperlinkAction.accept(href));
hyperlink.setOnAction(e -> {
String link = href;
try {
link = new URI(href).toASCIIString();
} catch (URISyntaxException ignored) {
}
hyperlinkAction.accept(link);
});
texts.add(hyperlink);
} else if ("b".equals(element.getTagName())) {
Text text = new Text(element.getTextContent());
@@ -960,7 +966,7 @@ public final class FXUtils {
}
return texts;
} catch (SAXException | ParserConfigurationException | IOException e) {
LOG.log(Level.WARNING, "Failed to parse xml", e);
LOG.warning("Failed to parse xml", e);
return Collections.singletonList(new Text(segment));
}
}
@@ -44,8 +44,9 @@ import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.util.Log4jLevel;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.logging.Logger;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.platform.Architecture;
import org.jackhuang.hmcl.util.platform.CommandBuilder;
@@ -60,14 +61,13 @@ import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -102,7 +102,7 @@ public class GameCrashWindow extends Stage {
memory = Optional.ofNullable(launchOptions.getMaxMemory()).map(i -> i + " MB").orElse("-");
total_memory = Optional.ofNullable(OperatingSystem.TOTAL_MEMORY).map(i -> i + " MB").orElse("-");
total_memory = OperatingSystem.TOTAL_MEMORY + " MB";
this.java = launchOptions.getJava().getArchitecture() == Architecture.SYSTEM_ARCH
? launchOptions.getJava().getVersion()
@@ -131,7 +131,7 @@ public class GameCrashWindow extends Stage {
try {
crashReport = CrashReportAnalyzer.findCrashReport(rawLog);
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to read crash report", e);
LOG.warning("Failed to read crash report", e);
}
if (crashReport == null) {
crashReport = CrashReportAnalyzer.extractCrashReport(rawLog);
@@ -148,7 +148,7 @@ public class GameCrashWindow extends Stage {
try {
log = FileUtils.readText(latestLog);
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to read logs/latest.log", e);
LOG.warning("Failed to read logs/latest.log", e);
return pair(new HashSet<CrashReportAnalyzer.Result>(), new HashSet<String>());
}
@@ -157,7 +157,7 @@ public class GameCrashWindow extends Stage {
loading.set(false);
if (exception != null) {
LOG.log(Level.WARNING, "Failed to analyze crash report", exception);
LOG.warning("Failed to analyze crash report", exception);
reasonTextFlow.getChildren().setAll(FXUtils.parseSegment(i18n("game.crash.reason.unknown"), Controllers::onHyperlinkAction));
} else {
EnumMap<CrashReportAnalyzer.Rule, CrashReportAnalyzer.Result> results = new EnumMap<>(CrashReportAnalyzer.Rule.class);
@@ -169,64 +169,60 @@ public class GameCrashWindow extends Stage {
keywords.addAll(pair.getValue());
}
List<Node> segments = new ArrayList<>();
List<Node> segments = new ArrayList<>(FXUtils.parseSegment(i18n("game.crash.feedback"), Controllers::onHyperlinkAction));
boolean hasMultipleRules = results.keySet().stream().distinct().count() > 1;
if (hasMultipleRules) {
segments.addAll(FXUtils.parseSegment(i18n("game.crash.feedback"), Controllers::onHyperlinkAction));
LOG.info("Number of reasons: " + results.size());
if (results.size() > 1) {
segments.add(new Text("\n"));
segments.addAll(FXUtils.parseSegment(i18n("game.crash.reason.multiple"), Controllers::onHyperlinkAction));
LOG.log(Level.INFO, "Multiple reasons detected");
} else {
segments.add(new Text("\n\n"));
}
for (CrashReportAnalyzer.Result result : results.values()) {
String message;
switch (result.getRule()) {
case TOO_OLD_JAVA:
segments.addAll(FXUtils.parseSegment(i18n("game.crash.reason.too_old_java",
CrashReportAnalyzer.getJavaVersionFromMajorVersion(Integer.parseInt(result.getMatcher().group("expected")))), Controllers::onHyperlinkAction));
message = i18n("game.crash.reason.too_old_java", CrashReportAnalyzer.getJavaVersionFromMajorVersion(Integer.parseInt(result.getMatcher().group("expected"))));
break;
case MOD_RESOLUTION_CONFLICT:
case MOD_RESOLUTION_MISSING:
case MOD_RESOLUTION_COLLECTION:
segments.addAll(FXUtils.parseSegment(i18n("game.crash.reason." + result.getRule().name().toLowerCase(Locale.ROOT),
message = i18n("game.crash.reason." + result.getRule().name().toLowerCase(Locale.ROOT),
translateFabricModId(result.getMatcher().group("sourcemod")),
parseFabricModId(result.getMatcher().group("destmod")),
parseFabricModId(result.getMatcher().group("destmod"))), Controllers::onHyperlinkAction));
parseFabricModId(result.getMatcher().group("destmod")));
break;
case MOD_RESOLUTION_MISSING_MINECRAFT:
segments.addAll(FXUtils.parseSegment(i18n("game.crash.reason." + result.getRule().name().toLowerCase(Locale.ROOT),
message = i18n("game.crash.reason." + result.getRule().name().toLowerCase(Locale.ROOT),
translateFabricModId(result.getMatcher().group("mod")),
result.getMatcher().group("version")), Controllers::onHyperlinkAction));
result.getMatcher().group("version"));
break;
case MOD_FOREST_OPTIFINE:
case TWILIGHT_FOREST_OPTIFINE:
case PERFORMANT_FOREST_OPTIFINE:
case JADE_FOREST_OPTIFINE:
segments.addAll(FXUtils.parseSegment(i18n("game.crash.reason.mod", "OptiFine"), Controllers::onHyperlinkAction));
case NEOFORGE_FOREST_OPTIFINE:
message = i18n("game.crash.reason.mod", "OptiFine");
LOG.info("Crash cause: " + result.getRule() + ": " + i18n("game.crash.reason.mod", "OptiFine"));
break;
default:
segments.addAll(FXUtils.parseSegment(i18n("game.crash.reason." + result.getRule().name().toLowerCase(Locale.ROOT),
message = i18n("game.crash.reason." + result.getRule().name().toLowerCase(Locale.ROOT),
Arrays.stream(result.getRule().getGroupNames()).map(groupName -> result.getMatcher().group(groupName))
.toArray()), Controllers::onHyperlinkAction));
.toArray());
break;
}
segments.add(new Text("\n"));
if (hasMultipleRules) {
segments.add(new Text("\n"));
} else {
segments.add(new Text("\n"));
segments.addAll(FXUtils.parseSegment(i18n("game.crash.feedback"), Controllers::onHyperlinkAction));
segments.add(new Text("\n"));
}
LOG.log(Level.INFO, "Crash cause: " + result.getRule());
LOG.info("Crash cause: " + result.getRule() + ": " + message);
segments.addAll(FXUtils.parseSegment(message, Controllers::onHyperlinkAction));
segments.add(new Text("\n\n"));
}
if (results.isEmpty()) {
if (!keywords.isEmpty()) {
reasonTextFlow.getChildren().setAll(new Text(i18n("game.crash.reason.stacktrace", String.join(", ", keywords))));
LOG.log(Level.INFO, "Crash reason unknown, but some log keywords have been found: " + String.join(", ", keywords));
LOG.info("Crash reason unknown, but some log keywords have been found: " + String.join(", ", keywords));
} else {
reasonTextFlow.getChildren().setAll(FXUtils.parseSegment(i18n("game.crash.reason.unknown"), Controllers::onHyperlinkAction));
LOG.log(Level.INFO, "Crash reason unknown");
LOG.info("Crash reason unknown");
}
} else {
feedbackTextFlow.setVisible(false);
@@ -268,7 +264,7 @@ public class GameCrashWindow extends Stage {
private void showLogWindow() {
LogWindow logWindow = new LogWindow(managedProcess);
logWindow.logLine(Logging.filterForbiddenToken("Command: " + new CommandBuilder().addAll(managedProcess.getCommands())), Log4jLevel.INFO);
logWindow.logLine(Logger.filterForbiddenToken("Command: " + new CommandBuilder().addAll(managedProcess.getCommands())), Log4jLevel.INFO);
if (managedProcess.getClasspath() != null)
logWindow.logLine("ClassPath: " + managedProcess.getClasspath(), Log4jLevel.INFO);
for (Map.Entry<String, Log4jLevel> entry : logs)
@@ -284,15 +280,20 @@ public class GameCrashWindow extends Stage {
logs.stream().map(Pair::getKey).collect(Collectors.joining(OperatingSystem.LINE_SEPARATOR)))
.thenComposeAsync(logs ->
LogExporter.exportLogs(logFile, repository, launchOptions.getVersionName(), logs, new CommandBuilder().addAll(managedProcess.getCommands()).toString()))
.thenRunAsync(() -> {
FXUtils.showFileInExplorer(logFile);
.handleAsync((result, exception) -> {
Alert alert;
if (exception == null) {
FXUtils.showFileInExplorer(logFile);
alert = new Alert(Alert.AlertType.INFORMATION, i18n("settings.launcher.launcher_log.export.success", logFile));
} else {
LOG.warning("Failed to export game crash info", exception);
alert = new Alert(Alert.AlertType.WARNING, i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(exception));
}
Alert alert = new Alert(Alert.AlertType.INFORMATION, i18n("settings.launcher.launcher_log.export.success", logFile));
alert.setTitle(i18n("settings.launcher.launcher_log.export"));
alert.showAndWait();
}, Schedulers.javafx())
.exceptionally(e -> {
LOG.log(Level.WARNING, "Failed to export game crash info", e);
return null;
});
}
@@ -270,10 +270,12 @@ public class InstallerItem extends Control {
return i18n("install.installer.incompatible", i18n("install.installer." + incompatibleWith));
} else if (version == null) {
return i18n("install.installer.not_installed");
} else {
} else if (control.id.equals(MINECRAFT.getPatchId()) || control.removable.get() || control.upgradable.get()) {
return i18n("install.installer.version", version);
} else {
return i18n("install.installer.external_version", version);
}
}, control.incompatibleLibraryName, control.incompatibleWithGame, control.libraryVersion));
}, control.incompatibleLibraryName, control.incompatibleWithGame, control.libraryVersion, control.installable, control.removable, control.upgradable));
BorderPane.setMargin(statusLabel, new Insets(0, 0, 0, 8));
BorderPane.setAlignment(statusLabel, Pos.CENTER_LEFT);
@@ -297,8 +299,9 @@ public class InstallerItem extends Control {
control.upgradable));
arrowButton.getStyleClass().add("toggle-icon4");
arrowButton.visibleProperty().bind(Bindings.createBooleanBinding(
() -> control.installable.get() && control.incompatibleLibraryName.get() == null,
control.installable, control.incompatibleLibraryName));
() -> control.installable.get() && control.libraryVersion.get() == null && control.incompatibleLibraryName.get() == null,
control.installable, control.libraryVersion, control.incompatibleLibraryName
));
arrowButton.managedProperty().bind(arrowButton.visibleProperty());
arrowButton.onMouseClickedProperty().bind(control.action);
buttonsContainer.getChildren().add(arrowButton);
@@ -55,13 +55,12 @@ import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.StringUtils.parseEscapeSequence;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -210,7 +209,7 @@ public final class LogWindow extends Stage {
try {
Files.write(logFile, logs.stream().map(x -> x.log).collect(Collectors.toList()));
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to export logs", e);
LOG.warning("Failed to export logs", e);
return;
}
@@ -238,7 +237,7 @@ public final class LogWindow extends Stage {
FXUtils.showFileInExplorer(dumpFile);
}
} catch (Throwable e) {
LOG.log(Level.WARNING, "Failed to create minecraft jstack dump", e);
LOG.warning("Failed to create minecraft jstack dump", e);
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.ERROR, i18n("logwindow.export_dump.dependency_ok.button"));
@@ -27,11 +27,10 @@ import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.upgrade.RemoteVersion;
import java.util.logging.Level;
import static org.jackhuang.hmcl.Metadata.CHANGELOG_URL;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class UpgradeDialog extends JFXDialogLayout {
@@ -56,7 +55,7 @@ public class UpgradeDialog extends JFXDialogLayout {
});
setBody(webView);
} catch (NoClassDefFoundError | UnsatisfiedLinkError e) {
LOG.log(Level.WARNING, "WebView is missing or initialization failed", e);
LOG.warning("WebView is missing or initialization failed", e);
FXUtils.openLink(url);
}
}
@@ -27,7 +27,6 @@ import javafx.scene.control.Tooltip;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.auth.yggdrasil.TextureModel;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.game.TexturesLoader;
import org.jackhuang.hmcl.setting.Accounts;
@@ -58,7 +57,7 @@ public class AccountAdvancedListItem extends AdvancedListItem {
tooltip.setText(i18n("account.create"));
TexturesLoader.unbindAvatar(canvas);
TexturesLoader.drawAvatar(canvas, TexturesLoader.getDefaultSkin(TextureModel.STEVE).getImage());
TexturesLoader.drawAvatar(canvas, TexturesLoader.getDefaultSkinImage());
} else {
titleProperty().bind(BindingMapping.of(account, Account::getCharacter));
@@ -54,11 +54,10 @@ import java.io.IOException;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.logging.Level;
import static java.util.Collections.emptySet;
import static javafx.beans.binding.Bindings.createBooleanBinding;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class AccountListItem extends RadioButton {
@@ -109,11 +108,11 @@ public class AccountListItem extends RadioButton {
} catch (CancellationException e1) {
// ignore cancellation
} catch (Exception e1) {
LOG.log(Level.WARNING, "Failed to refresh " + account + " with password", e1);
LOG.warning("Failed to refresh " + account + " with password", e1);
throw e1;
}
} catch (AuthenticationException e) {
LOG.log(Level.WARNING, "Failed to refresh " + account + " with token", e);
LOG.warning("Failed to refresh " + account + " with token", e);
throw e;
}
});
@@ -44,10 +44,9 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping;
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
import java.net.URI;
import java.util.logging.Level;
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
@@ -137,7 +136,7 @@ public class AccountListPage extends DecoratorAnimatedPage implements DecoratorP
try {
host = URI.create(server.getUrl()).getHost();
} catch (IllegalArgumentException e) {
LOG.log(Level.WARNING, "Unparsable authlib-injector server url " + server.getUrl(), e);
LOG.warning("Unparsable authlib-injector server url " + server.getUrl(), e);
}
item.subtitleProperty().set(host);
Tooltip tooltip = new Tooltip();
@@ -35,11 +35,10 @@ import org.jackhuang.hmcl.util.io.NetworkUtils;
import javax.net.ssl.SSLException;
import java.io.IOException;
import java.util.logging.Level;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class AddAuthlibInjectorServerPane extends TransitionPane implements DialogAware {
@@ -201,7 +200,7 @@ public final class AddAuthlibInjectorServerPane extends TransitionPane implement
this.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
} else {
LOG.log(Level.WARNING, "Failed to resolve auth server: " + url, exception);
LOG.warning("Failed to resolve auth server: " + url, exception);
lblCreationWarning.setText(resolveFetchExceptionMessage(exception));
}
}).start();
@@ -36,10 +36,9 @@ import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.RequiredValidator;
import java.util.function.Consumer;
import java.util.logging.Level;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class ClassicAccountLoginDialog extends StackPane {
@@ -109,7 +108,7 @@ public class ClassicAccountLoginDialog extends StackPane {
fireEvent(new DialogCloseEvent());
progressBar.setVisible(false);
}, e -> {
LOG.log(Level.INFO, "Failed to login with password: " + oldAccount, e);
LOG.info("Failed to login with password: " + oldAccount, e);
if (e instanceof NoSelectedCharacterException) {
fireEvent(new DialogCloseEvent());
} else {
@@ -21,9 +21,8 @@ import org.jackhuang.hmcl.ui.construct.JFXHyperlink;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import java.util.function.Consumer;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class OAuthAccountLoginDialog extends DialogPane {
@@ -96,7 +95,7 @@ public class OAuthAccountLoginDialog extends DialogPane {
success.accept(authInfo);
onSuccess();
} else {
LOG.log(Level.INFO, "Failed to login when credentials expired: " + account, exception);
LOG.info("Failed to login when credentials expired: " + account, exception);
onFailure(Accounts.localizeErrorMessage(exception));
}
}).start();
@@ -25,7 +25,6 @@ import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.input.DragEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.*;
@@ -35,6 +34,7 @@ import moe.mickey.minecraft.skin.fx.animation.SkinAniWavingArms;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.auth.offline.Skin;
import org.jackhuang.hmcl.auth.yggdrasil.TextureModel;
import org.jackhuang.hmcl.game.TexturesLoader;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
@@ -42,11 +42,11 @@ import org.jackhuang.hmcl.ui.construct.*;
import java.io.File;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.UUID;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class OfflineAccountSkinPane extends StackPane {
@@ -71,7 +71,7 @@ public class OfflineAccountSkinPane extends StackPane {
BorderPane pane = new BorderPane();
SkinCanvas canvas = new SkinCanvas(SkinCanvas.STEVE, 300, 300, true);
SkinCanvas canvas = new SkinCanvas(TexturesLoader.getDefaultSkinImage(), 300, 300, true);
StackPane canvasPane = new StackPane(canvas);
canvasPane.setPrefWidth(300);
canvasPane.setPrefHeight(300);
@@ -116,11 +116,11 @@ public class OfflineAccountSkinPane extends StackPane {
));
modelCombobox.setConverter(stringConverter(model -> i18n("account.skin.model." + model.modelName)));
modelCombobox.getItems().setAll(TextureModel.STEVE, TextureModel.ALEX);
modelCombobox.getItems().setAll(TextureModel.WIDE, TextureModel.SLIM);
if (account.getSkin() == null) {
skinItem.setSelectedData(Skin.Type.DEFAULT);
modelCombobox.setValue(TextureModel.STEVE);
modelCombobox.setValue(TextureModel.WIDE);
} else {
skinItem.setSelectedData(account.getSkin().getType());
cslApiField.setText(account.getSkin().getCslApi());
@@ -133,16 +133,21 @@ public class OfflineAccountSkinPane extends StackPane {
getSkin().load(account.getUsername())
.whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception != null) {
LOG.log(Level.WARNING, "Failed to load skin", exception);
LOG.warning("Failed to load skin", exception);
Controllers.showToast(i18n("message.failed"));
} else {
UUID uuid = this.account.getUUID();
if (result == null || result.getSkin() == null && result.getCape() == null) {
canvas.updateSkin(getDefaultTexture(), isDefaultSlim(), null);
canvas.updateSkin(
TexturesLoader.getDefaultSkin(uuid).getImage(),
TexturesLoader.getDefaultModel(uuid) == TextureModel.SLIM,
null
);
return;
}
canvas.updateSkin(
result.getSkin() != null ? result.getSkin().getImage() : getDefaultTexture(),
result.getModel() == TextureModel.ALEX,
result.getSkin() != null ? result.getSkin().getImage() : TexturesLoader.getDefaultSkin(uuid).getImage(),
result.getModel() == TextureModel.SLIM,
result.getCape() != null ? result.getCape().getImage() : null);
}
}).start();
@@ -198,17 +203,4 @@ public class OfflineAccountSkinPane extends StackPane {
private Skin getSkin() {
return new Skin(skinItem.getSelectedData(), cslApiField.getText(), modelCombobox.getValue(), skinSelector.getValue(), capeSelector.getValue());
}
private boolean isDefaultSlim() {
return TextureModel.detectUUID(account.getUUID()) == TextureModel.ALEX;
}
private Image getDefaultTexture() {
if (isDefaultSlim()) {
return SkinCanvas.ALEX;
} else {
return SkinCanvas.STEVE;
}
}
}
@@ -31,11 +31,11 @@ import org.jackhuang.hmcl.ui.animation.AnimationProducer;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionPane;
import org.jackhuang.hmcl.ui.wizard.Navigation;
import org.jackhuang.hmcl.util.Logging;
import java.util.Optional;
import java.util.Stack;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public class Navigator extends TransitionPane {
private static final String PROPERTY_DIALOG_CLOSE_HANDLER = Navigator.class.getName() + ".closeListener";
@@ -65,7 +65,7 @@ public class Navigator extends TransitionPane {
if (from == node)
return;
Logging.LOG.info("Navigate to " + node);
LOG.info("Navigate to " + node);
stack.push(node);
backable.set(canGoBack());
@@ -104,11 +104,11 @@ public class Navigator extends TransitionPane {
if (stack.peek() != from) {
// Allow page to be closed multiple times.
Logging.LOG.log(Level.INFO, "Closing already closed page: " + from, new Throwable());
LOG.info("Closing already closed page: " + from, new Throwable());
return;
}
Logging.LOG.info("Closed page " + from);
LOG.info("Closed page " + from);
Node poppedNode = stack.pop();
NavigationEvent exited = new NavigationEvent(this, poppedNode, Navigation.NavigationDirection.PREVIOUS, NavigationEvent.EXITED);
@@ -19,11 +19,12 @@ package org.jackhuang.hmcl.ui.construct;
import javafx.scene.Node;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.util.Logging;
import java.util.Optional;
import java.util.Stack;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public class StackContainerPane extends StackPane {
private final Stack<Node> stack = new Stack<>();
@@ -39,7 +40,7 @@ public class StackContainerPane extends StackPane {
stack.push(node);
getChildren().setAll(node);
Logging.LOG.info(this + " " + stack);
LOG.info(this + " " + stack);
}
public void pop(Node node) {
@@ -49,7 +50,7 @@ public class StackContainerPane extends StackPane {
else
getChildren().setAll(stack.peek());
Logging.LOG.info(this + " " + stack + ", removed: " + flag + ", object: " + node);
LOG.info(this + " " + stack + ", removed: " + flag + ", object: " + node);
}
public boolean isEmpty() {
@@ -50,6 +50,10 @@ import org.jackhuang.hmcl.ui.wizard.WizardProvider;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -58,15 +62,13 @@ import java.util.Locale;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.stream.Stream;
import static java.util.logging.Level.WARNING;
import static java.util.stream.Collectors.toList;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.io.FileUtils.getExtension;
public class DecoratorController {
@@ -168,8 +170,23 @@ public class DecoratorController {
break;
case NETWORK:
String backgroundImageUrl = config().getBackgroundImageUrl();
if (backgroundImageUrl != null && NetworkUtils.isURL(backgroundImageUrl))
image = tryLoadImage(backgroundImageUrl).orElse(null);
if (backgroundImageUrl != null) {
try {
URLConnection connection = NetworkUtils.createConnection(new URL(backgroundImageUrl));
if (connection instanceof HttpURLConnection) {
connection = NetworkUtils.resolveConnection((HttpURLConnection) connection);
}
try (InputStream input = connection.getInputStream()) {
image = new Image(input);
if (image.isError()) {
throw image.getException();
}
}
} catch (Exception e) {
LOG.warning("Couldn't load background image", e);
}
}
break;
case CLASSIC:
image = newBuiltinImage("/assets/img/background-classic.jpg");
@@ -215,7 +232,7 @@ public class DecoratorController {
})
.collect(toList());
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to list files in ./bg", e);
LOG.warning("Failed to list files in ./bg", e);
return Optional.empty();
}
@@ -244,12 +261,12 @@ public class DecoratorController {
try {
img = new Image(url);
} catch (IllegalArgumentException e) {
LOG.log(WARNING, "Couldn't load background image", e);
LOG.warning("Couldn't load background image", e);
return Optional.empty();
}
if (img.getException() != null) {
LOG.log(WARNING, "Couldn't load background image", img.getException());
LOG.warning("Couldn't load background image", img.getException());
return Optional.empty();
}
@@ -42,10 +42,9 @@ import java.io.File;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.Lang.tryCast;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class LocalModpackPage extends ModpackPage {
@@ -121,7 +120,7 @@ public final class LocalModpackPage extends ModpackPage {
controller.getSettings().put(MODPACK_MANUALLY_CREATED, true);
} else if (exception != null) {
LOG.log(Level.WARNING, "Failed to read modpack manifest", exception);
LOG.warning("Failed to read modpack manifest", exception);
Controllers.dialog(i18n("modpack.task.install.error"), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
Platform.runLater(controller::onEnd);
} else {
@@ -41,6 +41,7 @@ import org.jackhuang.hmcl.download.quilt.QuiltAPIRemoteVersion;
import org.jackhuang.hmcl.download.quilt.QuiltRemoteVersion;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.setting.VersionIconType;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionPane;
@@ -51,17 +52,15 @@ import org.jackhuang.hmcl.ui.construct.RipplerContainer;
import org.jackhuang.hmcl.ui.wizard.Navigation;
import org.jackhuang.hmcl.ui.wizard.Refreshable;
import org.jackhuang.hmcl.ui.wizard.WizardPage;
import org.jackhuang.hmcl.util.HMCLService;
import org.jackhuang.hmcl.util.Holder;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.wrap;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -232,7 +231,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
}
});
} else {
LOG.log(Level.WARNING, "Failed to fetch versions list", exception);
LOG.warning("Failed to fetch versions list", exception);
Platform.runLater(() -> {
if (versionList != currentVersionList) return;
root.setContent(failedPane, ContainerAnimations.FADE.getAnimationProducer());
@@ -263,7 +262,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
private void onBack() { navigation.onPrev(true); }
private void onSponsor() {
HMCLService.openRedirectLink("bmclapi_sponsor");
FXUtils.openLink("https://bmclapidoc.bangbang93.com");
}
private static class RemoteVersionListCell extends ListCell<RemoteVersion> {
@@ -179,7 +179,7 @@ public final class ModpackInfoPage extends Control implements WizardPage {
if (skinnable.controller.getSettings().get(MODPACK_TYPE) == MODPACK_TYPE_SERVER) {
Hyperlink hyperlink = new Hyperlink(i18n("modpack.wizard.step.initialization.server"));
hyperlink.setOnMouseClicked(e -> {
FXUtils.openLink("https://hmcl.huangyuhui.net/api/redirect/server-modpack");
FXUtils.openLink("https://docs.hmcl.net/modpack/serverpack.html");
});
borderPane.setTop(hyperlink);
} else {
@@ -80,6 +80,18 @@ public class AboutPage extends StackPane {
zkitefly.setSubtitle(i18n("about.thanks_to.zkitefly.statement"));
zkitefly.setExternalLink("https://github.com/zkitefly");
IconedTwoLineListItem burningtnt = new IconedTwoLineListItem();
burningtnt.setImage(FXUtils.newBuiltinImage("/assets/img/burningtnt.png"));
burningtnt.setTitle("Burning_TNT");
burningtnt.setSubtitle(i18n("about.thanks_to.burningtnt.statement"));
burningtnt.setExternalLink("https://github.com/burningtnt");
IconedTwoLineListItem shulkerSakura = new IconedTwoLineListItem();
shulkerSakura.setTitle("ShulkerSakura");
shulkerSakura.setImage(FXUtils.newBuiltinImage("/assets/img/ShulkerSakura.png"));
shulkerSakura.setSubtitle(i18n("about.thanks_to.shulkersakura.statement"));
shulkerSakura.setExternalLink("https://github.com/ShulkerSakura");
IconedTwoLineListItem gamerteam = new IconedTwoLineListItem();
gamerteam.setTitle("gamerteam");
gamerteam.setImage(FXUtils.newBuiltinImage("/assets/img/gamerteam.png"));
@@ -91,18 +103,17 @@ public class AboutPage extends StackPane {
redLnn.setImage(FXUtils.newBuiltinImage("/assets/img/red_lnn.png"));
redLnn.setSubtitle(i18n("about.thanks_to.red_lnn.statement"));
// IconedTwoLineListItem mcbbs = new IconedTwoLineListItem();
// mcbbs.setImage(FXUtils.newBuiltinImage("/assets/img/chest.png"));
// mcbbs.setTitle(i18n("about.thanks_to.mcbbs"));
// mcbbs.setSubtitle(i18n("about.thanks_to.mcbbs.statement"));
// mcbbs.setExternalLink("https://www.mcbbs.net/");
IconedTwoLineListItem mcmod = new IconedTwoLineListItem();
mcmod.setImage(FXUtils.newBuiltinImage("/assets/img/mcmod.png"));
mcmod.setTitle(i18n("about.thanks_to.mcmod"));
mcmod.setSubtitle(i18n("about.thanks_to.mcmod.statement"));
mcmod.setExternalLink("https://www.mcmod.cn/");
IconedTwoLineListItem mcbbs = new IconedTwoLineListItem();
mcbbs.setImage(FXUtils.newBuiltinImage("/assets/img/chest.png"));
mcbbs.setTitle(i18n("about.thanks_to.mcbbs"));
mcbbs.setSubtitle(i18n("about.thanks_to.mcbbs.statement"));
IconedTwoLineListItem contributors = new IconedTwoLineListItem();
contributors.setImage(FXUtils.newBuiltinImage("/assets/img/github.png"));
contributors.setTitle(i18n("about.thanks_to.contributors"));
@@ -113,9 +124,9 @@ public class AboutPage extends StackPane {
users.setImage(FXUtils.newBuiltinImage("/assets/img/icon.png"));
users.setTitle(i18n("about.thanks_to.users"));
users.setSubtitle(i18n("about.thanks_to.users.statement"));
users.setExternalLink("https://hmcl.huangyuhui.net/api/redirect/sponsor");
users.setExternalLink("https://docs.hmcl.net/groups.html");
thanks.getContent().setAll(yushijinhun, bangbang93, glavo, zekerzhayard, zkitefly, /* mcbbs, */ mcmod, gamerteam, redLnn, contributors, users);
thanks.getContent().setAll(yushijinhun, bangbang93, glavo, zekerzhayard, zkitefly, burningtnt, mcmod, mcbbs, shulkerSakura, gamerteam, redLnn, contributors, users);
}
ComponentList dep = new ComponentList();
@@ -138,7 +149,7 @@ public class AboutPage extends StackPane {
IconedTwoLineListItem xz = new IconedTwoLineListItem();
xz.setTitle("XZ for Java");
xz.setSubtitle("Lasse Collin, Igor Pavlov, and/or Brett Okken.\nPublic Domain.");
xz.setExternalLink("https://xz.tukaani.org/xz-for-java/");
xz.setExternalLink("https://tukaani.org/xz/java.html");
IconedTwoLineListItem fxgson = new IconedTwoLineListItem();
fxgson.setTitle("fx-gson");
@@ -59,13 +59,7 @@ public class FeedbackPage extends SpinnerPane {
discord.setSubtitle(i18n("feedback.discord.statement"));
discord.setExternalLink("https://discord.gg/jVvC7HfM6U");
IconedTwoLineListItem kookapp = new IconedTwoLineListItem();
kookapp.setImage(FXUtils.newBuiltinImage("/assets/img/kookapp.png"));
kookapp.setTitle(i18n("feedback.kookapp"));
kookapp.setSubtitle(i18n("feedback.kookapp.statement"));
kookapp.setExternalLink("https://kook.top/Kx7n3t");
community.getContent().setAll(users, github, discord, kookapp);
community.getContent().setAll(users, github, discord);
}
content.getChildren().addAll(
@@ -54,12 +54,11 @@ import java.time.Instant;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
@@ -102,7 +101,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
try {
Controllers.navigate(new NBTEditorPage(file));
} catch (Throwable e) {
LOG.log(Level.WARNING, "Fail to open nbt file", e);
LOG.warning("Fail to open nbt file", e);
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e),
i18n("message.error"), MessageDialogPane.MessageType.ERROR);
}
@@ -31,23 +31,23 @@ import org.jackhuang.hmcl.upgrade.RemoteVersion;
import org.jackhuang.hmcl.upgrade.UpdateChannel;
import org.jackhuang.hmcl.upgrade.UpdateChecker;
import org.jackhuang.hmcl.upgrade.UpdateHandler;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.i18n.Locales;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import java.util.logging.Level;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
@@ -129,11 +129,11 @@ public final class SettingsPage extends SettingsView {
Path logFile = Paths.get("hmcl-exported-logs-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss")) + ".log").toAbsolutePath();
LOG.info("Exporting logs to " + logFile);
try {
Files.write(logFile, Logging.getRawLogs());
try (OutputStream output = Files.newOutputStream(logFile)) {
LOG.exportLogs(output);
} catch (IOException e) {
Platform.runLater(() -> Controllers.dialog(i18n("settings.launcher.launcher_log.export.failed") + "\n" + e, null, MessageType.ERROR));
LOG.log(Level.WARNING, "Failed to export logs", e);
Platform.runLater(() -> Controllers.dialog(i18n("settings.launcher.launcher_log.export.failed") + "\n" + StringUtils.getStackTrace(e), null, MessageType.ERROR));
LOG.warning("Failed to export logs", e);
return;
}
@@ -144,7 +144,7 @@ public final class SettingsPage extends SettingsView {
@Override
protected void onSponsor() {
FXUtils.openLink("https://hmcl.huangyuhui.net/api/redirect/sponsor");
FXUtils.openLink("https://github.com/HMCL-dev/HMCL");
}
@Override
@@ -37,6 +37,7 @@ import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.ComponentList;
import org.jackhuang.hmcl.ui.construct.ComponentSublist;
import org.jackhuang.hmcl.ui.construct.MultiFileItem;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
import java.util.Arrays;
@@ -176,8 +177,8 @@ public abstract class SettingsView extends StackPane {
languagePane.setLeft(left);
cboLanguage = new JFXComboBox<>();
cboLanguage.setConverter(stringConverter(locale -> locale.getName(config().getLocalization().getResourceBundle())));
FXUtils.setLimitWidth(cboLanguage, 400);
cboLanguage.setConverter(stringConverter(I18n::getName));
FXUtils.setLimitWidth(cboLanguage, 300);
languagePane.setRight(cboLanguage);
settingsPane.getContent().add(languagePane);
@@ -1,174 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.main;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import com.jfoenix.controls.JFXListCell;
import com.jfoenix.controls.JFXListView;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Cursor;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.text.TextAlignment;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.Holder;
import org.jackhuang.hmcl.util.io.HttpRequest;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.List;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class SponsorPage extends StackPane {
private final JFXListView<Sponsor> listView;
public SponsorPage() {
VBox content = new VBox();
content.setPadding(new Insets(10));
content.setSpacing(10);
content.setFillWidth(true);
{
StackPane sponsorPane = new StackPane();
sponsorPane.getStyleClass().add("card");
sponsorPane.setCursor(Cursor.HAND);
sponsorPane.setOnMouseClicked(e -> onSponsor());
GridPane gridPane = new GridPane();
ColumnConstraints col = new ColumnConstraints();
col.setHgrow(Priority.SOMETIMES);
col.setMaxWidth(Double.POSITIVE_INFINITY);
gridPane.getColumnConstraints().setAll(col);
RowConstraints row = new RowConstraints();
row.setMinHeight(Double.NEGATIVE_INFINITY);
row.setValignment(VPos.TOP);
row.setVgrow(Priority.SOMETIMES);
gridPane.getRowConstraints().setAll(row);
{
Label label = new Label(i18n("sponsor.hmcl"));
label.setWrapText(true);
label.setTextAlignment(TextAlignment.JUSTIFY);
GridPane.setRowIndex(label, 0);
GridPane.setColumnIndex(label, 0);
gridPane.getChildren().add(label);
}
sponsorPane.getChildren().setAll(gridPane);
content.getChildren().add(sponsorPane);
}
{
StackPane pane = new StackPane();
pane.getStyleClass().add("card");
listView = new JFXListView<>();
Holder<Object> lastCell = new Holder<>();
listView.setCellFactory((listView) -> new JFXListCell<Sponsor>() {
@Override
public void updateItem(Sponsor item, boolean empty) {
super.updateItem(item, empty);
// https://mail.openjdk.org/pipermail/openjfx-dev/2022-July/034764.html
if (this == lastCell.value && !isVisible())
return;
lastCell.value = this;
if (!empty) {
setText(item.getName());
setGraphic(null);
}
}
});
VBox.setVgrow(pane, Priority.ALWAYS);
pane.getChildren().setAll(listView);
content.getChildren().add(pane);
}
loadSponsorList();
getChildren().setAll(content);
}
private void onSponsor() {
FXUtils.openLink("https://hmcl.huangyuhui.net/api/redirect/sponsor");
}
private void loadSponsorList() {
Task.<List<Sponsor>>supplyAsync(() -> HttpRequest.GET("https://hmcl.huangyuhui.net/api/sponsor").getJson(new TypeToken<List<Sponsor>>() {
}.getType())).thenAcceptAsync(Schedulers.javafx(), sponsors -> {
listView.getItems().setAll(sponsors);
}).start();
}
private static class Sponsor {
@SerializedName("name")
private final String name;
@SerializedName("create_time")
private final Instant createTime;
@SerializedName("money")
private final BigDecimal money;
@SerializedName("contact")
private final String contact;
@SerializedName("afdian_id")
private final String afdianId;
public Sponsor() {
this("", Instant.now(), BigDecimal.ZERO, "", "");
}
public Sponsor(String name, Instant createTime, BigDecimal money, String contact, String afdianId) {
this.name = name;
this.createTime = createTime;
this.money = money;
this.contact = contact;
this.afdianId = afdianId;
}
public String getName() {
return name;
}
public Instant getCreateTime() {
return createTime;
}
public BigDecimal getMoney() {
return money;
}
public String getContact() {
return contact;
}
public String getAfdianId() {
return afdianId;
}
}
}
@@ -18,10 +18,9 @@ import org.jackhuang.hmcl.util.StringUtils;
import java.io.*;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class NBTEditorPage extends BorderPane implements DecoratorPage {
@@ -53,7 +52,7 @@ public class NBTEditorPage extends BorderPane implements DecoratorPage {
try {
save();
} catch (IOException ex) {
LOG.log(Level.WARNING, "Failed to save NBT file", ex);
LOG.warning("Failed to save NBT file", ex);
Controllers.dialog(i18n("nbt.save.failed") + "\n\n" + StringUtils.getStackTrace(ex));
}
});
@@ -73,7 +72,7 @@ public class NBTEditorPage extends BorderPane implements DecoratorPage {
}, Schedulers.javafx())
.handleAsync((result, e) -> {
if (e != null) {
LOG.log(Level.WARNING, "Fail to open nbt file", e);
LOG.warning("Fail to open nbt file", e);
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e), null, MessageDialogPane.MessageType.WARNING, cancelButton::fire);
}
return null;
@@ -29,7 +29,6 @@ import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.ListPageBase;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
@@ -38,8 +37,8 @@ import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Objects;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class DatapackListPage extends ListPageBase<DatapackListPageSkin.DatapackInfoObject> implements DecoratorPage {
@@ -71,7 +70,7 @@ public class DatapackListPage extends ListPageBase<DatapackListPageSkin.Datapack
zip.loadFromZip();
zip.installTo(worldDir);
} catch (IOException | IllegalArgumentException e) {
Logging.LOG.log(Level.WARNING, "Unable to parse datapack file " + datapack, e);
LOG.warning("Unable to parse datapack file " + datapack, e);
}
}
@@ -117,7 +116,7 @@ public class DatapackListPage extends ListPageBase<DatapackListPageSkin.Datapack
datapack.deletePack(pack);
} catch (IOException e) {
// Fail to remove mods if the game is running or the datapack is absent.
Logging.LOG.warning("Failed to delete datapack " + pack);
LOG.warning("Failed to delete datapack " + pack);
}
});
}
@@ -477,7 +477,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
protected void updateControl(RemoteMod dataItem, boolean empty) {
if (empty) return;
ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(getSkinnable().repository.getType()).getModByCurseForgeId(dataItem.getSlug());
content.setTitle(mod != null && I18n.getCurrentLocale().getLocale() == Locale.CHINA ? mod.getDisplayName() : dataItem.getTitle());
content.setTitle(mod != null && I18n.isUseChinese() ? mod.getDisplayName() : dataItem.getTitle());
content.setSubtitle(dataItem.getDescription());
content.getTags().setAll(dataItem.getCategories().stream()
.map(category -> getSkinnable().getLocalizedCategory(category))
@@ -94,22 +94,11 @@ public class DownloadPage extends Control implements DecoratorPage {
}
private void loadModVersions() {
File versionJar = StringUtils.isNotBlank(version.getVersion())
? version.getProfile().getRepository().getVersionJar(version.getVersion())
: null;
setLoading(true);
setFailed(false);
Task.supplyAsync(() -> {
Stream<RemoteMod.Version> versions = addon.getData().loadVersions(repository);
// if (StringUtils.isNotBlank(version.getVersion())) {
// Optional<String> gameVersion = GameVersion.minecraftVersion(versionJar);
// if (gameVersion.isPresent()) {
// return sortVersions(
// .filter(file -> file.getGameVersions().contains(gameVersion.get())));
// }
// }
return sortVersions(versions);
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
if (exception == null) {
@@ -240,7 +229,7 @@ public class DownloadPage extends Control implements DecoratorPage {
TwoLineListItem content = new TwoLineListItem();
HBox.setHgrow(content, Priority.ALWAYS);
ModTranslations.Mod mod = getSkinnable().translations.getModByCurseForgeId(getSkinnable().addon.getSlug());
content.setTitle(mod != null && I18n.getCurrentLocale().getLocale() == Locale.CHINA ? mod.getDisplayName() : getSkinnable().addon.getTitle());
content.setTitle(mod != null && I18n.isUseChinese() ? mod.getDisplayName() : getSkinnable().addon.getTitle());
content.setSubtitle(getSkinnable().addon.getDescription());
content.getTags().setAll(getSkinnable().addon.getCategories().stream()
.map(category -> getSkinnable().page.getLocalizedCategory(category))
@@ -253,14 +242,6 @@ public class DownloadPage extends Control implements DecoratorPage {
descriptionPane.getChildren().add(openMcmodButton);
openMcmodButton.setMinWidth(Region.USE_PREF_SIZE);
runInFX(() -> FXUtils.installFastTooltip(openMcmodButton, i18n("mods.mcmod")));
// if (StringUtils.isNotBlank(getSkinnable().mod.getMcbbs())) {
// JFXHyperlink openMcbbsButton = new JFXHyperlink(i18n("mods.mcbbs"));
// openMcbbsButton.setExternalLink(ModManager.getMcbbsUrl(getSkinnable().mod.getMcbbs()));
// descriptionPane.getChildren().add(openMcbbsButton);
// openMcbbsButton.setMinWidth(Region.USE_PREF_SIZE);
// runInFX(() -> FXUtils.installFastTooltip(openMcbbsButton, i18n("mods.mcbbs")));
// }
}
JFXHyperlink openUrlButton = new JFXHyperlink(control.page.getLocalizedOfficialPage());
@@ -294,19 +275,27 @@ public class DownloadPage extends Control implements DecoratorPage {
if (control.version.getProfile() != null && control.version.getVersion() != null) {
HMCLGameRepository repository = control.version.getProfile().getRepository();
Version game = repository.getResolvedPreservingPatchesVersion(control.version.getVersion());
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(game, repository.getGameVersion(game).orElse(null));
libraryAnalyzer.getVersion(LibraryAnalyzer.LibraryType.MINECRAFT).ifPresent(currentGameVersion -> {
Set<ModLoaderType> currentGameModLoaders = libraryAnalyzer.getModLoaders();
if (control.versions.containsKey(currentGameVersion)) {
control.versions.get(currentGameVersion).stream()
.filter(version1 -> version1.getLoaders().isEmpty() || version1.getLoaders().stream().anyMatch(currentGameModLoaders::contains))
.findFirst()
.ifPresent(value -> list.getContent().addAll(
ComponentList.createComponentListTitle(i18n("mods.download.recommend", currentGameVersion)),
new ModItem(value, control)
));
String gameVersion = repository.getGameVersion(game).orElse(null);
if (gameVersion != null) {
List<RemoteMod.Version> modVersions = control.versions.get(gameVersion);
if (modVersions != null && !modVersions.isEmpty()) {
Set<ModLoaderType> targetLoaders = LibraryAnalyzer.analyze(game, gameVersion).getModLoaders();
resolve:
for (RemoteMod.Version modVersion : modVersions) {
for (ModLoaderType loader : modVersion.getLoaders()) {
if (targetLoaders.contains(loader)) {
list.getContent().addAll(
ComponentList.createComponentListTitle(i18n("mods.download.recommend", gameVersion)),
new ModItem(modVersion, control)
);
break resolve;
}
}
}
}
});
}
}
for (String gameVersion : control.versions.keys().stream()
@@ -352,15 +341,21 @@ public class DownloadPage extends Control implements DecoratorPage {
container.setOnMouseClicked(e -> Controllers.navigate(new DownloadPage(page, addon, version, callback)));
getChildren().setAll(container);
ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(page.repository.getType()).getModByCurseForgeId(addon.getSlug());
content.setTitle(mod != null && I18n.getCurrentLocale().getLocale() == Locale.CHINA ? mod.getDisplayName() : addon.getTitle());
content.setSubtitle(addon.getDescription());
content.getTags().setAll(addon.getCategories().stream()
.map(page::getLocalizedCategory)
.collect(Collectors.toList()));
if (addon != RemoteMod.BROKEN) {
ModTranslations.Mod mod = ModTranslations.getTranslationsByRepositoryType(page.repository.getType()).getModByCurseForgeId(addon.getSlug());
content.setTitle(mod != null && I18n.isUseChinese() ? mod.getDisplayName() : addon.getTitle());
content.setSubtitle(addon.getDescription());
content.getTags().setAll(addon.getCategories().stream()
.map(page::getLocalizedCategory)
.collect(Collectors.toList()));
if (StringUtils.isNotBlank(addon.getIconUrl())) {
imageView.setImage(FXUtils.newRemoteImage(addon.getIconUrl(), 40, 40, true, true, true));
if (StringUtils.isNotBlank(addon.getIconUrl())) {
imageView.setImage(FXUtils.newRemoteImage(addon.getIconUrl(), 40, 40, true, true, true));
}
} else {
content.setTitle(i18n("mods.broken_dependency.title"));
content.setSubtitle(i18n("mods.broken_dependency.desc"));
imageView.setImage(FXUtils.newBuiltinImage("/assets/img/icon@8x.png", 40, 40, true, true));
}
}
}
@@ -35,12 +35,11 @@ import java.io.IOException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT;
import static org.jackhuang.hmcl.util.Lang.handleUncaught;
import static org.jackhuang.hmcl.util.Lang.threadPool;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class GameItem extends Control {
@@ -84,7 +83,7 @@ public class GameItem extends Control {
if (config == null) return;
tag.set(config.getVersion());
} catch (IOException | JsonParseException e) {
LOG.log(Level.WARNING, "Failed to read modpack configuration from " + version, e);
LOG.warning("Failed to read modpack configuration from " + version, e);
}
}, Platform::runLater)
.exceptionally(handleUncaught);
@@ -30,7 +30,6 @@ import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.task.TaskListener;
import org.jackhuang.hmcl.ui.*;
import org.jackhuang.hmcl.ui.download.UpdateInstallerWizardProvider;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.TaskCancellationAction;
import org.jackhuang.hmcl.util.io.FileUtils;
@@ -90,13 +89,15 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
for (InstallerItem installerItem : group.getLibraries()) {
String libraryId = installerItem.getLibraryId();
String libraryVersion = analyzer.getVersion(libraryId).orElse(null);
boolean libraryConfigurable = libraryVersion != null && analyzer.getLibraryStatus(libraryId) == LibraryAnalyzer.LibraryMark.LibraryStatus.CLEAR;
installerItem.libraryVersion.set(libraryVersion);
installerItem.upgradable.set(libraryVersion != null);
installerItem.upgradable.set(libraryConfigurable);
installerItem.installable.set(true);
installerItem.action.set(e -> {
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, libraryId, libraryVersion));
});
boolean removable = !LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId().equals(libraryId) && libraryVersion != null;
boolean removable = !LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId().equals(libraryId) && libraryConfigurable;
installerItem.removable.set(removable);
if (removable) {
Runnable action = removeAction.apply(libraryId);
@@ -119,17 +120,10 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
InstallerItem installerItem = new InstallerItem(libraryId);
installerItem.libraryVersion.set(libraryVersion);
installerItem.installable.set(false);
installerItem.upgradable.bind(installerItem.installable);
installerItem.upgradable.set(false);
installerItem.removable.set(true);
installerItem.removeAction.set(e -> action.run());
if (libraryVersion != null && Lang.test(() -> profile.getDependency().getVersionList(libraryId))) {
installerItem.installable.set(true);
installerItem.action.set(e -> {
Controllers.getDecorator().startWizard(new UpdateInstallerWizardProvider(profile, gameVersion, version, libraryId, libraryVersion));
});
}
itemsProperty().add(installerItem);
}
}, Platform::runLater);
@@ -175,7 +169,8 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
@Override
protected List<Node> initializeToolbar(InstallerListPage skinnable) {
return Collections.singletonList(
createToolbarButton2(i18n("install.installer.install_offline"), SVG.PLUS, skinnable::installOffline));
createToolbarButton2(i18n("install.installer.install_offline"), SVG.PLUS, skinnable::installOffline)
);
}
}
}
@@ -36,7 +36,6 @@ import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.ListPageBase;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.PageAware;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.TaskCancellationAction;
import org.jackhuang.hmcl.util.io.FileUtils;
@@ -45,10 +44,10 @@ import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObject> implements VersionPage.VersionLoadable, PageAware {
@@ -65,7 +64,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
try {
modManager.addMod(it.toPath());
} catch (IOException | IllegalArgumentException e) {
Logging.LOG.log(Level.WARNING, "Unable to parse mod file " + it, e);
LOG.warning("Unable to parse mod file " + it, e);
}
});
loadMods(modManager);
@@ -135,7 +134,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
modManager.addMod(file.toPath());
succeeded.add(file.getName());
} catch (Exception e) {
Logging.LOG.log(Level.WARNING, "Unable to add mod " + file, e);
LOG.warning("Unable to add mod " + file, e);
failed.add(file.getName());
// Actually addMod will not throw exceptions because FileChooser has already filtered files.
@@ -61,14 +61,13 @@ import java.nio.file.Path;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.createToolbarButton2;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.StringUtils.isNotBlank;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -218,7 +217,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
Pattern pattern = Pattern.compile(queryString.substring("regex:".length()));
predicate = s -> pattern.matcher(s).find();
} catch (Throwable e) {
LOG.log(Level.WARNING, "Illegal regular expression", e);
LOG.warning("Illegal regular expression", e);
return;
}
} else {
@@ -391,15 +390,6 @@ class ModListPageSkin extends SkinBase<ModListPage> {
getActions().add(officialPageButton);
}
// if (modInfo.getMod() != null && StringUtils.isNotBlank(modInfo.getMod().getMcbbs())) {
// JFXHyperlink mcbbsButton = new JFXHyperlink(i18n("mods.mcbbs"));
// mcbbsButton.setOnAction(e -> {
// fireEvent(new DialogCloseEvent());
// FXUtils.openLink(ModManager.getMcbbsUrl(modInfo.getMod().getMcbbs()));
// });
// getActions().add(mcbbsButton);
// }
if (modInfo.getMod() == null || StringUtils.isBlank(modInfo.getMod().getMcmod())) {
JFXHyperlink searchButton = new JFXHyperlink(i18n("mods.mcmod.search"));
searchButton.setOnAction(e -> {
@@ -490,10 +480,8 @@ class ModListPageSkin extends SkinBase<ModListPage> {
content.getTags().add(i18n("install.installer.quilt"));
break;
}
if (dataItem.getMod() != null) {
if (I18n.getCurrentLocale().getLocale() == Locale.CHINA) {
content.getTags().add(dataItem.getMod().getDisplayName());
}
if (dataItem.getMod() != null && I18n.isUseChinese()) {
content.getTags().add(dataItem.getMod().getDisplayName());
}
content.setSubtitle(dataItem.getSubtitle());
if (booleanProperty != null) {
@@ -24,10 +24,9 @@ import org.jackhuang.hmcl.util.io.IOUtils;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.Pair.pair;
/**
@@ -127,7 +126,7 @@ public enum ModTranslations {
mods = Arrays.stream(modData.split("\n")).filter(line -> !line.startsWith("#")).map(Mod::new).collect(Collectors.toList());
return true;
} catch (Exception e) {
LOG.log(Level.WARNING, "Failed to load " + resourceName, e);
LOG.warning("Failed to load " + resourceName, e);
return false;
}
}
@@ -198,10 +197,9 @@ public enum ModTranslations {
return true;
}
public static class Mod {
public static final class Mod {
private final String curseforge;
private final String mcmod;
private final String mcbbs;
private final List<String> modIds;
private final String name;
private final String subname;
@@ -209,23 +207,21 @@ public enum ModTranslations {
public Mod(String line) {
String[] items = line.split(";", -1);
if (items.length != 7) {
throw new IllegalArgumentException("Illegal mod data line, 7 items expected " + line);
if (items.length != 6) {
throw new IllegalArgumentException("Illegal mod data line, 6 items expected " + line);
}
curseforge = items[0];
mcmod = items[1];
mcbbs = items[2];
modIds = Collections.unmodifiableList(Arrays.asList(items[3].split(",")));
name = items[4];
subname = items[5];
abbr = items[6];
modIds = Collections.unmodifiableList(Arrays.asList(items[2].split(",")));
name = items[3];
subname = items[4];
abbr = items[5];
}
public Mod(String curseforge, String mcmod, String mcbbs, List<String> modIds, String name, String subname, String abbr) {
public Mod(String curseforge, String mcmod, List<String> modIds, String name, String subname, String abbr) {
this.curseforge = curseforge;
this.mcmod = mcmod;
this.mcbbs = mcbbs;
this.modIds = modIds;
this.name = name;
this.subname = subname;
@@ -252,10 +248,6 @@ public enum ModTranslations {
return mcmod;
}
public String getMcbbs() {
return mcbbs;
}
public List<String> getModIds() {
return modIds;
}
@@ -21,7 +21,11 @@ import org.jackhuang.hmcl.game.LocalizedRemoteModRepository;
import org.jackhuang.hmcl.mod.RemoteModRepository;
import org.jackhuang.hmcl.mod.curse.CurseForgeRemoteModRepository;
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
import org.jackhuang.hmcl.util.i18n.I18n;
import java.util.MissingResourceException;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class ResourcePackDownloadListPage extends DownloadListPage {
@@ -66,10 +70,18 @@ public class ResourcePackDownloadListPage extends DownloadListPage {
@Override
protected String getLocalizedCategory(String category) {
String key;
if ("mods.modrinth".equals(downloadSource.get())) {
return i18n("modrinth.category." + category);
key = "modrinth.category." + category;
} else {
return i18n("curse.category." + category);
key = "curse.category." + category;
}
try {
return I18n.getResourceBundle().getString(key);
} catch (MissingResourceException e) {
LOG.warning("Cannot find key " + key + " in resource bundle", e);
return category;
}
}
@@ -31,13 +31,11 @@ import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.DialogPane;
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class VersionIconDialog extends DialogPane {
@@ -74,20 +72,19 @@ public class VersionIconDialog extends DialogPane {
private void exploreIcon() {
FileChooser chooser = new FileChooser();
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.png"), "*.png"));
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.png"), "*.png", "*.jpg", "*.bmp", "*.gif"));
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
if (selectedFile != null) {
File iconFile = profile.getRepository().getVersionIconFile(versionId);
try {
FileUtils.copyFile(selectedFile, iconFile);
profile.getRepository().setVersionIconFile(versionId, selectedFile);
if (vs != null) {
vs.setVersionIcon(VersionIconType.DEFAULT);
}
onAccept();
} catch (IOException e) {
Logging.LOG.log(Level.SEVERE, "Failed to copy icon file from " + selectedFile + " to " + iconFile, e);
} catch (IOException | IllegalArgumentException e) {
LOG.error("Failed to set icon file: " + selectedFile, e);
}
}
}
@@ -33,6 +33,7 @@ import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.EventPriority;
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
import org.jackhuang.hmcl.game.GameRepository;
import org.jackhuang.hmcl.game.HMCLGameRepository;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
@@ -164,7 +165,11 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
}
private void clearAssets() {
FileUtils.deleteDirectoryQuietly(new File(getProfile().getRepository().getBaseDirectory(), "assets"));
HMCLGameRepository baseDirectory = getProfile().getRepository();
FileUtils.deleteDirectoryQuietly(new File(baseDirectory.getBaseDirectory(), "assets"));
if (version.get() != null) {
FileUtils.deleteDirectoryQuietly(new File(baseDirectory.getRunDirectory(version.get().getVersion()), "resources"));
}
}
private void clearJunkFiles() {
@@ -30,7 +30,9 @@ import javafx.scene.control.ScrollPane;
import javafx.scene.layout.*;
import javafx.scene.text.Text;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.game.GameDirectoryType;
import org.jackhuang.hmcl.game.HMCLGameRepository;
import org.jackhuang.hmcl.game.ProcessPriority;
import org.jackhuang.hmcl.setting.*;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
@@ -48,7 +50,6 @@ import org.jackhuang.hmcl.util.platform.JavaVersion;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
@@ -185,7 +186,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
gameDirSublist = new ComponentSublist();
gameDirSublist.getContent().add(gameDirItem);
gameDirSublist.setTitle(i18n("settings.game.working_directory"));
gameDirSublist.setHasSubtitle(true);
gameDirSublist.setHasSubtitle(versionId != null);
gameDirItem.disableProperty().bind(modpack);
gameDirCustomOption = new MultiFileItem.FileOption<>(i18n("settings.custom"), GameDirectoryType.CUSTOM)
.setChooserTitle(i18n("settings.game.working_directory.choose"))
@@ -642,9 +643,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
if (versionId == null)
return;
File iconFile = profile.getRepository().getVersionIconFile(versionId);
if (iconFile.exists())
iconFile.delete();
profile.getRepository().deleteIconFile(versionId);
VersionSetting localVersionSetting = profile.getRepository().getLocalVersionSettingOrCreate(versionId);
if (localVersionSetting != null) {
localVersionSetting.setVersionIcon(VersionIconType.DEFAULT);
@@ -41,7 +41,6 @@ import org.jackhuang.hmcl.ui.construct.PromptDialogPane;
import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.TaskCancellationAction;
import org.jackhuang.hmcl.util.io.FileUtils;
@@ -55,8 +54,8 @@ import java.nio.file.Path;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class Versions {
@@ -84,7 +83,7 @@ public final class Versions {
} catch (IOException e) {
Controllers.dialog(
i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e),
i18n("download.failed"), MessageDialogPane.MessageType.ERROR);
i18n("download.failed.no_code", file.getFile().getUrl()), MessageDialogPane.MessageType.ERROR);
return;
}
Controllers.taskDialog(
@@ -97,7 +96,7 @@ public final class Versions {
} else {
Controllers.dialog(
i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e),
i18n("download.failed"), MessageDialogPane.MessageType.ERROR);
i18n("download.failed.no_code", file.getFile().getUrl()), MessageDialogPane.MessageType.ERROR);
}
}).executor(true),
i18n("message.downloading"),
@@ -184,7 +183,7 @@ public final class Versions {
try {
profile.getRepository().clean(id);
} catch (IOException e) {
Logging.LOG.log(Level.WARNING, "Unable to clean game directory", e);
LOG.warning("Unable to clean game directory", e);
}
}
@@ -27,9 +27,8 @@ import java.text.DecimalFormat;
import java.time.Instant;
import java.util.Arrays;
import java.util.Locale;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -421,7 +420,7 @@ public final class WorldInfoPage extends StackPane implements DecoratorPage {
try {
this.world.writeLevelDat(levelDat);
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to save level.dat of world " + world.getWorldName(), e);
LOG.warning("Failed to save level.dat of world " + world.getWorldName(), e);
}
}
@@ -28,7 +28,6 @@ import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.*;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
@@ -39,10 +38,10 @@ import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class WorldListPage extends ListPageBase<WorldListItem> implements VersionPage.VersionLoadable {
@@ -139,7 +138,7 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
}).start();
}, world.getWorldName());
}, e -> {
Logging.LOG.log(Level.WARNING, "Unable to parse world file " + zipFile, e);
LOG.warning("Unable to parse world file " + zipFile, e);
Controllers.dialog(i18n("world.import.invalid"));
}).start();
}
@@ -19,10 +19,11 @@ package org.jackhuang.hmcl.ui.wizard;
import javafx.scene.Node;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Logging;
import java.util.*;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public class WizardController implements Navigation {
private final WizardDisplayer displayer;
private WizardProvider provider = null;
@@ -71,7 +72,7 @@ public class WizardController implements Navigation {
displayer.onStart();
Logging.LOG.info("Navigating to " + page + ", pages: " + pages);
LOG.info("Navigating to " + page + ", pages: " + pages);
displayer.navigateTo(page, NavigationDirection.START);
}
@@ -90,7 +91,7 @@ public class WizardController implements Navigation {
if (page instanceof WizardPage)
((WizardPage) page).onNavigate(settings);
Logging.LOG.info("Navigating to " + page + ", pages: " + pages);
LOG.info("Navigating to " + page + ", pages: " + pages);
displayer.navigateTo(page, NavigationDirection.NEXT);
}
@@ -113,7 +114,7 @@ public class WizardController implements Navigation {
if (prevPage instanceof WizardPage)
((WizardPage) prevPage).onNavigate(settings);
Logging.LOG.info("Navigating to " + prevPage + ", pages: " + pages);
LOG.info("Navigating to " + prevPage + ", pages: " + pages);
displayer.navigateTo(prevPage, NavigationDirection.PREVIOUS);
}
@@ -31,12 +31,11 @@ import java.security.spec.X509EncodedKeySpec;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/**
* A class that checks the integrity of HMCL.
@@ -132,7 +131,7 @@ public final class IntegrityChecker {
LOG.info("Successfully verified current JAR");
selfVerified = true;
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to verify myself, is the JAR corrupt?", e);
LOG.warning("Failed to verify myself, is the JAR corrupt?", e);
selfVerified = false;
}
@@ -27,11 +27,10 @@ import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.io.IOException;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.Pair.pair;
public final class UpdateChecker {
@@ -112,7 +111,7 @@ public final class UpdateChecker {
result = checkUpdate(channel);
LOG.info("Latest version (" + channel + ") is " + result);
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to check for update", e);
LOG.warning("Failed to check for update", e);
}
RemoteVersion finalResult = result;
@@ -42,13 +42,12 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CancellationException;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.jackhuang.hmcl.ui.FXUtils.checkFxUserThread;
import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class UpdateHandler {
@@ -66,7 +65,7 @@ public final class UpdateHandler {
try {
performMigration();
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to perform migration", e);
LOG.warning("Failed to perform migration", e);
SwingUtils.showErrorDialog(i18n("fatal.apply_update_failure", Metadata.PUBLISH_URL) + "\n" + StringUtils.getStackTrace(e));
}
return true;
@@ -81,7 +80,7 @@ public final class UpdateHandler {
try {
applyUpdate(Paths.get(args[1]));
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to apply update", e);
LOG.warning("Failed to apply update", e);
SwingUtils.showErrorDialog(i18n("fatal.apply_update_failure", Metadata.PUBLISH_URL) + "\n" + StringUtils.getStackTrace(e));
}
return true;
@@ -108,7 +107,7 @@ public final class UpdateHandler {
try {
downloaded = Files.createTempFile("hmcl-update-", ".jar");
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to create temp file", e);
LOG.warning("Failed to create temp file", e);
return;
}
@@ -127,15 +126,15 @@ public final class UpdateHandler {
}
requestUpdate(downloaded, getCurrentLocation());
System.exit(0);
Main.exit(0);
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to update to " + version, e);
LOG.warning("Failed to update to " + version, e);
Platform.runLater(() -> Controllers.dialog(StringUtils.getStackTrace(e), i18n("update.failed"), MessageType.ERROR));
}
} else {
Exception e = executor.getException();
LOG.log(Level.WARNING, "Failed to update to " + version, e);
LOG.warning("Failed to update to " + version, e);
if (e instanceof CancellationException) {
Platform.runLater(() -> Controllers.showToast(i18n("message.cancelled")));
} else {
@@ -162,7 +161,7 @@ public final class UpdateHandler {
Files.move(target, newFilename.get());
target = newFilename.get();
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to move target", e);
LOG.warning("Failed to move target", e);
}
}
@@ -273,7 +272,7 @@ public final class UpdateHandler {
LOG.info("Successfully broke the force update feature");
}
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to break the force update feature", e);
LOG.warning("Failed to break the force update feature", e);
} catch (JsonParseException e) {
hmclVersionJson.toFile().delete();
}
@@ -29,9 +29,8 @@ import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
@@ -67,14 +66,14 @@ public final class CrashReporter implements Thread.UncaughtExceptionHandler {
if (s.contains(entry.getKey())) {
if (StringUtils.isNotBlank(entry.getValue())) {
String info = entry.getValue();
LOG.severe(info);
LOG.error(info);
try {
Alert alert = new Alert(AlertType.INFORMATION, info);
alert.setTitle(i18n("message.info"));
alert.setHeaderText(i18n("message.info"));
alert.showAndWait();
} catch (Throwable t) {
LOG.log(Level.SEVERE, "Unable to show message", t);
LOG.error("Unable to show message", t);
}
}
return false;
@@ -90,14 +89,14 @@ public final class CrashReporter implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
LOG.log(Level.SEVERE, "Uncaught exception in thread " + t.getName(), e);
LOG.error("Uncaught exception in thread " + t.getName(), e);
try {
CrashReport report = new CrashReport(t, e);
if (!report.shouldBeReport())
return;
LOG.log(Level.SEVERE, report.getDisplayText());
LOG.error(report.getDisplayText());
Platform.runLater(() -> {
if (checkThrowable(e)) {
if (showCrashWindow) {
@@ -109,8 +108,10 @@ public final class CrashReporter implements Thread.UncaughtExceptionHandler {
}
});
} catch (Throwable handlingException) {
LOG.log(Level.SEVERE, "Unable to handle uncaught exception", handlingException);
LOG.error("Unable to handle uncaught exception", handlingException);
}
LOG.shutdown();
}
private void reportToServer(CrashReport crashReport) {
@@ -118,13 +119,13 @@ public final class CrashReporter implements Thread.UncaughtExceptionHandler {
HashMap<String, String> map = new HashMap<>();
map.put("crash_report", crashReport.getDisplayText());
map.put("version", Metadata.VERSION);
map.put("log", Logging.getLogs());
map.put("log", LOG.getLogs());
try {
String response = NetworkUtils.doPost(NetworkUtils.toURL("https://hmcl.huangyuhui.net/hmcl/crash.php"), map);
if (StringUtils.isNotBlank(response))
LOG.log(Level.SEVERE, "Crash server response: " + response);
LOG.error("Crash server response: " + response);
} catch (IOException ex) {
LOG.log(Level.SEVERE, "Unable to post HMCL server.", ex);
LOG.error("Unable to post HMCL server.", ex);
}
});
t.setDaemon(true);
@@ -1,29 +0,0 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util;
import org.jackhuang.hmcl.ui.FXUtils;
public final class HMCLService {
private HMCLService() {
}
public static void openRedirectLink(String id) {
FXUtils.openLink("https://hmcl.huangyuhui.net/api/redirect/" + id);
}
}
@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.util;
import java.nio.file.Path;
import java.util.Set;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/**
* Utility for Adding JavaFX to module path.
@@ -15,10 +15,9 @@ import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class NativePatcher {
private NativePatcher() {
@@ -37,7 +36,7 @@ public final class NativePatcher {
return natives.getOrDefault(p.toString(), Collections.emptyMap());
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to load native library list", e);
LOG.warning("Failed to load native library list", e);
return Collections.emptyMap();
}
});
@@ -43,6 +43,7 @@ package org.jackhuang.hmcl.util;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.ui.SwingUtils;
import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
import org.jackhuang.hmcl.util.io.IOUtils;
@@ -62,12 +63,11 @@ import java.util.List;
import java.util.*;
import java.util.concurrent.CancellationException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
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.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.platform.JavaVersion.CURRENT_JAVA;
@@ -121,7 +121,7 @@ public final class SelfDependencyPatcher {
Class.forName("netscape.javascript.JSObject", false, classLoader);
Class.forName("org.w3c.dom.html.HTMLDocument", false, classLoader);
} catch (Throwable e) {
LOG.log(Level.WARNING, "Disable javafx.web because JRE is incomplete", e);
LOG.warning("Disable javafx.web because JRE is incomplete", e);
dependencies.removeIf(it -> "javafx.web".equals(it.module) || "javafx.media".equals(it.module));
}
@@ -250,7 +250,7 @@ public final class SelfDependencyPatcher {
}
} else {
LOG.info("User choose not to download JavaFX");
System.exit(0);
Main.exit(0);
}
throw new AssertionError();
}
@@ -17,44 +17,49 @@
*/
package org.jackhuang.hmcl.util.i18n;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
import java.time.Instant;
import java.util.Arrays;
import java.util.IllegalFormatException;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class I18n {
private I18n() {
}
public static SupportedLocale getCurrentLocale() {
try {
return ConfigHolder.config().getLocalization();
} catch (IllegalStateException e) {
// e is thrown by ConfigHolder.config(), indicating the config hasn't been loaded
// fallback to use default locale
return Locales.DEFAULT;
}
private static volatile SupportedLocale locale = Locales.DEFAULT;
private static volatile ResourceBundle resourceBundle = locale.getResourceBundle();
private static volatile DateTimeFormatter dateTimeFormatter;
public static void setLocale(SupportedLocale locale) {
I18n.locale = locale;
resourceBundle = locale.getResourceBundle();
dateTimeFormatter = null;
}
public static boolean isUseChinese() {
return locale.getLocale() == Locale.CHINA;
}
public static ResourceBundle getResourceBundle() {
return getCurrentLocale().getResourceBundle();
return resourceBundle;
}
public static String getName(SupportedLocale locale) {
return locale == Locales.DEFAULT ? resourceBundle.getString("lang.default") : locale.getResourceBundle().getString("lang");
}
public static String i18n(String key, Object... formatArgs) {
try {
return String.format(getResourceBundle().getString(key), formatArgs);
} catch (MissingResourceException e) {
LOG.log(Level.SEVERE, "Cannot find key " + key + " in resource bundle", e);
LOG.error("Cannot find key " + key + " in resource bundle", e);
} catch (IllegalFormatException e) {
LOG.log(Level.SEVERE, "Illegal format string, key=" + key + ", args=" + Arrays.toString(formatArgs), e);
LOG.error("Illegal format string, key=" + key + ", args=" + Arrays.toString(formatArgs), e);
}
return key + Arrays.toString(formatArgs);
@@ -64,13 +69,18 @@ public final class I18n {
try {
return getResourceBundle().getString(key);
} catch (MissingResourceException e) {
LOG.log(Level.SEVERE, "Cannot find key " + key + " in resource bundle", e);
LOG.error("Cannot find key " + key + " in resource bundle", e);
return key;
}
}
public static String formatDateTime(Instant instant) {
return getCurrentLocale().getDateTimeFormatter().format(instant);
DateTimeFormatter formatter = dateTimeFormatter;
if (formatter == null) {
formatter = dateTimeFormatter = DateTimeFormatter.ofPattern(getResourceBundle().getString("world.time")).withZone(ZoneId.systemDefault());
}
return formatter.format(instant);
}
public static boolean hasKey(String key) {
@@ -24,8 +24,6 @@ import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import java.io.IOException;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
@@ -34,7 +32,7 @@ public final class Locales {
private Locales() {
}
public static final SupportedLocale DEFAULT = new SupportedLocale(Locale.getDefault(), "lang.default");
public static final SupportedLocale DEFAULT = new SupportedLocale(Locale.getDefault());
/**
* English
@@ -102,23 +100,10 @@ public final class Locales {
@JsonAdapter(SupportedLocale.TypeAdapter.class)
public static final class SupportedLocale {
private final Locale locale;
private final String name;
private final ResourceBundle resourceBundle;
private DateTimeFormatter dateTimeFormatter;
private ResourceBundle resourceBundle;
SupportedLocale(Locale locale) {
this(locale, null);
}
SupportedLocale(Locale locale, String name) {
this.locale = locale;
this.name = name;
if (JavaVersion.CURRENT_JAVA.getParsedVersion() == JavaVersion.JAVA_8) {
resourceBundle = ResourceBundle.getBundle("assets.lang.I18N", locale, UTF8Control.INSTANCE);
} else {
// UTF-8 is supported in Java 9+
resourceBundle = ResourceBundle.getBundle("assets.lang.I18N", locale);
}
}
public Locale getLocale() {
@@ -126,20 +111,22 @@ public final class Locales {
}
public ResourceBundle getResourceBundle() {
return resourceBundle;
}
ResourceBundle bundle = resourceBundle;
public DateTimeFormatter getDateTimeFormatter() {
if (dateTimeFormatter == null) {
dateTimeFormatter = DateTimeFormatter.ofPattern(resourceBundle.getString("world.time")).withZone(ZoneId.systemDefault());
if (resourceBundle == null) {
if (this != DEFAULT && this.locale == DEFAULT.locale) {
bundle = DEFAULT.getResourceBundle();
} else if (JavaVersion.CURRENT_JAVA.getParsedVersion() < 9) {
bundle = ResourceBundle.getBundle("assets.lang.I18N", locale, UTF8Control.INSTANCE);
} else {
// Java 9+ uses UTF-8 as the default encoding for resource bundles
bundle = ResourceBundle.getBundle("assets.lang.I18N", locale);
}
resourceBundle = bundle;
}
return dateTimeFormatter;
}
public String getName(ResourceBundle newResourceBundle) {
if (name == null) return resourceBundle.getString("lang");
else return newResourceBundle.getString(name);
return bundle;
}
public static final class TypeAdapter extends com.google.gson.TypeAdapter<SupportedLocale> {
@@ -0,0 +1,23 @@
package org.jackhuang.hmcl.util.logging;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* @author Glavo
*/
final class CallerFinder {
private static final StackWalker WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
private static final String PACKAGE_PREFIX = CallerFinder.class.getPackageName() + ".";
private static final Predicate<StackWalker.StackFrame> PREDICATE = stackFrame -> !stackFrame.getClassName().startsWith(PACKAGE_PREFIX);
private static final Function<Stream<StackWalker.StackFrame>, Optional<StackWalker.StackFrame>> FUNCTION = stream -> stream.filter(PREDICATE).findFirst();
static String getCaller() {
return WALKER.walk(FUNCTION).map(it -> it.getClassName() + "." + it.getMethodName()).orElse(null);
}
private CallerFinder() {
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 999 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1001 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 861 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 958 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 988 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 947 B

@@ -30,17 +30,19 @@ about.dependency=Third-party Libraries
about.legal=Legal Acknowledgement
about.thanks_to=Thanks to
about.thanks_to.bangbang93.statement=For providing BMCLAPI download API, please consider donating.
about.thanks_to.burningtnt.statement=Contribute a lot of technical support to HMCL.
about.thanks_to.contributors=All contributors on GitHub
about.thanks_to.contributors.statement=Without the awesome open-source community, Hello Minecraft! Launcher would not make it so far.
about.thanks_to.gamerteam.statement=For providing the default background image.
about.thanks_to.glavo.statement=Contribute a lot of technical support to HMCL.
about.thanks_to.glavo.statement=Responsible for maintaining HMCL.
about.thanks_to.zekerzhayard.statement=Contribute a lot of technical support to HMCL.
about.thanks_to.zkitefly.statement=Contribute a lot of technical support to HMCL.
about.thanks_to.zkitefly.statement=Responsible for maintaining documentation of HMCL.
about.thanks_to.mcbbs=MCBBS (Minecraft Chinese Forum)
about.thanks_to.mcbbs.statement=For providing mcbbs.net download mirror for Mainland China users.
about.thanks_to.mcbbs.statement=or providing mcbbs.net download mirror for Mainland China users.
about.thanks_to.mcmod=mcmod.cn
about.thanks_to.mcmod.statement=For providing Chinese translations and wiki for various mods.
about.thanks_to.red_lnn.statement=For providing the default background image.
about.thanks_to.shulkersakura.statement=For providing the icon for HMCL
about.thanks_to.users=HMCL User Group Members
about.thanks_to.users.statement=Thanks for donations, bug reports, and so on.
about.thanks_to.yushijinhun.statement=For providing authlib-injector related support.
@@ -141,8 +143,8 @@ account.portable=Portable Account
account.skin=Skin
account.skin.file=Skin File
account.skin.model=Model
account.skin.model.default=Classic (Steve)
account.skin.model.slim=Slim (Alex)
account.skin.model.default=Classic
account.skin.model.slim=Slim
account.skin.type.csl_api=Blessing Skin
account.skin.type.csl_api.location=Address
account.skin.type.csl_api.location.hint=CustomSkinAPI URL
@@ -183,7 +185,7 @@ button.save_as=Save As
button.select_all=Select All
button.yes=Yes
chat=QQ Groups
chat=Join Group Chat
color.recent=Recommended
color.custom=Custom Color
@@ -323,9 +325,9 @@ download.existing=The file cannot be saved because it already exists. You can us
download.external_link=Open Download Website
download.failed=Failed to download %1$s, response code: %2$d
download.failed.empty=No versions are available, please click here to go back.
download.failed.no_code=Failed to download %s
download.failed.refresh=Unable to fetch version list. Please click here to retry.
download.game=Game
download.provider.mcbbs=MCBBS (https://www.mcbbs.net/)
download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)
download.provider.mojang=Mojang (OptiFine is provided by BMCLAPI)
download.provider.official=From Official Sources
@@ -396,8 +398,6 @@ feedback.discord=Discord
feedback.discord.statement=Join our Discord community.
feedback.github=GitHub Issue
feedback.github.statement=Creating an issue on GitHub.
feedback.kookapp=KOOK
feedback.kookapp.statement=Join our KOOK community.
feedback.qq_group=HMCL User Group
feedback.qq_group.statement=Join HMCL user QQ group.
@@ -413,7 +413,6 @@ folder.saves=Saves
folder.screenshots=Screenshots
game=Game
#game.crash.feedback=Is it a common issue that almost everyone has had? Please leave feedback so that we can improve the crash analysis system!
game.crash.feedback=<b>Please do not share screenshots of this interface with others! </b> If you ask for help from others, please click to export the game crash information in the lower left corner and send the exported file to others for analysis.
game.crash.info=Crash Info
game.crash.reason=Crash Cause
@@ -476,6 +475,7 @@ If your computer has a discrete graphics card, you need to check whether the gam
If you are using your integrated graphics card, please notice that Minecraft 1.16.5 or older requires <a href="https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html">Java 1.8.0_51 or older</a> for Intel(R) Core(TM) 3000 processor series or earlier.\n\
\n\
Turning on the "Use OpenGL software renderer" option in the instance settings can also solve this problem, but when this option is turned on, the frame rate will be significantly reduced in the case of insufficient CPU performance. So it is only recommended to turn it on for debugging purposes or in case of emergency.
game.crash.reason.macos_failed_to_find_service_port_for_display=The current game cannot continue due to a failure to initialize the OpenGL window on the Apple silicon platform.\nFor this issue, HMCL does not have direct solutions at the moment. Please try opening any browser and going fullscreen, then return to HMCL, launch the game, and <b>quickly return to the browser page</b> before the game window pops up, wait for the game window to appear, and then switch back to the game window.
game.crash.reason.illegal_access_error=The game crashed because of some mod(s).\n\
\n\
If you know: %1$s, you can update or delete the mod(s) and then try again.
@@ -637,6 +637,7 @@ install.installer.optifine=OptiFine
install.installer.quilt=Quilt
install.installer.quilt-api=QSL/QFAPI
install.installer.version=%s
install.installer.external_version=%s Installed by external process, which cannot be configured
install.modpack=Install a Modpack
install.new_game=Add a New Instance
install.new_game.already_exists=This instance already exists. Please use another name.
@@ -825,26 +826,39 @@ Keep in mind that you are not allowed to add mods and resource packs that are ex
modpack.wizard.step.initialization.server=Click here for more tutorials for making a server modpack that can be automatically updated.
modrinth.category.adventure=Adventure
modrinth.category.audio=Audio
modrinth.category.blocks=Blocks
modrinth.category.bukkit=Bukkit
modrinth.category.bungeecord=BungeeCord
modrinth.category.challenging=Challenging
modrinth.category.core-shaders=Core Shaders
modrinth.category.combat=Combat
modrinth.category.cursed=Cursed
modrinth.category.decoration=Decoration
modrinth.category.economy=Economy
modrinth.category.entities=Entities
modrinth.category.environment=Environment
modrinth.category.equipment=Equipment
modrinth.category.fabric=Fabric
modrinth.category.fonts=Fonts
modrinth.category.food=Food
modrinth.category.forge=Forge
modrinth.category.game-mechanics=Game Mechanics
modrinth.category.gui=GUI
modrinth.category.items=Items
modrinth.category.kitchen-sink=Kitchen-Sink
modrinth.category.library=Library
modrinth.category.lightweight=Lightweight
modrinth.category.liteloader=LiteLoader
modrinth.category.locale=Locale
modrinth.category.magic=Magic
modrinth.category.management=Management
modrinth.category.minecraft=Minecraft
modrinth.category.minigame=Minigame
modrinth.category.misc=Misc
modrinth.category.mobs=Mobs
modrinth.category.modded=Modded
modrinth.category.models=Models
modrinth.category.modloader=Modloader
modrinth.category.multiplayer=Multiplayer
modrinth.category.neoforge=NeoForge
@@ -853,19 +867,32 @@ modrinth.category.paper=Paper
modrinth.category.purpur=Purpur
modrinth.category.quests=Quests
modrinth.category.quilt=Quilt
modrinth.category.realistic=Realistic
modrinth.category.rift=Rift
modrinth.category.simplistic=Simplistic
modrinth.category.social=Social
modrinth.category.spigot=Spigot
modrinth.category.sponge=Sponge
modrinth.category.storage=Storage
modrinth.category.technology=Technology
modrinth.category.themed=Themed
modrinth.category.transportation=Transportation
modrinth.category.tweaks=Tweaks
modrinth.category.utility=Utility
modrinth.category.vanilla-like=Vanilla-like
modrinth.category.velocity=Velocity
modrinth.category.waterfall=Waterfall
modrinth.category.worldgen=Worldgen
modrinth.category.datapack=Datapack
modrinth.category.folia=Folia
modrinth.category.8x-=8x-
modrinth.category.16x=16x
modrinth.category.32x=32x
modrinth.category.48x=48x
modrinth.category.64x=64x
modrinth.category.128x=128x
modrinth.category.256x=256x
modrinth.category.512x+=512x+
mods=Mods
mods.add=Add Mods
@@ -1118,7 +1145,7 @@ settings.launcher.download_source.auto=Auto Choose Download Mirror
settings.launcher.enable_game_list=Show version list in home page
settings.launcher.font=Font
settings.launcher.general=General
settings.launcher.language=Language
settings.launcher.language=Language (applies after restart)
settings.launcher.launcher_log.export=Export launcher logs
settings.launcher.launcher_log.export.failed=Unable to export logs
settings.launcher.launcher_log.export.success=Logs have been exported to %s
@@ -1162,7 +1189,7 @@ settings.type.special.edit.hint=Current instance [%s] has enabled per-instance s
sponsor=Donators
sponsor.bmclapi=Downloads are provided by BMCLAPI. Click here for more information.
sponsor.hmcl=Hello Minecraft! Launcher is a FOSS Minecraft launcher which allows users to manage multiple Minecraft instances easily. You can donate to us on Afdian to support our file hosting and project development. Click here for more information.
sponsor.hmcl=Hello Minecraft! Launcher is a FOSS Minecraft launcher which allows users to manage multiple Minecraft instances easily. Click here for more information.
system.architecture=Architecture
system.operating_system=Operating System
@@ -36,8 +36,6 @@ about.thanks_to.gamerteam.statement=Por proporcionar la imagen de fondo por defe
about.thanks_to.glavo.statement=Aportar mucho apoyo técnico a HMCL.
about.thanks_to.zekerzhayard.statement=Aportar mucho apoyo técnico a HMCL.
about.thanks_to.zkitefly.statement=Aportar mucho apoyo técnico a HMCL.
about.thanks_to.mcbbs=MCBBS (Foro chino de Minecraft)
about.thanks_to.mcbbs.statement=Por proporcionar un espejo de descarga de mcbbs.net para los usuarios de China continental.
about.thanks_to.mcmod=mcmod.cn
about.thanks_to.mcmod.statement=Por facilitar las traducciones al chino y el wiki de varios mods.
about.thanks_to.red_lnn.statement=Por proporcionar la imagen de fondo por defecto.
@@ -125,8 +123,8 @@ account.password=Contraseña
account.skin=Skin
account.skin.file=Archivo de skin
account.skin.model=Modelo
account.skin.model.default=Clásico (Steve)
account.skin.model.slim=Delgado (Alex)
account.skin.model.default=Clásico
account.skin.model.slim=Delgado
account.skin.type.csl_api=Blessing skin
account.skin.type.csl_api.location=URL
account.skin.type.csl_api.location.hint=CustomSkinAPI URL
@@ -304,7 +302,6 @@ download.failed=Falló la descarga de %1$s, código de respuesta: %2$d
download.failed.empty=No hay versiones disponibles, por favor haga clic aquí para volver.
download.failed.refresh=No se ha podido obtener la lista de versiones. Por favor, haga clic aquí para volver a intentarlo.
download.game=Juego
download.provider.mcbbs=MCBBS (https://www.mcbbs.net/)
download.provider.bmclapi=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)
download.provider.mojang=Mojang (OptiFine es proporcionado por BMCLAPI)
download.provider.official=De fuentes oficiales
@@ -351,8 +348,6 @@ Por ejemplo, no podrá utilizar authlib-injector o cambiar el skin de su cuenta
feedback=Comentarios
feedback.discord=Discord
feedback.discord.statement=¡Únete a nuestra comunidad de Discord!
feedback.kookapp=KOOK
feedback.kookapp.statement=¡Únete a nuestra comunidad de KaiHeiLa!
file=Archivo
@@ -365,7 +360,6 @@ folder.saves=Guardados
folder.screenshots=Capturas de pantalla
game=Juego
#game.crash.feedback=¿Es un problema común que casi todo el mundo ha tenido? Por favor, deja tus comentarios para que podamos mejorar.
game.crash.feedback=<b>¡Por favor, no comparta capturas de pantalla de esta interfaz con otras personas! </b> Si solicita ayuda a otros, haga clic para exportar la información del bloqueo del juego en la esquina inferior izquierda y envíe el archivo exportado a otros para que lo analicen.
game.crash.info=Información sobre el fallo
game.crash.reason=Causa del fallo
@@ -416,6 +410,7 @@ Por favor, inténtelo de nuevo después de actualizar su controlador gráfico a
Si tu ordenador tiene una tarjeta gráfica discreta, tienes que comprobar si el juego utiliza gráficos integrados/núcleo. Si es así, inicie el launcher utilizando su tarjeta gráfica discreta. Si el problema persiste, probablemente debería considerar la posibilidad de adquirir una nueva tarjeta gráfica o un nuevo ordenador.\n\
\n\
Si utiliza su tarjeta gráfica integrada, tenga en cuenta que Minecraft 1.16.5 o anterior requiere <a href="https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html">Java 1.8.0_51 o más antiguo</a> para la serie de procesadores Intel(R) Core(TM) 3000 o anteriores.
game.crash.reason.macos_failed_to_find_service_port_for_display=El juego actual no puede continuar debido a un fallo al inicializar la ventana de OpenGL en la plataforma de Apple silicon.\nPara este problema, HMCL no tiene soluciones directas por el momento. Intente abrir cualquier navegador y ponerlo en pantalla completa, luego vuelva a HMCL, inicie el juego y <b>vuelva rápidamente a la página del navegador</b> antes de que aparezca la ventana del juego, espere a que aparezca la ventana del juego y luego vuelva a la ventana del juego.
game.crash.reason.illegal_access_error=El juego se bloquea por culpa de algún(os) mod(s).\n\
\n\
Si sabes: %1$s, puedes actualizar o eliminar los mods y volver a intentarlo.
@@ -484,7 +479,7 @@ game.crash.reason.mod_profile_causes_game_crash=El juego actual no puede seguir
game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge puede haber dado un mensaje de error.\nPuede ver el registro y realizar las acciones correspondientes de acuerdo con la información de registro en el informe de errores.\nSi no ve un mensaje de error, puede consultar el informe de errores para saber cómo ocurrió el error.
game.crash.reason.java_version_is_too_high=El juego actual se bloqueó porque la versión de Java es demasiado alta y no puede seguir ejecutándose.\nCambie a una versión anterior de Java en la pestaña Java Path de Configuración global del juego o Configuración específica del juego antes de iniciar el juego.\nSi no es así, puede descargarlo desde <a href="https://www.java.com/download/">java.com (Java8)</a> o <a href="https://bell-sw.com/pages/downloads/#downloads">BellSoft Liberica Full JRE (Java17)</a> y otras plataformas para descargar e instalar una (reinicie el iniciador después de la instalación).
game.crash.reason.mod_name=El juego actual no puede continuar ejecutándose debido a un problema con el nombre del archivo mod. El nombre del archivo de\nMod solo debe usar mayúsculas y minúsculas (Aa ~ Zz), números (0 ~ 9), líneas horizontales (-), subrayado (_) y puntos (.) en toda la mitad del inglés.\n Por favor, vaya a la carpeta mod para agregar un carácter de Cumplimiento anterior a todos los nombres de archivo mod que no cumplan.
game.crash.reason.incomplete_forge_installation=O jogo atual não pode continuar devido a uma instalação incompleta do Forge / NeoForge.\nDesinstale e reinstale o Forge em Configurações de versão - Instalação automática.
game.crash.reason.incomplete_forge_installation=O jogo atual não pode continuar devido a uma instalação incompleta do Forge / NeoForge.\nDesinstale e reinstale o Forge / NeoForge em Configurações de versão - Instalação automática.
game.crash.reason.forge_error=Forge puede haber proporcionado un mensaje de error. \nPuede ver el registro y realizar las acciones correspondientes de acuerdo con la información de registro en el informe de errores. \nSi no ve un mensaje de error, puede consultar el informe de errores para saber cómo ocurrió el error.\n%1$s
game.crash.reason.mod_solution0=El juego actual no puede seguir ejecutándose debido a algunos problemas de modificación. \nPuede consultar el registro para ver si hay un mod incorrecto.
game.crash.reason.mod_profile_causes_game_crash=El juego actual no puede seguir ejecutándose debido a un problema con el perfil de mod. \nPuede consultar el registro del mod defectuoso y su archivo de configuración.
@@ -34,8 +34,6 @@ about.thanks_to.gamerteam.statement=デフォルトの背景画像プロバイ
about.thanks_to.glavo.statement=HMCLに多くのテクニカルサポートを提供
about.thanks_to.zekerzhayard.statement=HMCLに多くのテクニカルサポートを提供
about.thanks_to.zkitefly.statement=HMCLに多くのテクニカルサポートを提供。
about.thanks_to.mcbbs=MCBBS
about.thanks_to.mcbbs.statement=MCBBSダウンロードソースプロバイダー。
about.thanks_to.mcmod=mcmod.cn
about.thanks_to.mcmod.statement=Mod中国語の名前翻訳プロバイダー。
about.thanks_to.red_lnn.statement=デフォルトの背景画像プロバイダー。
@@ -112,8 +110,8 @@ account.password=パスワード
account.skin=スキン
account.skin.file=スキンイメージファイル
account.skin.model=モデル
account.skin.model.default=Classic (Steve)
account.skin.model.slim=Slim (Alex)
account.skin.model.default=Classic
account.skin.model.slim=Slim
account.skin.type.csl_api=Blessing Skin
account.skin.type.csl_api.location=アドレス
account.skin.type.csl_api.location.hint=CustomSkinAPI
@@ -286,7 +284,6 @@ download.failed=%1$s のダウンロードに失敗しました、応答コー
download.failed.empty=候補者なし。戻るにはここをクリックしてください。
download.failed.refresh=バージョンリストをダウンロードできません。ここをクリックして再試行してください。
download.game=ゲームのダウンロード
download.provider.mcbbs=MCBBS(https://www.mcbbs.net/)
download.provider.bmclapi=BMCLAPI(ミラーソース)
download.provider.mojang=Mojang(OptiFineはBMCLAPIにより提供されます) 推奨
download.provider.official=公式ソースからロード
@@ -323,8 +320,6 @@ fatal.illegal_char=ユーザーフォルダーのパスに不正な文字'='が
feedback=フィードバック
feedback.discord=Discord
feedback.discord.statement=チャットに参加しよう!
feedback.kookapp=KOOK
feedback.kookapp.statement=チャットに参加しよう!
file=ファイル
@@ -337,7 +332,6 @@ folder.saves=マップ
folder.screenshots=スクリーンショット
game=ゲーム
#game.crash.feedback=問題は非常に一般的ですが、理由がわかりませんでしたか?フィードバックページでフィードバックをお寄せください。
game.crash.feedback=<b>このインターフェースのスクリーンショットを他の人と共有しないでください。 </b> 他の人に助けを求める場合は、左下隅にあるゲーム クラッシュ情報をクリックしてエクスポートし、エクスポートされたファイルを分析のために他の人に送信してください。
game.crash.info=ゲームステータス
game.crash.reason=クラッシュアナライザー
@@ -361,7 +355,7 @@ game.crash.reason.mod_profile_causes_game_crash=mod プロファイルに問題
game.crash.reason.fabric_reports_an_error_and_gives_a_solution=Forge がエラー メッセージを表示した可能性があります。 \nログを表示し、エラー レポートのログ情報に従って対応するアクションを実行できます。 \nエラー メッセージが表示されない場合は、エラー レポートをチェックして、エラーがどのように発生したかを知ることができます。
game.crash.reason.java_version_is_too_high=Java のバージョンが高すぎて実行を継続できないため、現在のゲームがクラッシュしました。 \nゲームを起動する前に、グローバル ゲーム設定またはゲーム固有の設定の Java パス タブで Java の以前のバージョンに切り替えてください。 \nそうでない場合は、<a href="https://www.java.com/download/">java.com (Java8)</a> または <a href="https://bell-sw.com/pages/downloads/#downloads">BellSoft Liberica Full JRE (Java17)</a> およびその他のプラットフォームをダウンロードしてインストールします (インストール後にランチャーを再起動します)。
game.crash.reason.mod_name=現在のゲームはModファイル名の問題で続行できません。\nModファイル名は、英文の全半角の大文字と小文字(Aa ~ Zz)、数字(0 ~ 9)、横線(-)、アンダースコア(_)、点(.)のみを使用してください。\n上記のコンプライアンス文字をModフォルダに追加してください。
game.crash.reason.incomplete_forge_installation=Forge / NeoForge のインストールが不完全なため、現在のゲームを続行できません。\nバージョン設定 - 自動インストールで Forge をアンインストールしてから再インストールしてください。
game.crash.reason.incomplete_forge_installation=Forge / NeoForge のインストールが不完全なため、現在のゲームを続行できません。\nバージョン設定 - 自動インストールで Forge / NeoForge をアンインストールしてから再インストールしてください。
game.crash.reason.file_already_exists=ファイル %1$s が既に存在するので、現在のゲームは続行できません。\このファイルを削除してもよいと思われる場合は、このファイルをバックアップして、ゲームを再起動してから削除してみてください。
game.crash.reason.need_jdk11=Java 仮想マシンのバージョンが不適切なため、現在のゲームは実行を続行できません。\nJava 11 をダウンロードしてインストールし、グローバル (特定) ゲーム設定で Java を 11 で始まるバージョンに設定する必要があります。
game.crash.reason.file_changed=ファイルのチェックサムに失敗したため、現在のゲームを続行できません。\nMinecraft.jar ファイルを手動で変更した場合、変更をロールバックするか、ゲームを再度ダウンロードする必要があります。
@@ -370,6 +364,7 @@ game.crash.reason.graphics_driver=現在、お使いのグラフィックカー
ゲームを起動する前に、グラフィックカードのドライバーを最新バージョンにアップグレードしてみてください。\n\
ディスクリートグラフィックカードを搭載している場合、統合型/コアグラフィックカードを使用してゲームが起動するかどうかを確認する必要があります。その場合、ディスクリートグラフィックカードを使用したゲームでHMCLを起動してみてください。それでも問題がある場合は、新しいグラフィックカードや新しいコンピュータの購入を検討する必要があるかもしれません。\n\
コアグラフィックカードが必要な場合は、お使いのパソコンのCPUがIntel(R) Core(TM) 3000シリーズ以上であることをご確認ください。その場合、Minecraft 1.16.5以前の場合は、ゲームに使用しているJavaバージョンを1.8.0_51以下にダウングレードするか、ディスクリートグラフィックカードを交換するか、または、新しい コンピュータを使用します。
game.crash.reason.macos_failed_to_find_service_port_for_display=現在のゲームは、Apple silicon プラットフォームでの OpenGL ウィンドウの初期化に失敗したため、続行できません。\nこの問題に対して、HMCL には直接的な解決策がありません。ブラウザを任意に開いてフルスクリーンにし、その後 HMCL に戻り、ゲームを起動し、ゲームウィンドウが表示される前<b>に素早くブラウザのページに戻ってください</b>。ゲームウィンドウが表示されたらゲームウィンドウに戻ってください。
game.crash.reason.illegal_access_error=一部のmodが原因でゲームがクラッシュしました。\n認識している場合:%1$s、modを更新または削除して、再試行してください。
game.crash.reason.install_mixinbootstrap=MixinBootstrapが見つからないため、現在のゲームを続行できません。\n<a href="https://www.curseforge.com/minecraft/mc-mods/mixinbootstrap">MixinBootstrap</a>をインストールしてみてください。インストール後にクラッシュする場合は、モジュールのファイル名の前に英語の「! をモジュールのファイル名の前につけてみてください。
game.crash.reason.jdk_9=Javaのバージョンが高すぎるため、ゲームを実行できません。\nJava 8をダウンロードしてインストールし、ゲーム設定で新しくインストールしたJavaを選択する必要があります。
@@ -873,7 +868,7 @@ update.channel.dev.title=ベータ版のヒント
update.channel.nightly=アルファ
update.channel.nightly.hint=アルファ版を使用しています。これには、ベータ版およびリリース版と比較していくつかの追加機能が含まれている可能性があり、テストにのみ使用されます。\n\
アルファ版は不安定です!\n\
問題が発生した場合は、<a href="hmcl://settings/feedback">フィードバックページ</a>にアクセスして報告するか、チャット <a href="https://discord.gg/jVvC7HfM6U">Discord</a>または<a href="https://kook.top/Kx7n3t">KOOK</a>で問題を報告します。
問題が発生した場合は、<a href="hmcl://settings/feedback">フィードバックページ</a>にアクセスして報告するか、チャット <a href="https://discord.gg/jVvC7HfM6U">Discord</a>で問題を報告します。
update.channel.nightly.title=アルファ版のヒント
update.channel.stable=リリース
update.checking=更新の確認
Loaded 100 of 212 files, more files were not shown because too many files have changed in this diff. Show more