Compare commits
64
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5abc1f1f9d | ||
|
|
40b4ecd42a | ||
|
|
e0f5c4d91e | ||
|
|
35ecd58460 | ||
|
|
f29c2bc202 | ||
|
|
361e928e73 | ||
|
|
484b65b513 | ||
|
|
19f02e3967 | ||
|
|
495da6b6b0 | ||
|
|
168c4ac708 | ||
|
|
7e8a6679cc | ||
|
|
e90d5185f6 | ||
|
|
8a89baee09 | ||
|
|
7f4c5fc4a0 | ||
|
|
75789c774d | ||
|
|
f0a16b3d14 | ||
|
|
710e69751f | ||
|
|
938d8647ce | ||
|
|
4b4dd592c3 | ||
|
|
225dba7448 | ||
|
|
5b1414428e | ||
|
|
cb467ae240 | ||
|
|
9416c0cfb3 | ||
|
|
16ea98c95a | ||
|
|
a8552a92d1 | ||
|
|
36d71bd14e | ||
|
|
cbe6554390 | ||
|
|
1b65e0b6d7 | ||
|
|
bd9ae189f8 | ||
|
|
867a04dfba | ||
|
|
dd7703c7a9 | ||
|
|
756b95ca8c | ||
|
|
ac0a0c7604 | ||
|
|
9ee611c0f6 | ||
|
|
c38371f454 | ||
|
|
eb3be8c465 | ||
|
|
e6ac07f62b | ||
|
|
a24fea4a95 | ||
|
|
8bd104c207 | ||
|
|
a14a265986 | ||
|
|
66524613b4 | ||
|
|
a0568e34a8 | ||
|
|
45f7719096 | ||
|
|
77fadf5d28 | ||
|
|
e9d7d0a33c | ||
|
|
94994ffdb6 | ||
|
|
26e274a89a | ||
|
|
6a497df0d1 | ||
|
|
fc1cce5e5a | ||
|
|
b328ed2cc9 | ||
|
|
5a8d567bd7 | ||
|
|
5005343d00 | ||
|
|
e4bc8f48c5 | ||
|
|
831a8a9bb8 | ||
|
|
7c024473d6 | ||
|
|
f594abc37d | ||
|
|
b66e249591 | ||
|
|
15e490f313 | ||
|
|
7ce59cac2c | ||
|
|
a7178802f8 | ||
|
|
51954163d4 | ||
|
|
19072bd617 | ||
|
|
a39a23f938 | ||
|
|
a10e9a04b1 |
No files matched your search
@@ -1071,6 +1071,7 @@ ij_jsp_keep_indents_on_empty_lines = false
|
||||
ij_jspx_keep_indents_on_empty_lines = false
|
||||
|
||||
[{*.markdown,*.md}]
|
||||
max_line_length = 200
|
||||
ij_markdown_force_one_space_after_blockquote_symbol = true
|
||||
ij_markdown_force_one_space_after_header_symbol = true
|
||||
ij_markdown_force_one_space_after_list_bullet = true
|
||||
|
||||
@@ -13,26 +13,16 @@ jobs:
|
||||
if: ${{ github.repository_owner == 'HMCL-dev' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '25'
|
||||
- name: Fetch tags
|
||||
run: git fetch --all --tags
|
||||
- name: Install tools
|
||||
run: sudo apt-get install -y jq
|
||||
- name: Fetch last version
|
||||
run: |
|
||||
wget -O ci.json https://ci.huangyuhui.net/job/HMCL/lastSuccessfulBuild/api/json
|
||||
|
||||
export HMCL_EXE_FILE_NAME=`cat ci.json | jq -M -r '.artifacts[] | select(.fileName | endswith(".exe")) | .fileName'`
|
||||
if [ -z `echo $HMCL_EXE_FILE_NAME | grep -E "^HMCL-[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?\.exe\$"` ]; then exit 1; fi
|
||||
|
||||
export HMCL_VERSION=`echo "${HMCL_EXE_FILE_NAME%.exe}" | tail -c +6`
|
||||
export HMCL_COMMIT_SHA=`cat ci.json | jq -M -r '.actions[] | select(._class == "hudson.plugins.git.util.BuildData") | .lastBuiltRevision.SHA1'`
|
||||
|
||||
if [ "${#HMCL_COMMIT_SHA}" != 40 ]; then exit 1; fi
|
||||
|
||||
echo "HMCL_VERSION=$HMCL_VERSION" >> $GITHUB_ENV
|
||||
echo "HMCL_COMMIT_SHA=$HMCL_COMMIT_SHA" >> $GITHUB_ENV
|
||||
echo "HMCL_TAG_NAME=v$HMCL_VERSION" >> $GITHUB_ENV
|
||||
run: ./gradlew checkUpdateDev --no-daemon --info --stacktrace
|
||||
- name: Check for existing tags
|
||||
run: if [ -z "$(git tag -l "$HMCL_TAG_NAME")" ]; then echo "continue=true" >> $GITHUB_ENV; fi
|
||||
- name: Download artifacts
|
||||
@@ -78,26 +68,17 @@ jobs:
|
||||
needs: dev-check-update
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v5
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '25'
|
||||
- name: Fetch tags
|
||||
run: git fetch --all --tags
|
||||
- name: Install tools
|
||||
run: sudo apt-get install -y jq
|
||||
|
||||
- name: Fetch last version
|
||||
run: |
|
||||
wget -O ci.json https://ci.huangyuhui.net/job/HMCL-stable/lastSuccessfulBuild/api/json
|
||||
|
||||
export HMCL_EXE_FILE_NAME=`cat ci.json | jq -M -r '.artifacts[] | select(.fileName | endswith(".exe")) | .fileName'`
|
||||
if [ -z `echo $HMCL_EXE_FILE_NAME | grep -E "^HMCL-[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?\.exe\$"` ]; then exit 1; fi
|
||||
|
||||
export HMCL_VERSION=`echo "${HMCL_EXE_FILE_NAME%.exe}" | tail -c +6`
|
||||
export HMCL_COMMIT_SHA=`cat ci.json | jq -M -r '.actions[] | select(._class == "hudson.plugins.git.util.BuildData") | .lastBuiltRevision.SHA1'`
|
||||
|
||||
if [ "${#HMCL_COMMIT_SHA}" != 40 ]; then exit 1; fi
|
||||
|
||||
echo "HMCL_VERSION=$HMCL_VERSION" >> $GITHUB_ENV
|
||||
echo "HMCL_COMMIT_SHA=$HMCL_COMMIT_SHA" >> $GITHUB_ENV
|
||||
echo "HMCL_TAG_NAME=release-$HMCL_VERSION" >> $GITHUB_ENV
|
||||
run: ./gradlew checkUpdateStable --no-daemon --info --stacktrace
|
||||
- name: Check for existing tags
|
||||
run: if ! git tag -l | grep -q "$HMCL_TAG_NAME"; then echo "continue=true" >> $GITHUB_ENV; fi
|
||||
- name: Download artifacts
|
||||
|
||||
@@ -27,8 +27,24 @@ jobs:
|
||||
CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }}
|
||||
- name: Get short SHA
|
||||
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
- name: Upload Artifacts
|
||||
- name: Upload JAR
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: HMCL-${{ env.SHORT_SHA }}
|
||||
path: HMCL/build/libs
|
||||
name: HMCL-${{ env.SHORT_SHA }}-jar
|
||||
path: |
|
||||
HMCL/build/libs/HMCL-*.jar
|
||||
HMCL/build/libs/HMCL-*.jar.sha256
|
||||
- name: Upload EXE
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: HMCL-${{ env.SHORT_SHA }}-exe
|
||||
path: |
|
||||
HMCL/build/libs/HMCL-*.exe
|
||||
HMCL/build/libs/HMCL-*.exe.sha256
|
||||
- name: Upload SH
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: HMCL-${{ env.SHORT_SHA }}-sh
|
||||
path: |
|
||||
HMCL/build/libs/HMCL-*.sh
|
||||
HMCL/build/libs/HMCL-*.sh.sha256
|
||||
+5
-1
@@ -2,6 +2,7 @@
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
*.hprof
|
||||
|
||||
.gradle
|
||||
|
||||
@@ -14,14 +15,15 @@ hs_err_pid*
|
||||
NVIDIA
|
||||
minecraft-exported-crash-info*
|
||||
hmcl-exported-logs-*
|
||||
/.java/
|
||||
|
||||
# gradle build
|
||||
/build/
|
||||
/HMCL/build/
|
||||
/HMCLCore/build/
|
||||
/HMCLBoot/build/
|
||||
/HMCLTransformerDiscoveryService/build/
|
||||
/minecraft/libraries/HMCLTransformerDiscoveryService/build/
|
||||
/minecraft/libraries/HMCLMultiMCBootstrap/build/
|
||||
/buildSrc/build/
|
||||
|
||||
# idea
|
||||
@@ -30,12 +32,14 @@ hmcl-exported-logs-*
|
||||
/HMCL/out/
|
||||
/HMCLCore/out/
|
||||
/minecraft/libraries/HMCLTransformerDiscoveryService/out/
|
||||
/minecraft/libraries/HMCLMultiMCBootstrap/out/
|
||||
|
||||
# eclipse
|
||||
/bin/
|
||||
/HMCL/bin/
|
||||
/HMCLCore/bin/
|
||||
/minecraft/libraries/HMCLTransformerDiscoveryService/bin/
|
||||
/minecraft/libraries/HMCLMultiMCBootstrap/bin/
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
|
||||
+63
-19
@@ -1,5 +1,11 @@
|
||||
import org.jackhuang.hmcl.gradle.CheckTranslations
|
||||
import org.jackhuang.hmcl.gradle.ci.GitHubActionUtils
|
||||
import org.jackhuang.hmcl.gradle.ci.JenkinsUtils
|
||||
import org.jackhuang.hmcl.gradle.l10n.CheckTranslations
|
||||
import org.jackhuang.hmcl.gradle.l10n.CreateLanguageList
|
||||
import org.jackhuang.hmcl.gradle.l10n.CreateLocaleNamesResourceBundle
|
||||
import org.jackhuang.hmcl.gradle.l10n.UpsideDownTranslate
|
||||
import org.jackhuang.hmcl.gradle.mod.ParseModDataTask
|
||||
import org.jackhuang.hmcl.gradle.utils.PropertiesUtils
|
||||
import java.net.URI
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
@@ -13,30 +19,36 @@ plugins {
|
||||
alias(libs.plugins.shadow)
|
||||
}
|
||||
|
||||
val isOfficial = System.getenv("HMCL_SIGNATURE_KEY") != null
|
||||
|| (System.getenv("GITHUB_REPOSITORY_OWNER") == "HMCL-dev" && System.getenv("GITHUB_BASE_REF")
|
||||
.isNullOrEmpty())
|
||||
val projectConfig = PropertiesUtils.load(rootProject.file("config/project.properties").toPath())
|
||||
|
||||
val isOfficial = JenkinsUtils.IS_ON_CI || GitHubActionUtils.IS_ON_OFFICIAL_REPO
|
||||
|
||||
val buildNumber = System.getenv("BUILD_NUMBER")?.toInt().let { number ->
|
||||
val offset = System.getenv("BUILD_NUMBER_OFFSET")?.toInt() ?: 0
|
||||
if (number != null) {
|
||||
(number - offset).toString()
|
||||
} else {
|
||||
val shortCommit = System.getenv("GITHUB_SHA")?.lowercase()?.substring(0, 7)
|
||||
val prefix = if (isOfficial) "dev" else "unofficial"
|
||||
if (!shortCommit.isNullOrEmpty()) "$prefix-$shortCommit" else "SNAPSHOT"
|
||||
}
|
||||
}
|
||||
val versionRoot = System.getenv("VERSION_ROOT") ?: "3.6"
|
||||
val versionType = System.getenv("VERSION_TYPE") ?: if (isOfficial) "nightly" else "unofficial"
|
||||
val versionRoot = System.getenv("VERSION_ROOT") ?: projectConfig.getProperty("versionRoot") ?: "3"
|
||||
|
||||
val microsoftAuthId = System.getenv("MICROSOFT_AUTH_ID") ?: ""
|
||||
val microsoftAuthSecret = System.getenv("MICROSOFT_AUTH_SECRET") ?: ""
|
||||
val curseForgeApiKey = System.getenv("CURSEFORGE_API_KEY") ?: ""
|
||||
|
||||
val launcherExe = System.getenv("HMCL_LAUNCHER_EXE")
|
||||
val launcherExe = System.getenv("HMCL_LAUNCHER_EXE") ?: ""
|
||||
|
||||
version = "$versionRoot.$buildNumber"
|
||||
val buildNumber = System.getenv("BUILD_NUMBER")?.toInt()
|
||||
if (buildNumber != null) {
|
||||
version = if (JenkinsUtils.IS_ON_CI && versionType == "dev") {
|
||||
"$versionRoot.0.$buildNumber"
|
||||
} else {
|
||||
"$versionRoot.$buildNumber"
|
||||
}
|
||||
} else {
|
||||
val shortCommit = System.getenv("GITHUB_SHA")?.lowercase()?.substring(0, 7)
|
||||
version = if (shortCommit.isNullOrBlank()) {
|
||||
"$versionRoot.SNAPSHOT"
|
||||
} else if (isOfficial) {
|
||||
"$versionRoot.dev-$shortCommit"
|
||||
} else {
|
||||
"$versionRoot.unofficial-$shortCommit"
|
||||
}
|
||||
}
|
||||
|
||||
val embedResources by configurations.registering
|
||||
|
||||
@@ -47,7 +59,7 @@ dependencies {
|
||||
implementation(libs.twelvemonkeys.imageio.webp)
|
||||
implementation(libs.java.info)
|
||||
|
||||
if (launcherExe == null) {
|
||||
if (launcherExe.isBlank()) {
|
||||
implementation(libs.hmclauncher)
|
||||
}
|
||||
|
||||
@@ -192,7 +204,7 @@ tasks.shadowJar {
|
||||
"Enable-Native-Access" to "ALL-UNNAMED"
|
||||
)
|
||||
|
||||
if (launcherExe != null) {
|
||||
if (launcherExe.isNotBlank()) {
|
||||
into("assets") {
|
||||
from(file(launcherExe))
|
||||
}
|
||||
@@ -206,11 +218,20 @@ tasks.shadowJar {
|
||||
|
||||
tasks.processResources {
|
||||
dependsOn(createPropertiesFile)
|
||||
dependsOn(upsideDownTranslate)
|
||||
dependsOn(createLocaleNamesResourceBundle)
|
||||
dependsOn(createLanguageList)
|
||||
|
||||
into("assets/") {
|
||||
from(hmclPropertiesFile)
|
||||
from(embedResources)
|
||||
}
|
||||
|
||||
into("assets/lang") {
|
||||
from(createLanguageList.map { it.outputFile })
|
||||
from(upsideDownTranslate.map { it.outputFile })
|
||||
from(createLocaleNamesResourceBundle.map { it.outputDirectory })
|
||||
}
|
||||
}
|
||||
|
||||
val makeExecutables by tasks.registering {
|
||||
@@ -344,6 +365,29 @@ tasks.register<CheckTranslations>("checkTranslations") {
|
||||
classicalChineseFile.set(dir.file("I18N_lzh.properties"))
|
||||
}
|
||||
|
||||
// l10n
|
||||
|
||||
val generatedDir = layout.buildDirectory.dir("generated")
|
||||
|
||||
val upsideDownTranslate by tasks.registering(UpsideDownTranslate::class) {
|
||||
inputFile.set(layout.projectDirectory.file("src/main/resources/assets/lang/I18N.properties"))
|
||||
outputFile.set(generatedDir.map { it.file("generated/i18n/I18N_en_Qabs.properties") })
|
||||
}
|
||||
|
||||
val createLanguageList by tasks.registering(CreateLanguageList::class) {
|
||||
resourceBundleDir.set(layout.projectDirectory.dir("src/main/resources/assets/lang"))
|
||||
resourceBundleBaseName.set("I18N")
|
||||
additionalLanguages.set(listOf("en-Qabs"))
|
||||
outputFile.set(generatedDir.map { it.file("languages.json") })
|
||||
}
|
||||
|
||||
val createLocaleNamesResourceBundle by tasks.registering(CreateLocaleNamesResourceBundle::class) {
|
||||
dependsOn(createLanguageList)
|
||||
|
||||
languagesFile.set(createLanguageList.flatMap { it.outputFile })
|
||||
outputDirectory.set(generatedDir.map { it.dir("generated/LocaleNames") })
|
||||
}
|
||||
|
||||
// mcmod data
|
||||
|
||||
tasks.register<ParseModDataTask>("parseModData") {
|
||||
|
||||
@@ -46,21 +46,7 @@ public final class EntryPoint {
|
||||
createHMCLDirectories();
|
||||
LOG.start(Metadata.HMCL_CURRENT_DIRECTORY.resolve("logs"));
|
||||
|
||||
if ("true".equalsIgnoreCase(System.getenv("HMCL_FORCE_GPU")))
|
||||
System.getProperties().putIfAbsent("prism.forceGPU", "true");
|
||||
|
||||
String animationFrameRate = System.getenv("HMCL_ANIMATION_FRAME_RATE");
|
||||
if (animationFrameRate != null) {
|
||||
try {
|
||||
if (Integer.parseInt(animationFrameRate) <= 0)
|
||||
throw new NumberFormatException(animationFrameRate);
|
||||
|
||||
System.getProperties().putIfAbsent("javafx.animation.pulse", animationFrameRate);
|
||||
} catch (NumberFormatException e) {
|
||||
LOG.warning("Invalid animation frame rate: " + animationFrameRate);
|
||||
}
|
||||
}
|
||||
|
||||
setupJavaFXVMOptions();
|
||||
checkDirectoryPath();
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)
|
||||
@@ -80,6 +66,71 @@ public final class EntryPoint {
|
||||
System.exit(exitCode);
|
||||
}
|
||||
|
||||
private static void setupJavaFXVMOptions() {
|
||||
if ("true".equalsIgnoreCase(System.getenv("HMCL_FORCE_GPU"))) {
|
||||
LOG.info("HMCL_FORCE_GPU: true");
|
||||
System.getProperties().putIfAbsent("prism.forceGPU", "true");
|
||||
}
|
||||
|
||||
String animationFrameRate = System.getenv("HMCL_ANIMATION_FRAME_RATE");
|
||||
if (animationFrameRate != null) {
|
||||
LOG.info("HMCL_ANIMATION_FRAME_RATE: " + animationFrameRate);
|
||||
|
||||
try {
|
||||
if (Integer.parseInt(animationFrameRate) <= 0)
|
||||
throw new NumberFormatException(animationFrameRate);
|
||||
|
||||
System.getProperties().putIfAbsent("javafx.animation.pulse", animationFrameRate);
|
||||
} catch (NumberFormatException e) {
|
||||
LOG.warning("Invalid animation frame rate: " + animationFrameRate);
|
||||
}
|
||||
}
|
||||
|
||||
String uiScale = System.getProperty("hmcl.uiScale", System.getenv("HMCL_UI_SCALE"));
|
||||
if (uiScale != null) {
|
||||
uiScale = uiScale.trim();
|
||||
|
||||
LOG.info("HMCL_UI_SCALE: " + uiScale);
|
||||
|
||||
try {
|
||||
float scaleValue;
|
||||
if (uiScale.endsWith("%")) {
|
||||
scaleValue = Integer.parseInt(uiScale.substring(0, uiScale.length() - 1)) / 100.0f;
|
||||
} else if (uiScale.endsWith("dpi") || uiScale.endsWith("DPI")) {
|
||||
scaleValue = Integer.parseInt(uiScale.substring(0, uiScale.length() - 3)) / 96.0f;
|
||||
} else {
|
||||
scaleValue = Float.parseFloat(uiScale);
|
||||
}
|
||||
|
||||
float lowerBound;
|
||||
float upperBound;
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
// JavaFX behavior may be abnormal when the DPI scaling factor is too high
|
||||
lowerBound = 0.25f;
|
||||
upperBound = 4f;
|
||||
} else {
|
||||
lowerBound = 0.01f;
|
||||
upperBound = 10f;
|
||||
}
|
||||
|
||||
if (scaleValue >= lowerBound && scaleValue <= upperBound) {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
System.getProperties().putIfAbsent("glass.win.uiScale", uiScale);
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
LOG.warning("macOS does not support setting UI scale, so it will be ignored");
|
||||
} else {
|
||||
System.getProperties().putIfAbsent("glass.gtk.uiScale", uiScale);
|
||||
}
|
||||
} else {
|
||||
LOG.warning("UI scale out of range: " + uiScale);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Invalid UI scale: " + uiScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void createHMCLDirectories() {
|
||||
if (!Files.isDirectory(Metadata.HMCL_CURRENT_DIRECTORY)) {
|
||||
try {
|
||||
|
||||
@@ -75,13 +75,7 @@ public final class Launcher extends Application {
|
||||
CookieHandler.setDefault(COOKIE_MANAGER);
|
||||
|
||||
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.warning("Failed to get prism pipeline", e);
|
||||
}
|
||||
|
||||
LOG.info("Prism Pipeline: " + FXUtils.GRAPHICS_PIPELINE);
|
||||
LOG.info("Dark Mode: " + Optional.ofNullable(FXUtils.DARK_MODE).map(ObservableBooleanValue::get).orElse(false));
|
||||
LOG.info("Reduced Motion: " + Objects.requireNonNullElse(FXUtils.REDUCED_MOTION, false));
|
||||
|
||||
@@ -243,8 +237,8 @@ public final class Launcher extends Application {
|
||||
try {
|
||||
LOG.info("*** " + Metadata.TITLE + " ***");
|
||||
LOG.info("Operating System: " + (OperatingSystem.OS_RELEASE_PRETTY_NAME == null
|
||||
? OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION
|
||||
: OperatingSystem.OS_RELEASE_PRETTY_NAME + " (" + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION + ')'));
|
||||
? OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION.getVersion()
|
||||
: OperatingSystem.OS_RELEASE_PRETTY_NAME + " (" + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION.getVersion() + ')'));
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
LOG.info("Processor Identifier: " + System.getenv("PROCESSOR_IDENTIFIER"));
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class CrashReport {
|
||||
"\n Content: \n " +
|
||||
stackTrace + "\n\n" +
|
||||
"-- System Details --\n" +
|
||||
" Operating System: " + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION + "\n" +
|
||||
" Operating System: " + OperatingSystem.SYSTEM_NAME + ' ' + OperatingSystem.SYSTEM_VERSION.getVersion() + "\n" +
|
||||
" System Architecture: " + Architecture.SYSTEM_ARCH.getDisplayName() + "\n" +
|
||||
" Java Architecture: " + Architecture.CURRENT_ARCH.getDisplayName() + "\n" +
|
||||
" Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor") + "\n" +
|
||||
|
||||
@@ -26,7 +26,6 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.ManagedProcess;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
@@ -64,7 +63,7 @@ public final class HMCLGameLauncher extends DefaultLauncher {
|
||||
if (config().isDisableAutoGameOptions())
|
||||
return;
|
||||
|
||||
Path runDir = repository.getRunDirectory(version.getId()).toPath();
|
||||
Path runDir = repository.getRunDirectory(version.getId());
|
||||
Path optionsFile = runDir.resolve("options.txt");
|
||||
Path configFolder = runDir.resolve("config");
|
||||
|
||||
@@ -81,8 +80,6 @@ public final class HMCLGameLauncher extends DefaultLauncher {
|
||||
}
|
||||
|
||||
Locale locale = Locale.getDefault();
|
||||
if (LocaleUtils.isEnglish(locale))
|
||||
return;
|
||||
|
||||
/*
|
||||
* 1.0 : No language option, do not set for these versions
|
||||
@@ -113,7 +110,7 @@ public final class HMCLGameLauncher extends DefaultLauncher {
|
||||
private static String normalizedLanguageTag(Locale locale, GameVersionNumber gameVersion) {
|
||||
String region = locale.getCountry();
|
||||
|
||||
return switch (LocaleUtils.getISO1Language(locale)) {
|
||||
return switch (LocaleUtils.getRootLanguage(locale)) {
|
||||
case "es" -> "es_ES";
|
||||
case "ja" -> "ja_JP";
|
||||
case "ru" -> "ru_RU";
|
||||
@@ -130,6 +127,13 @@ public final class HMCLGameLauncher extends DefaultLauncher {
|
||||
}
|
||||
yield "zh_CN";
|
||||
}
|
||||
case "en" -> {
|
||||
if ("Qabs".equals(LocaleUtils.getScript(locale)) && gameVersion.compareTo("1.16") >= 0) {
|
||||
yield "en_UD";
|
||||
}
|
||||
|
||||
yield "";
|
||||
}
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
@@ -141,7 +145,7 @@ public final class HMCLGameLauncher extends DefaultLauncher {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void makeLaunchScript(File scriptFile) throws IOException {
|
||||
public void makeLaunchScript(Path scriptFile) throws IOException {
|
||||
generateOptionsTxt();
|
||||
super.makeLaunchScript(scriptFile);
|
||||
}
|
||||
|
||||
@@ -44,9 +44,9 @@ import org.jackhuang.hmcl.util.platform.SystemInfo;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
@@ -57,7 +57,7 @@ import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public class HMCLGameRepository extends DefaultGameRepository {
|
||||
public final class HMCLGameRepository extends DefaultGameRepository {
|
||||
private final Profile profile;
|
||||
|
||||
// local version settings
|
||||
@@ -66,7 +66,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
|
||||
public final EventManager<Event> onVersionIconChanged = new EventManager<>();
|
||||
|
||||
public HMCLGameRepository(Profile profile, File baseDirectory) {
|
||||
public HMCLGameRepository(Profile profile, Path baseDirectory) {
|
||||
super(baseDirectory);
|
||||
this.profile = profile;
|
||||
}
|
||||
@@ -85,18 +85,20 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getRunDirectory(String id) {
|
||||
public Path getRunDirectory(String id) {
|
||||
switch (getGameDirectoryType(id)) {
|
||||
case VERSION_FOLDER:
|
||||
return getVersionRoot(id);
|
||||
case ROOT_FOLDER:
|
||||
return super.getRunDirectory(id);
|
||||
case CUSTOM:
|
||||
File dir = new File(getVersionSetting(id).getGameDir());
|
||||
if (!FileUtils.isValidPath(dir)) return getVersionRoot(id);
|
||||
return dir;
|
||||
try {
|
||||
return Path.of(getVersionSetting(id).getGameDir());
|
||||
} catch (InvalidPathException ignored) {
|
||||
return getVersionRoot(id);
|
||||
}
|
||||
default:
|
||||
throw new Error();
|
||||
throw new AssertionError("Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,22 +121,24 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
});
|
||||
|
||||
try {
|
||||
File file = new File(getBaseDirectory(), "launcher_profiles.json");
|
||||
if (!file.exists() && !versions.isEmpty())
|
||||
FileUtils.writeText(file, PROFILE);
|
||||
Path file = getBaseDirectory().resolve("launcher_profiles.json");
|
||||
if (!Files.exists(file) && !versions.isEmpty()) {
|
||||
Files.createDirectories(file.getParent());
|
||||
Files.writeString(file, PROFILE);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
LOG.warning("Unable to create launcher_profiles.json, Forge/LiteLoader installer will not work.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void changeDirectory(File newDirectory) {
|
||||
public void changeDirectory(Path newDirectory) {
|
||||
setBaseDirectory(newDirectory);
|
||||
refreshVersionsAsync().start();
|
||||
}
|
||||
|
||||
private void clean(File directory) throws IOException {
|
||||
FileUtils.deleteDirectory(new File(directory, "crash-reports"));
|
||||
FileUtils.deleteDirectory(new File(directory, "logs"));
|
||||
private void clean(Path directory) throws IOException {
|
||||
FileUtils.deleteDirectory(directory.resolve("crash-reports"));
|
||||
FileUtils.deleteDirectory(directory.resolve("logs"));
|
||||
}
|
||||
|
||||
public void clean(String id) throws IOException {
|
||||
@@ -143,8 +147,8 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
}
|
||||
|
||||
public void duplicateVersion(String srcId, String dstId, boolean copySaves) throws IOException {
|
||||
Path srcDir = getVersionRoot(srcId).toPath();
|
||||
Path dstDir = getVersionRoot(dstId).toPath();
|
||||
Path srcDir = getVersionRoot(srcId);
|
||||
Path dstDir = getVersionRoot(dstId);
|
||||
|
||||
Version fromVersion = getVersion(srcId);
|
||||
|
||||
@@ -178,22 +182,22 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
VersionSetting newVersionSetting = initLocalVersionSetting(dstId, oldVersionSetting);
|
||||
saveVersionSetting(dstId);
|
||||
|
||||
File srcGameDir = getRunDirectory(srcId);
|
||||
File dstGameDir = getRunDirectory(dstId);
|
||||
Path srcGameDir = getRunDirectory(srcId);
|
||||
Path dstGameDir = getRunDirectory(dstId);
|
||||
|
||||
if (originalGameDirType != GameDirectoryType.VERSION_FOLDER)
|
||||
FileUtils.copyDirectory(srcGameDir.toPath(), dstGameDir.toPath(), path -> Modpack.acceptFile(path, blackList, null));
|
||||
FileUtils.copyDirectory(srcGameDir, dstGameDir, path -> Modpack.acceptFile(path, blackList, null));
|
||||
}
|
||||
|
||||
private File getLocalVersionSettingFile(String id) {
|
||||
return new File(getVersionRoot(id), "hmclversion.cfg");
|
||||
private Path getLocalVersionSettingFile(String id) {
|
||||
return getVersionRoot(id).resolve("hmclversion.cfg");
|
||||
}
|
||||
|
||||
private void loadLocalVersionSetting(String id) {
|
||||
File file = getLocalVersionSettingFile(id);
|
||||
if (file.exists())
|
||||
Path file = getLocalVersionSettingFile(id);
|
||||
if (Files.exists(file))
|
||||
try {
|
||||
VersionSetting versionSetting = GSON.fromJson(Files.readString(file.toPath()), VersionSetting.class);
|
||||
VersionSetting versionSetting = JsonUtils.fromJsonFile(file, VersionSetting.class);
|
||||
initLocalVersionSetting(id, versionSetting);
|
||||
} catch (Exception ex) {
|
||||
// If [JsonParseException], [IOException] or [NullPointerException] happens, the json file is malformed and needed to be recreated.
|
||||
@@ -256,12 +260,12 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
return vs;
|
||||
}
|
||||
|
||||
public Optional<File> getVersionIconFile(String id) {
|
||||
File root = getVersionRoot(id);
|
||||
public Optional<Path> getVersionIconFile(String id) {
|
||||
Path root = getVersionRoot(id);
|
||||
|
||||
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
|
||||
File file = new File(root, "icon." + extension);
|
||||
if (file.exists()) {
|
||||
Path file = root.resolve("icon." + extension);
|
||||
if (Files.exists(file)) {
|
||||
return Optional.of(file);
|
||||
}
|
||||
}
|
||||
@@ -269,7 +273,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public void setVersionIconFile(String id, File iconFile) throws IOException {
|
||||
public void setVersionIconFile(String id, Path iconFile) throws IOException {
|
||||
String ext = FileUtils.getExtension(iconFile).toLowerCase(Locale.ROOT);
|
||||
if (!FXUtils.IMAGE_EXTENSIONS.contains(ext)) {
|
||||
throw new IllegalArgumentException("Unsupported icon file: " + ext);
|
||||
@@ -277,13 +281,18 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
|
||||
deleteIconFile(id);
|
||||
|
||||
FileUtils.copyFile(iconFile, new File(getVersionRoot(id), "icon." + ext));
|
||||
FileUtils.copyFile(iconFile, getVersionRoot(id).resolve("icon." + ext));
|
||||
}
|
||||
|
||||
public void deleteIconFile(String id) {
|
||||
File root = getVersionRoot(id);
|
||||
Path root = getVersionRoot(id);
|
||||
for (String extension : FXUtils.IMAGE_EXTENSIONS) {
|
||||
new File(root, "icon." + extension).delete();
|
||||
Path file = root.resolve("icon." + extension);
|
||||
try {
|
||||
Files.deleteIfExists(file);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to delete icon file: " + file, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,10 +305,10 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
|
||||
if (iconType == VersionIconType.DEFAULT) {
|
||||
Version version = getVersion(id).resolve(this);
|
||||
Optional<File> iconFile = getVersionIconFile(id);
|
||||
Optional<Path> iconFile = getVersionIconFile(id);
|
||||
if (iconFile.isPresent()) {
|
||||
try {
|
||||
return FXUtils.loadImage(iconFile.get().toPath());
|
||||
return FXUtils.loadImage(iconFile.get());
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Failed to load version icon of " + id, e);
|
||||
}
|
||||
@@ -334,7 +343,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
public void saveVersionSetting(String id) {
|
||||
if (!localVersionSettings.containsKey(id))
|
||||
return;
|
||||
Path file = getLocalVersionSettingFile(id).toPath().toAbsolutePath().normalize();
|
||||
Path file = getLocalVersionSettingFile(id).toAbsolutePath().normalize();
|
||||
try {
|
||||
Files.createDirectories(file.getParent());
|
||||
} catch (IOException e) {
|
||||
@@ -368,7 +377,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
vs.setUsesGlobal(true);
|
||||
}
|
||||
|
||||
public LaunchOptions getLaunchOptions(String version, JavaRuntime javaVersion, File gameDir, List<String> javaAgents, List<String> javaArguments, boolean makeLaunchScript) {
|
||||
public LaunchOptions getLaunchOptions(String version, JavaRuntime javaVersion, Path gameDir, List<String> javaAgents, List<String> javaArguments, boolean makeLaunchScript) {
|
||||
VersionSetting vs = getVersionSetting(version);
|
||||
|
||||
LaunchOptions.Builder builder = new LaunchOptions.Builder()
|
||||
@@ -379,7 +388,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
.setProfileName(Metadata.TITLE)
|
||||
.setGameArguments(StringUtils.tokenize(vs.getMinecraftArgs()))
|
||||
.setOverrideJavaArguments(StringUtils.tokenize(vs.getJavaArgs()))
|
||||
.setMaxMemory(vs.isNoJVMArgs() && vs.isAutoMemory() ? null : (int)(getAllocatedMemory(
|
||||
.setMaxMemory(vs.isNoJVMArgs() && vs.isAutoMemory() ? null : (int) (getAllocatedMemory(
|
||||
vs.getMaxMemory() * 1024L * 1024L,
|
||||
SystemInfo.getPhysicalMemoryStatus().getAvailable(),
|
||||
vs.isAutoMemory()
|
||||
@@ -404,6 +413,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
.setPreLaunchCommand(vs.getPreLaunchCommand())
|
||||
.setPostExitCommand(vs.getPostExitCommand())
|
||||
.setNoGeneratedJVMArgs(vs.isNoJVMArgs())
|
||||
.setNoGeneratedOptimizingJVMArgs(vs.isNoOptimizingJVMArgs())
|
||||
.setNativesDirType(vs.getNativesDirType())
|
||||
.setNativesDir(vs.getNativesDir())
|
||||
.setProcessPriority(vs.getProcessPriority())
|
||||
@@ -425,15 +435,15 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
}
|
||||
}
|
||||
|
||||
File json = getModpackConfiguration(version);
|
||||
if (json.exists()) {
|
||||
Path json = getModpackConfiguration(version);
|
||||
if (Files.exists(json)) {
|
||||
try {
|
||||
String jsonText = Files.readString(json.toPath());
|
||||
String jsonText = Files.readString(json);
|
||||
ModpackConfiguration<?> modpackConfiguration = JsonUtils.GSON.fromJson(jsonText, ModpackConfiguration.class);
|
||||
ModpackProvider provider = ModpackHelper.getProviderByType(modpackConfiguration.getType());
|
||||
if (provider != null) provider.injectLaunchOptions(jsonText, builder);
|
||||
} catch (IOException | JsonParseException e) {
|
||||
e.printStackTrace();
|
||||
LOG.warning("Failed to parse modpack configuration file " + json, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,8 +454,8 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getModpackConfiguration(String version) {
|
||||
return new File(getVersionRoot(version), "modpack.cfg");
|
||||
public Path getModpackConfiguration(String version) {
|
||||
return getVersionRoot(version).resolve("modpack.cfg");
|
||||
}
|
||||
|
||||
public void markVersionAsModpack(String id) {
|
||||
@@ -458,16 +468,24 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
|
||||
public void markVersionLaunchedAbnormally(String id) {
|
||||
try {
|
||||
Files.createFile(getVersionRoot(id).toPath().resolve(".abnormal"));
|
||||
Files.createFile(getVersionRoot(id).resolve(".abnormal"));
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean unmarkVersionLaunchedAbnormally(String id) {
|
||||
File file = new File(getVersionRoot(id), ".abnormal");
|
||||
boolean result = file.isFile();
|
||||
file.delete();
|
||||
return result;
|
||||
Path file = getVersionRoot(id).resolve(".abnormal");
|
||||
if (Files.isRegularFile(file)) {
|
||||
try {
|
||||
Files.delete(file);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to delete abnormal mark file: " + file, e);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
@@ -489,7 +507,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
FORBIDDEN_VERSION_IDS.contains(id.toLowerCase(Locale.ROOT)))
|
||||
return false;
|
||||
|
||||
return OperatingSystem.isNameValid(id);
|
||||
return FileUtils.isNameValid(id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,14 +29,15 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class HMCLModpackInstallTask extends Task<Void> {
|
||||
private final File zipFile;
|
||||
private final Path zipFile;
|
||||
private final String name;
|
||||
private final HMCLGameRepository repository;
|
||||
private final DefaultDependencyManager dependency;
|
||||
@@ -44,16 +45,16 @@ public final class HMCLModpackInstallTask extends Task<Void> {
|
||||
private final List<Task<?>> dependencies = new ArrayList<>(1);
|
||||
private final List<Task<?>> dependents = new ArrayList<>(4);
|
||||
|
||||
public HMCLModpackInstallTask(Profile profile, File zipFile, Modpack modpack, String name) {
|
||||
public HMCLModpackInstallTask(Profile profile, Path zipFile, Modpack modpack, String name) {
|
||||
dependency = profile.getDependency();
|
||||
repository = profile.getRepository();
|
||||
this.zipFile = zipFile;
|
||||
this.name = name;
|
||||
this.modpack = modpack;
|
||||
|
||||
File run = repository.getRunDirectory(name);
|
||||
File json = repository.getModpackConfiguration(name);
|
||||
if (repository.hasVersion(name) && !json.exists())
|
||||
Path run = repository.getRunDirectory(name);
|
||||
Path json = repository.getModpackConfiguration(name);
|
||||
if (repository.hasVersion(name) && Files.notExists(json))
|
||||
throw new IllegalArgumentException("Version " + name + " already exists");
|
||||
|
||||
dependents.add(dependency.gameBuilder().name(name).gameVersion(modpack.getGameVersion()).buildAsync());
|
||||
@@ -64,8 +65,8 @@ public final class HMCLModpackInstallTask extends Task<Void> {
|
||||
|
||||
ModpackConfiguration<Modpack> config = null;
|
||||
try {
|
||||
if (json.exists()) {
|
||||
config = JsonUtils.fromJsonFile(json.toPath(), ModpackConfiguration.typeOf(Modpack.class));
|
||||
if (Files.exists(json)) {
|
||||
config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(Modpack.class));
|
||||
|
||||
if (!HMCLModpackProvider.INSTANCE.getName().equals(config.getType()))
|
||||
throw new IllegalArgumentException("Version " + name + " is not a HMCL modpack. Cannot update this version.");
|
||||
|
||||
@@ -30,7 +30,6 @@ import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
@@ -49,15 +48,14 @@ public final class HMCLModpackProvider implements ModpackProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, File zipFile, Modpack modpack) throws MismatchedModpackTypeException {
|
||||
public Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, Path zipFile, Modpack modpack) throws MismatchedModpackTypeException {
|
||||
if (!(modpack.getManifest() instanceof HMCLModpackManifest))
|
||||
throw new MismatchedModpackTypeException(getName(), modpack.getManifest().getProvider().getName());
|
||||
|
||||
if (!(dependencyManager.getGameRepository() instanceof HMCLGameRepository)) {
|
||||
if (!(dependencyManager.getGameRepository() instanceof HMCLGameRepository repository)) {
|
||||
throw new IllegalArgumentException("HMCLModpackProvider requires HMCLGameRepository");
|
||||
}
|
||||
|
||||
HMCLGameRepository repository = (HMCLGameRepository) dependencyManager.getGameRepository();
|
||||
Profile profile = repository.getProfile();
|
||||
|
||||
return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new HMCLModpackInstallTask(profile, zipFile, modpack, name));
|
||||
@@ -79,9 +77,9 @@ public final class HMCLModpackProvider implements ModpackProvider {
|
||||
return manifest;
|
||||
}
|
||||
|
||||
private static class HMCLModpack extends Modpack {
|
||||
private final static class HMCLModpack extends Modpack {
|
||||
@Override
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, File zipFile, String name) {
|
||||
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name) {
|
||||
return new HMCLModpackInstallTask(((HMCLGameRepository) dependencyManager.getGameRepository()).getProfile(), zipFile, this, name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,15 +49,16 @@ import org.jackhuang.hmcl.util.platform.*;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.stream.Collectors;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
@@ -76,7 +77,7 @@ public final class LauncherHelper {
|
||||
private final Profile profile;
|
||||
private Account account;
|
||||
private final String selectedVersion;
|
||||
private File scriptFile;
|
||||
private Path scriptFile;
|
||||
private final VersionSetting setting;
|
||||
private LauncherVisibility launcherVisibility;
|
||||
private boolean showLogs;
|
||||
@@ -119,9 +120,8 @@ public final class LauncherHelper {
|
||||
launch0();
|
||||
}
|
||||
|
||||
public void makeLaunchScript(File scriptFile) {
|
||||
public void makeLaunchScript(Path scriptFile) {
|
||||
this.scriptFile = Objects.requireNonNull(scriptFile);
|
||||
|
||||
launch();
|
||||
}
|
||||
|
||||
@@ -160,19 +160,17 @@ public final class LauncherHelper {
|
||||
}),
|
||||
Task.composeAsync(() -> {
|
||||
Renderer renderer = setting.getRenderer();
|
||||
if (renderer != Renderer.DEFAULT
|
||||
&& OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
|
||||
&& OperatingSystem.WINDOWS_VERSION != null) {
|
||||
Library lib = NativePatcher.getWindowsMesaLoader(java, renderer, OperatingSystem.WINDOWS_VERSION);
|
||||
if (renderer != Renderer.DEFAULT && OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
Library lib = NativePatcher.getWindowsMesaLoader(java, renderer, OperatingSystem.SYSTEM_VERSION);
|
||||
if (lib == null)
|
||||
return null;
|
||||
File file = dependencyManager.getGameRepository().getLibraryFile(version.get(), lib);
|
||||
if (file.getAbsolutePath().indexOf('=') >= 0) {
|
||||
Path file = dependencyManager.getGameRepository().getLibraryFile(version.get(), lib);
|
||||
if (file.toAbsolutePath().toString().indexOf('=') >= 0) {
|
||||
LOG.warning("Invalid character '=' in the libraries directory path, unable to attach software renderer loader");
|
||||
return null;
|
||||
}
|
||||
|
||||
String agent = file.getAbsolutePath() + "=" + renderer.name().toLowerCase(Locale.ROOT);
|
||||
String agent = FileUtils.getAbsolutePath(file) + "=" + renderer.name().toLowerCase(Locale.ROOT);
|
||||
|
||||
if (GameLibrariesTask.shouldDownloadLibrary(repository, version.get(), lib, integrityCheck)) {
|
||||
return new LibraryDownloadTask(dependencyManager, file, lib)
|
||||
@@ -226,7 +224,7 @@ public final class LauncherHelper {
|
||||
} else {
|
||||
runLater(() -> {
|
||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
|
||||
Controllers.dialog(i18n("version.launch_script.success", FileUtils.getAbsolutePath(scriptFile)));
|
||||
});
|
||||
}
|
||||
}).withFakeProgress(
|
||||
@@ -709,6 +707,7 @@ public final class LauncherHelper {
|
||||
*/
|
||||
private final class HMCLProcessListener implements ProcessListener {
|
||||
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
private final HMCLGameRepository repository;
|
||||
private final Version version;
|
||||
private final LaunchOptions launchOptions;
|
||||
@@ -852,21 +851,27 @@ public final class LauncherHelper {
|
||||
level = Lang.requireNonNullElse(Log4jLevel.guessLevel(log), Log4jLevel.INFO);
|
||||
logBuffer.add(new Log(log, level));
|
||||
} else {
|
||||
synchronized (this) {
|
||||
lock.lock();
|
||||
try {
|
||||
logs.addLast(new Log(log, level));
|
||||
if (logs.size() > Log.getLogLines())
|
||||
logs.removeFirst();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
if (!lwjgl) {
|
||||
String lowerCaseLog = log.toLowerCase(Locale.ROOT);
|
||||
if (!detectWindow || lowerCaseLog.contains("lwjgl version") || lowerCaseLog.contains("lwjgl openal")) {
|
||||
synchronized (this) {
|
||||
lock.lock();
|
||||
try {
|
||||
if (!lwjgl) {
|
||||
lwjgl = true;
|
||||
finishLaunch();
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -891,9 +896,12 @@ public final class LauncherHelper {
|
||||
|
||||
// Game crashed before opening the game window.
|
||||
if (!lwjgl) {
|
||||
synchronized (this) {
|
||||
lock.lock();
|
||||
try {
|
||||
if (!lwjgl)
|
||||
finishLaunch();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,8 +44,8 @@ public final class LogExporter {
|
||||
}
|
||||
|
||||
public static CompletableFuture<Void> exportLogs(Path zipFile, DefaultGameRepository gameRepository, String versionId, String logs, String launchScript) {
|
||||
Path runDirectory = gameRepository.getRunDirectory(versionId).toPath();
|
||||
Path baseDirectory = gameRepository.getBaseDirectory().toPath();
|
||||
Path runDirectory = gameRepository.getRunDirectory(versionId);
|
||||
Path baseDirectory = gameRepository.getBaseDirectory();
|
||||
List<String> versions = new ArrayList<>();
|
||||
|
||||
String currentVersionId = versionId;
|
||||
|
||||
@@ -19,11 +19,13 @@ package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import kala.compress.archivers.zip.ZipArchiveReader;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.mod.curse.CurseModpackProvider;
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackManifest;
|
||||
import org.jackhuang.hmcl.mod.mcbbs.McbbsModpackProvider;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthModpackProvider;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCComponents;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCInstanceConfiguration;
|
||||
import org.jackhuang.hmcl.mod.multimc.MultiMCModpackProvider;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackManifest;
|
||||
@@ -42,7 +44,6 @@ import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
@@ -58,7 +59,8 @@ import static org.jackhuang.hmcl.util.Lang.toIterable;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public final class ModpackHelper {
|
||||
private ModpackHelper() {}
|
||||
private ModpackHelper() {
|
||||
}
|
||||
|
||||
private static final Map<String, ModpackProvider> providers = mapOf(
|
||||
pair(CurseModpackProvider.INSTANCE.getName(), CurseModpackProvider.INSTANCE),
|
||||
@@ -69,12 +71,16 @@ public final class ModpackHelper {
|
||||
pair(HMCLModpackProvider.INSTANCE.getName(), HMCLModpackProvider.INSTANCE)
|
||||
);
|
||||
|
||||
static {
|
||||
MultiMCComponents.setImplementation(Metadata.FULL_TITLE);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ModpackProvider getProviderByType(String type) {
|
||||
return providers.get(type);
|
||||
}
|
||||
|
||||
public static boolean isFileModpackByExtension(File file) {
|
||||
public static boolean isFileModpackByExtension(Path file) {
|
||||
String ext = FileUtils.getExtension(file);
|
||||
return "zip".equals(ext) || "mrpack".equals(ext);
|
||||
}
|
||||
@@ -132,15 +138,12 @@ public final class ModpackHelper {
|
||||
(path.getFileName() == null || ".minecraft".equals(FileUtils.getName(path)));
|
||||
}
|
||||
|
||||
public static ModpackConfiguration<?> readModpackConfiguration(File file) throws IOException {
|
||||
if (!file.exists())
|
||||
throw new FileNotFoundException(file.getPath());
|
||||
else
|
||||
try {
|
||||
return JsonUtils.fromJsonFile(file.toPath(), ModpackConfiguration.class);
|
||||
} catch (JsonParseException e) {
|
||||
throw new IOException("Malformed modpack configuration");
|
||||
}
|
||||
public static ModpackConfiguration<?> readModpackConfiguration(Path file) throws IOException {
|
||||
try {
|
||||
return JsonUtils.fromJsonFile(file, ModpackConfiguration.class);
|
||||
} catch (JsonParseException e) {
|
||||
throw new IOException("Malformed modpack configuration");
|
||||
}
|
||||
}
|
||||
|
||||
public static Task<?> getInstallTask(Profile profile, ServerModpackManifest manifest, String name, Modpack modpack) {
|
||||
@@ -171,21 +174,21 @@ public final class ModpackHelper {
|
||||
return Files.exists(Paths.get("externalgames").resolve(name));
|
||||
}
|
||||
|
||||
public static Task<?> getInstallManuallyCreatedModpackTask(Profile profile, File zipFile, String name, Charset charset) {
|
||||
public static Task<?> getInstallManuallyCreatedModpackTask(Profile profile, Path zipFile, String name, Charset charset) {
|
||||
if (isExternalGameNameConflicts(name)) {
|
||||
throw new IllegalArgumentException("name existing");
|
||||
}
|
||||
|
||||
return new ManuallyCreatedModpackInstallTask(profile, zipFile.toPath(), charset, name)
|
||||
return new ManuallyCreatedModpackInstallTask(profile, zipFile, charset, name)
|
||||
.thenAcceptAsync(Schedulers.javafx(), location -> {
|
||||
Profile newProfile = new Profile(name, location.toFile());
|
||||
Profile newProfile = new Profile(name, location);
|
||||
newProfile.setUseRelativePath(true);
|
||||
Profiles.getProfiles().add(newProfile);
|
||||
Profiles.setSelectedProfile(newProfile);
|
||||
});
|
||||
}
|
||||
|
||||
public static Task<?> getInstallTask(Profile profile, File zipFile, String name, Modpack modpack) {
|
||||
public static Task<?> getInstallTask(Profile profile, Path zipFile, String name, Modpack modpack) {
|
||||
profile.getRepository().markVersionAsModpack(name);
|
||||
|
||||
ExceptionalRunnable<?> success = () -> {
|
||||
@@ -230,8 +233,8 @@ public final class ModpackHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static Task<?> getUpdateTask(Profile profile, File zipFile, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, ManuallyCreatedModpackException, MismatchedModpackTypeException {
|
||||
Modpack modpack = ModpackHelper.readModpackManifest(zipFile.toPath(), charset);
|
||||
public static Task<?> getUpdateTask(Profile profile, Path zipFile, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, ManuallyCreatedModpackException, MismatchedModpackTypeException {
|
||||
Modpack modpack = ModpackHelper.readModpackManifest(zipFile, charset);
|
||||
ModpackProvider provider = getProviderByType(configuration.getType());
|
||||
if (provider == null) {
|
||||
throw new UnsupportedModpackException();
|
||||
|
||||
@@ -207,7 +207,7 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
public Task<Void> getUninstallJavaTask(Platform platform, String name) {
|
||||
return Task.runAsync(() -> {
|
||||
Files.deleteIfExists(getManifestFile(platform, name));
|
||||
FileUtils.deleteDirectory(getJavaDir(platform, name).toFile());
|
||||
FileUtils.deleteDirectory(getJavaDir(platform, name));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ public final class HMCLJavaRepository implements JavaRepository {
|
||||
if (relativized.getNameCount() > 1) {
|
||||
String name = relativized.getName(0).toString();
|
||||
Files.deleteIfExists(getManifestFile(java.getPlatform(), name));
|
||||
FileUtils.deleteDirectory(getJavaDir(java.getPlatform(), name).toFile());
|
||||
FileUtils.deleteDirectory(getJavaDir(java.getPlatform(), name));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,10 +20,13 @@ package org.jackhuang.hmcl.java;
|
||||
import kala.compress.archivers.ArchiveEntry;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.tree.ArchiveFileTree;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -98,8 +101,7 @@ public final class JavaInstallTask extends Task<JavaManifest> {
|
||||
}
|
||||
|
||||
if (tree.isExecutable(entry))
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
path.toFile().setExecutable(true);
|
||||
FileUtils.setExecutable(path);
|
||||
|
||||
files.put(String.join("/", nameStack), new JavaLocalFiles.LocalFile(HexFormat.of().formatHex(messageDigest.digest()), size));
|
||||
}
|
||||
|
||||
@@ -209,9 +209,18 @@ public final class JavaManager {
|
||||
|
||||
public static Task<Void> getUninstallJavaTask(JavaRuntime java) {
|
||||
assert java.isManaged();
|
||||
Path root = REPOSITORY.getPlatformRoot(java.getPlatform());
|
||||
Path relativized = root.relativize(java.getBinary());
|
||||
|
||||
Path platformRoot;
|
||||
try {
|
||||
platformRoot = REPOSITORY.getPlatformRoot(java.getPlatform()).toRealPath();
|
||||
} catch (Throwable ignored) {
|
||||
return Task.completed(null);
|
||||
}
|
||||
|
||||
if (!java.getBinary().startsWith(platformRoot))
|
||||
return Task.completed(null);
|
||||
|
||||
Path relativized = platformRoot.relativize(java.getBinary());
|
||||
if (relativized.getNameCount() > 1) {
|
||||
FXUtils.runInFX(() -> {
|
||||
try {
|
||||
@@ -255,16 +264,15 @@ public final class JavaManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static int compareJavaVersion(JavaRuntime java1, JavaRuntime java2, GameJavaVersion suggestedJavaVersion) {
|
||||
if (suggestedJavaVersion != null) {
|
||||
boolean b1 = java1.getParsedVersion() == suggestedJavaVersion.getMajorVersion();
|
||||
boolean b2 = java2.getParsedVersion() == suggestedJavaVersion.getMajorVersion();
|
||||
private static JavaRuntime chooseJava(@Nullable JavaRuntime java1, JavaRuntime java2) {
|
||||
if (java1 == null)
|
||||
return java2;
|
||||
|
||||
if (b1 != b2)
|
||||
return b1 ? 1 : -1;
|
||||
}
|
||||
|
||||
return java1.getVersionNumber().compareTo(java2.getVersionNumber());
|
||||
if (java1.getParsedVersion() != java2.getParsedVersion())
|
||||
// Prefer the Java version that is closer to the game's recommended Java version
|
||||
return java1.getParsedVersion() < java2.getParsedVersion() ? java1 : java2;
|
||||
else
|
||||
return java1.getVersionNumber().compareTo(java2.getVersionNumber()) >= 0 ? java1 : java2;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -280,9 +288,6 @@ public final class JavaManager {
|
||||
&& (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS || OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)
|
||||
&& (gameVersion == null || gameVersion.compareTo("1.6") < 0);
|
||||
|
||||
GameJavaVersion suggestedJavaVersion =
|
||||
(version != null && gameVersion != null && gameVersion.compareTo("1.7.10") >= 0) ? version.getJavaVersion() : null;
|
||||
|
||||
JavaRuntime mandatory = null;
|
||||
JavaRuntime suggested = null;
|
||||
for (JavaRuntime java : javaRuntimes) {
|
||||
@@ -310,15 +315,10 @@ public final class JavaManager {
|
||||
}
|
||||
|
||||
if (!violationMandatory) {
|
||||
if (mandatory == null) mandatory = java;
|
||||
else if (compareJavaVersion(java, mandatory, suggestedJavaVersion) > 0)
|
||||
mandatory = java;
|
||||
mandatory = chooseJava(mandatory, java);
|
||||
|
||||
if (!violationSuggested) {
|
||||
if (suggested == null) suggested = java;
|
||||
else if (compareJavaVersion(java, suggested, suggestedJavaVersion) > 0)
|
||||
suggested = java;
|
||||
}
|
||||
if (!violationSuggested)
|
||||
suggested = chooseJava(suggested, java);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonSerializable;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.gson.TolerableValidationException;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
@@ -37,6 +38,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
@JsonSerializable
|
||||
public final class AuthlibInjectorServers implements Validation {
|
||||
|
||||
public static final String CONFIG_FILENAME = "authlib-injectors.json";
|
||||
|
||||
@@ -34,20 +34,18 @@ import org.hildan.fxgson.creators.ObservableSetCreator;
|
||||
import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.util.gson.EnumOrdinalDeserializer;
|
||||
import org.jackhuang.hmcl.util.gson.FileTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.gson.ObservableField;
|
||||
import org.jackhuang.hmcl.util.gson.PaintAdapter;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.gson.*;
|
||||
import org.jackhuang.hmcl.util.i18n.SupportedLocale;
|
||||
import org.jackhuang.hmcl.util.javafx.DirtyTracker;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.*;
|
||||
import java.net.Proxy;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
@JsonAdapter(value = Config.Adapter.class)
|
||||
@@ -57,7 +55,7 @@ public final class Config implements Observable {
|
||||
public static final int CURRENT_UI_VERSION = 0;
|
||||
|
||||
public static final Gson CONFIG_GSON = new GsonBuilder()
|
||||
.registerTypeAdapter(File.class, FileTypeAdapter.INSTANCE)
|
||||
.registerTypeAdapter(Path.class, PathTypeAdapter.INSTANCE)
|
||||
.registerTypeAdapter(ObservableList.class, new ObservableListCreator())
|
||||
.registerTypeAdapter(ObservableSet.class, new ObservableSetCreator())
|
||||
.registerTypeAdapter(ObservableMap.class, new ObservableMapCreator())
|
||||
@@ -223,7 +221,7 @@ public final class Config implements Observable {
|
||||
}
|
||||
|
||||
@SerializedName("localization")
|
||||
private final ObjectProperty<SupportedLocale> localization = new SimpleObjectProperty<>(Locales.DEFAULT);
|
||||
private final ObjectProperty<SupportedLocale> localization = new SimpleObjectProperty<>(SupportedLocale.DEFAULT);
|
||||
|
||||
public ObjectProperty<SupportedLocale> localizationProperty() {
|
||||
return localization;
|
||||
@@ -260,7 +258,7 @@ public final class Config implements Observable {
|
||||
}
|
||||
|
||||
@SerializedName("commonDirType")
|
||||
private final ObjectProperty<EnumCommonDirectory> commonDirType = new SimpleObjectProperty<>(EnumCommonDirectory.DEFAULT);
|
||||
private final ObjectProperty<EnumCommonDirectory> commonDirType = new RawPreservingObjectProperty<>(EnumCommonDirectory.DEFAULT);
|
||||
|
||||
public ObjectProperty<EnumCommonDirectory> commonDirTypeProperty() {
|
||||
return commonDirType;
|
||||
@@ -367,7 +365,11 @@ public final class Config implements Observable {
|
||||
}
|
||||
|
||||
@SerializedName("animationDisabled")
|
||||
private final BooleanProperty animationDisabled = new SimpleBooleanProperty();
|
||||
private final BooleanProperty animationDisabled = new SimpleBooleanProperty(
|
||||
FXUtils.REDUCED_MOTION == Boolean.TRUE
|
||||
|| !JavaRuntime.CURRENT_JIT_ENABLED
|
||||
|| !FXUtils.GPU_ACCELERATION_ENABLED
|
||||
);
|
||||
|
||||
public BooleanProperty animationDisabledProperty() {
|
||||
return animationDisabled;
|
||||
@@ -397,7 +399,7 @@ public final class Config implements Observable {
|
||||
}
|
||||
|
||||
@SerializedName("backgroundType")
|
||||
private final ObjectProperty<EnumBackgroundImage> backgroundImageType = new SimpleObjectProperty<>(EnumBackgroundImage.DEFAULT);
|
||||
private final ObjectProperty<EnumBackgroundImage> backgroundImageType = new RawPreservingObjectProperty<>(EnumBackgroundImage.DEFAULT);
|
||||
|
||||
public ObjectProperty<EnumBackgroundImage> backgroundImageTypeProperty() {
|
||||
return backgroundImageType;
|
||||
@@ -769,9 +771,7 @@ public final class Config implements Observable {
|
||||
for (var field : FIELDS) {
|
||||
Observable observable = field.get(config);
|
||||
if (config.tracker.isDirty(observable)) {
|
||||
JsonElement serialized = field.serialize(config, context);
|
||||
if (serialized != null && !serialized.isJsonNull())
|
||||
result.add(field.getSerializedName(), serialized);
|
||||
field.serialize(result, config, context);
|
||||
}
|
||||
}
|
||||
config.unknownFields.forEach(result::add);
|
||||
@@ -791,6 +791,14 @@ public final class Config implements Observable {
|
||||
var values = new LinkedHashMap<>(json.getAsJsonObject().asMap());
|
||||
for (ObservableField<Config> field : FIELDS) {
|
||||
JsonElement value = values.remove(field.getSerializedName());
|
||||
if (value == null) {
|
||||
for (String alternateName : field.getAlternateNames()) {
|
||||
value = values.remove(alternateName);
|
||||
if (value != null)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (value != null) {
|
||||
config.tracker.markDirty(field.get(config));
|
||||
field.deserialize(config, value, context);
|
||||
|
||||
@@ -31,23 +31,21 @@ import java.io.FileNotFoundException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.task.FetchTask.DEFAULT_CONCURRENCY;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class DownloadProviders {
|
||||
private DownloadProviders() {}
|
||||
private DownloadProviders() {
|
||||
}
|
||||
|
||||
private static DownloadProvider currentDownloadProvider;
|
||||
private static final DownloadProviderWrapper provider;
|
||||
|
||||
public static final Map<String, DownloadProvider> providersById;
|
||||
public static final Map<String, DownloadProvider> rawProviders;
|
||||
@@ -59,6 +57,7 @@ public final class DownloadProviders {
|
||||
public static final String DEFAULT_PROVIDER_ID = "balanced";
|
||||
public static final String DEFAULT_RAW_PROVIDER_ID = "bmclapi";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final InvalidationListener observer;
|
||||
|
||||
static {
|
||||
@@ -68,71 +67,68 @@ public final class DownloadProviders {
|
||||
|
||||
MOJANG = new MojangDownloadProvider();
|
||||
BMCLAPI = new BMCLAPIDownloadProvider(bmclapiRoot);
|
||||
rawProviders = mapOf(
|
||||
pair("mojang", MOJANG),
|
||||
pair("bmclapi", BMCLAPI)
|
||||
rawProviders = Map.of(
|
||||
"mojang", MOJANG,
|
||||
"bmclapi", BMCLAPI
|
||||
);
|
||||
|
||||
AdaptedDownloadProvider fileProvider = new AdaptedDownloadProvider();
|
||||
fileProvider.setDownloadProviderCandidates(Arrays.asList(BMCLAPI, MOJANG));
|
||||
fileProvider.setDownloadProviderCandidates(List.of(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, fileProvider)));
|
||||
providersById = Map.of(
|
||||
"official", new AutoDownloadProvider(MOJANG, fileProvider),
|
||||
"balanced", new AutoDownloadProvider(balanced, fileProvider),
|
||||
"mirror", new AutoDownloadProvider(BMCLAPI, fileProvider));
|
||||
|
||||
observer = FXUtils.observeWeak(() -> {
|
||||
FetchTask.setDownloadExecutorConcurrency(
|
||||
config().getAutoDownloadThreads() ? DEFAULT_CONCURRENCY : config().getDownloadThreads());
|
||||
}, config().autoDownloadThreadsProperty(), config().downloadThreadsProperty());
|
||||
|
||||
provider = new DownloadProviderWrapper(MOJANG);
|
||||
}
|
||||
|
||||
static void init() {
|
||||
FXUtils.onChangeAndOperate(config().versionListSourceProperty(), versionListSource -> {
|
||||
if (!providersById.containsKey(versionListSource)) {
|
||||
config().setVersionListSource(DEFAULT_PROVIDER_ID);
|
||||
return;
|
||||
InvalidationListener onChangeDownloadSource = observable -> {
|
||||
String versionListSource = Objects.requireNonNullElse(config().getVersionListSource(), "");
|
||||
if (config().isAutoChooseDownloadType()) {
|
||||
DownloadProvider currentDownloadProvider = providersById.get(versionListSource);
|
||||
if (currentDownloadProvider == null)
|
||||
currentDownloadProvider = Objects.requireNonNull(providersById.get(DEFAULT_PROVIDER_ID),
|
||||
"default provider is null");
|
||||
|
||||
provider.setProvider(currentDownloadProvider);
|
||||
} else {
|
||||
provider.setProvider(fileDownloadProvider);
|
||||
}
|
||||
};
|
||||
config().versionListSourceProperty().addListener(onChangeDownloadSource);
|
||||
config().autoChooseDownloadTypeProperty().addListener(onChangeDownloadSource);
|
||||
|
||||
currentDownloadProvider = Optional.ofNullable(providersById.get(versionListSource))
|
||||
.orElse(providersById.get(DEFAULT_PROVIDER_ID));
|
||||
});
|
||||
|
||||
if (!rawProviders.containsKey(config().getDownloadType())) {
|
||||
config().setDownloadType(DEFAULT_RAW_PROVIDER_ID);
|
||||
}
|
||||
onChangeDownloadSource.invalidated(null);
|
||||
|
||||
FXUtils.onChangeAndOperate(config().downloadTypeProperty(), downloadType -> {
|
||||
DownloadProvider primary = Optional.ofNullable(rawProviders.get(downloadType))
|
||||
.orElse(rawProviders.get(DEFAULT_RAW_PROVIDER_ID));
|
||||
fileDownloadProvider.setDownloadProviderCandidates(
|
||||
Stream.concat(
|
||||
Stream.of(primary),
|
||||
rawProviders.values().stream().filter(x -> x != primary)
|
||||
).collect(Collectors.toList())
|
||||
);
|
||||
DownloadProvider primary = Objects.requireNonNullElseGet(
|
||||
rawProviders.get(Objects.requireNonNullElse(downloadType, "")),
|
||||
() -> rawProviders.get(DEFAULT_RAW_PROVIDER_ID));
|
||||
|
||||
List<DownloadProvider> providers = new ArrayList<>(rawProviders.size());
|
||||
providers.add(primary);
|
||||
for (DownloadProvider provider : rawProviders.values()) {
|
||||
if (provider != primary)
|
||||
providers.add(provider);
|
||||
}
|
||||
|
||||
fileDownloadProvider.setDownloadProviderCandidates(providers);
|
||||
});
|
||||
}
|
||||
|
||||
public static String getPrimaryDownloadProviderId() {
|
||||
String downloadType = config().getDownloadType();
|
||||
if (providersById.containsKey(downloadType))
|
||||
return downloadType;
|
||||
else
|
||||
return DEFAULT_PROVIDER_ID;
|
||||
}
|
||||
|
||||
public static DownloadProvider getDownloadProviderByPrimaryId(String primaryId) {
|
||||
return Optional.ofNullable(providersById.get(primaryId))
|
||||
.orElse(providersById.get(DEFAULT_PROVIDER_ID));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current primary preferred download provider
|
||||
*/
|
||||
public static DownloadProvider getDownloadProvider() {
|
||||
return config().isAutoChooseDownloadType() ? currentDownloadProvider : fileDownloadProvider;
|
||||
return provider;
|
||||
}
|
||||
|
||||
public static String localizeErrorMessage(Throwable exception) {
|
||||
|
||||
@@ -35,8 +35,8 @@ import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
@@ -65,17 +65,17 @@ public final class Profile implements Observable {
|
||||
this.selectedVersion.set(selectedVersion);
|
||||
}
|
||||
|
||||
private final ObjectProperty<File> gameDir;
|
||||
private final ObjectProperty<Path> gameDir;
|
||||
|
||||
public ObjectProperty<File> gameDirProperty() {
|
||||
public ObjectProperty<Path> gameDirProperty() {
|
||||
return gameDir;
|
||||
}
|
||||
|
||||
public File getGameDir() {
|
||||
public Path getGameDir() {
|
||||
return gameDir.get();
|
||||
}
|
||||
|
||||
public void setGameDir(File gameDir) {
|
||||
public void setGameDir(Path gameDir) {
|
||||
this.gameDir.set(gameDir);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ public final class Profile implements Observable {
|
||||
this.name.set(name);
|
||||
}
|
||||
|
||||
private BooleanProperty useRelativePath = new SimpleBooleanProperty(this, "useRelativePath", false);
|
||||
private final BooleanProperty useRelativePath = new SimpleBooleanProperty(this, "useRelativePath", false);
|
||||
|
||||
public BooleanProperty useRelativePathProperty() {
|
||||
return useRelativePath;
|
||||
@@ -118,18 +118,18 @@ public final class Profile implements Observable {
|
||||
}
|
||||
|
||||
public Profile(String name) {
|
||||
this(name, new File(".minecraft"));
|
||||
this(name, Path.of(".minecraft"));
|
||||
}
|
||||
|
||||
public Profile(String name, File initialGameDir) {
|
||||
public Profile(String name, Path initialGameDir) {
|
||||
this(name, initialGameDir, new VersionSetting());
|
||||
}
|
||||
|
||||
public Profile(String name, File initialGameDir, VersionSetting global) {
|
||||
public Profile(String name, Path initialGameDir, VersionSetting global) {
|
||||
this(name, initialGameDir, global, null, false);
|
||||
}
|
||||
|
||||
public Profile(String name, File initialGameDir, VersionSetting global, String selectedVersion, boolean useRelativePath) {
|
||||
public Profile(String name, Path initialGameDir, VersionSetting global, String selectedVersion, boolean useRelativePath) {
|
||||
this.name = new SimpleStringProperty(this, "name", name);
|
||||
gameDir = new SimpleObjectProperty<>(this, "gameDir", initialGameDir);
|
||||
repository = new HMCLGameRepository(this, initialGameDir);
|
||||
@@ -234,7 +234,7 @@ public final class Profile implements Observable {
|
||||
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.add("global", context.serialize(src.getGlobal()));
|
||||
jsonObject.addProperty("gameDir", src.getGameDir().getPath());
|
||||
jsonObject.addProperty("gameDir", src.getGameDir().toString());
|
||||
jsonObject.addProperty("useRelativePath", src.isUseRelativePath());
|
||||
jsonObject.addProperty("selectedMinecraftVersion", src.getSelectedVersion());
|
||||
|
||||
@@ -243,12 +243,11 @@ public final class Profile implements Observable {
|
||||
|
||||
@Override
|
||||
public Profile deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
if (json == JsonNull.INSTANCE || !(json instanceof JsonObject)) return null;
|
||||
JsonObject obj = (JsonObject) json;
|
||||
if (!(json instanceof JsonObject obj)) return null;
|
||||
String gameDir = Optional.ofNullable(obj.get("gameDir")).map(JsonElement::getAsString).orElse("");
|
||||
|
||||
return new Profile("Default",
|
||||
new File(gameDir),
|
||||
Path.of(gameDir),
|
||||
context.deserialize(obj.get("global"), VersionSetting.class),
|
||||
Optional.ofNullable(obj.get("selectedMinecraftVersion")).map(JsonElement::getAsString).orElse(""),
|
||||
Optional.ofNullable(obj.get("useRelativePath")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -48,20 +48,17 @@ public final class Profiles {
|
||||
}
|
||||
|
||||
public static String getProfileDisplayName(Profile profile) {
|
||||
switch (profile.getName()) {
|
||||
case Profiles.DEFAULT_PROFILE:
|
||||
return i18n("profile.default");
|
||||
case Profiles.HOME_PROFILE:
|
||||
return i18n("profile.home");
|
||||
default:
|
||||
return profile.getName();
|
||||
}
|
||||
return switch (profile.getName()) {
|
||||
case Profiles.DEFAULT_PROFILE -> i18n("profile.default");
|
||||
case Profiles.HOME_PROFILE -> i18n("profile.home");
|
||||
default -> profile.getName();
|
||||
};
|
||||
}
|
||||
|
||||
private static final ObservableList<Profile> profiles = observableArrayList(profile -> new Observable[] { profile });
|
||||
private static final ReadOnlyListWrapper<Profile> profilesWrapper = new ReadOnlyListWrapper<>(profiles);
|
||||
|
||||
private static ObjectProperty<Profile> selectedProfile = new SimpleObjectProperty<Profile>() {
|
||||
private static final ObjectProperty<Profile> selectedProfile = new SimpleObjectProperty<Profile>() {
|
||||
{
|
||||
profiles.addListener(onInvalidating(this::invalidated));
|
||||
}
|
||||
@@ -104,8 +101,8 @@ public final class Profiles {
|
||||
|
||||
private static void checkProfiles() {
|
||||
if (profiles.isEmpty()) {
|
||||
Profile current = new Profile(Profiles.DEFAULT_PROFILE, new File(".minecraft"), new VersionSetting(), null, true);
|
||||
Profile home = new Profile(Profiles.HOME_PROFILE, Metadata.MINECRAFT_DIRECTORY.toFile());
|
||||
Profile current = new Profile(Profiles.DEFAULT_PROFILE, Path.of(".minecraft"), new VersionSetting(), null, true);
|
||||
Profile home = new Profile(Profiles.HOME_PROFILE, Metadata.MINECRAFT_DIRECTORY);
|
||||
Platform.runLater(() -> profiles.addAll(current, home));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,6 +380,20 @@ public final class VersionSetting implements Cloneable, Observable {
|
||||
noJVMArgsProperty.set(noJVMArgs);
|
||||
}
|
||||
|
||||
private final BooleanProperty noOptimizingJVMArgsProperty = new SimpleBooleanProperty(this, "noOptimizingJVMArgs", false);
|
||||
|
||||
public BooleanProperty noOptimizingJVMArgsProperty() {
|
||||
return noOptimizingJVMArgsProperty;
|
||||
}
|
||||
|
||||
public boolean isNoOptimizingJVMArgs() {
|
||||
return noOptimizingJVMArgsProperty.get();
|
||||
}
|
||||
|
||||
public void setNoOptimizingJVMArgs(boolean noOptimizingJVMArgs) {
|
||||
noOptimizingJVMArgsProperty.set(noOptimizingJVMArgs);
|
||||
}
|
||||
|
||||
private final BooleanProperty notCheckJVMProperty = new SimpleBooleanProperty(this, "notCheckJVM", false);
|
||||
|
||||
public BooleanProperty notCheckJVMProperty() {
|
||||
@@ -757,6 +771,7 @@ public final class VersionSetting implements Cloneable, Observable {
|
||||
obj.addProperty("wrapper", src.getWrapper());
|
||||
obj.addProperty("fullscreen", src.isFullscreen());
|
||||
obj.addProperty("noJVMArgs", src.isNoJVMArgs());
|
||||
obj.addProperty("noOptimizingJVMArgs", src.isNoOptimizingJVMArgs());
|
||||
obj.addProperty("notCheckGame", src.isNotCheckGame());
|
||||
obj.addProperty("notCheckJVM", src.isNotCheckJVM());
|
||||
obj.addProperty("notPatchNatives", src.isNotPatchNatives());
|
||||
@@ -827,6 +842,7 @@ public final class VersionSetting implements Cloneable, Observable {
|
||||
vs.setNativesDir(Optional.ofNullable(obj.get("nativesDir")).map(JsonElement::getAsString).orElse(""));
|
||||
vs.setFullscreen(Optional.ofNullable(obj.get("fullscreen")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNoJVMArgs(Optional.ofNullable(obj.get("noJVMArgs")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNoOptimizingJVMArgs(Optional.ofNullable(obj.get("noOptimizingJVMArgs")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNotCheckGame(Optional.ofNullable(obj.get("notCheckGame")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNotCheckJVM(Optional.ofNullable(obj.get("notCheckJVM")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNotPatchNatives(Optional.ofNullable(obj.get("notPatchNatives")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
|
||||
@@ -62,7 +62,7 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@@ -72,6 +72,8 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class Controllers {
|
||||
public static final String JAVA_VERSION_TIP = "javaVersion";
|
||||
public static final String JAVA_INTERPRETED_MODE_TIP = "javaInterpretedMode";
|
||||
public static final String SOFTWARE_RENDERING = "softwareRendering";
|
||||
|
||||
public static final int MIN_WIDTH = 800 + 2 + 16; // bg width + border width*2 + shadow width*2
|
||||
public static final int MIN_HEIGHT = 450 + 2 + 40 + 16; // bg height + border width*2 + toolbar height + shadow width*2
|
||||
@@ -90,7 +92,7 @@ public final class Controllers {
|
||||
gameListPage.selectedProfileProperty().bindBidirectional(Profiles.selectedProfileProperty());
|
||||
gameListPage.profilesProperty().bindContent(Profiles.profilesProperty());
|
||||
FXUtils.applyDragListener(gameListPage, ModpackHelper::isFileModpackByExtension, modpacks -> {
|
||||
File modpack = modpacks.get(0);
|
||||
Path modpack = modpacks.get(0);
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack"));
|
||||
});
|
||||
return gameListPage;
|
||||
@@ -347,6 +349,24 @@ public final class Controllers {
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether JIT is enabled in the current environment
|
||||
if (!JavaRuntime.CURRENT_JIT_ENABLED && !Boolean.TRUE.equals(config().getShownTips().get(JAVA_INTERPRETED_MODE_TIP))) {
|
||||
Controllers.dialog(new MessageDialogPane.Builder(i18n("warning.java_interpreted_mode"), i18n("message.warning"), MessageType.WARNING)
|
||||
.ok(null)
|
||||
.addCancel(i18n("button.do_not_show_again"), () ->
|
||||
config().getShownTips().put(JAVA_INTERPRETED_MODE_TIP, true))
|
||||
.build());
|
||||
}
|
||||
|
||||
// Check whether hardware acceleration is enabled
|
||||
if (!FXUtils.GPU_ACCELERATION_ENABLED && !Boolean.TRUE.equals(config().getShownTips().get(SOFTWARE_RENDERING))) {
|
||||
Controllers.dialog(new MessageDialogPane.Builder(i18n("warning.software_rendering"), i18n("message.warning"), MessageType.WARNING)
|
||||
.ok(null)
|
||||
.addCancel(i18n("button.do_not_show_again"), () ->
|
||||
config().getShownTips().put(SOFTWARE_RENDERING, true))
|
||||
.build());
|
||||
}
|
||||
|
||||
if (globalConfig().getAgreementVersion() < 1) {
|
||||
JFXDialogLayout agreementPane = new JFXDialogLayout();
|
||||
agreementPane.setHeading(new Label(i18n("launcher.agreement")));
|
||||
|
||||
@@ -49,9 +49,7 @@ import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.*;
|
||||
import javafx.util.Callback;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
@@ -79,24 +77,25 @@ import org.xml.sax.SAXException;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.*;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.net.*;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
@@ -109,6 +108,25 @@ public final class FXUtils {
|
||||
|
||||
public static final int JAVAFX_MAJOR_VERSION;
|
||||
|
||||
public static final String GRAPHICS_PIPELINE;
|
||||
public static final boolean GPU_ACCELERATION_ENABLED;
|
||||
|
||||
static {
|
||||
String pipelineName = "";
|
||||
|
||||
try {
|
||||
Object pipeline = Class.forName("com.sun.prism.GraphicsPipeline").getMethod("getPipeline").invoke(null);
|
||||
if (pipeline != null) {
|
||||
pipelineName = pipeline.getClass().getName();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to get prism pipeline", e);
|
||||
}
|
||||
|
||||
GRAPHICS_PIPELINE = pipelineName;
|
||||
GPU_ACCELERATION_ENABLED = !pipelineName.endsWith(".SWPipeline");
|
||||
}
|
||||
|
||||
/// @see Platform.Preferences
|
||||
public static final @Nullable ObservableMap<String, Object> PREFERENCES;
|
||||
public static final @Nullable ObservableBooleanValue DARK_MODE;
|
||||
@@ -440,20 +458,27 @@ public final class FXUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void openFolder(File file) {
|
||||
if (!FileUtils.makeDirectory(file)) {
|
||||
LOG.error("Unable to make directory " + file);
|
||||
public static void openFolder(Path file) {
|
||||
if (file.getFileSystem() != FileSystems.getDefault()) {
|
||||
LOG.warning("Cannot open folder as the file system is not supported: " + file);
|
||||
return;
|
||||
}
|
||||
|
||||
String path = file.getAbsolutePath();
|
||||
try {
|
||||
Files.createDirectories(file);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to create directory " + file);
|
||||
return;
|
||||
}
|
||||
|
||||
String path = FileUtils.getAbsolutePath(file);
|
||||
|
||||
String openCommand;
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
|
||||
openCommand = "explorer.exe";
|
||||
else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)
|
||||
openCommand = "/usr/bin/open";
|
||||
else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && new File("/usr/bin/xdg-open").exists())
|
||||
else if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && Files.exists(Path.of("/usr/bin/xdg-open")))
|
||||
openCommand = "/usr/bin/xdg-open";
|
||||
else
|
||||
openCommand = null;
|
||||
@@ -475,7 +500,7 @@ public final class FXUtils {
|
||||
|
||||
// Fallback to java.awt.Desktop::open
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().open(file);
|
||||
java.awt.Desktop.getDesktop().open(file.toFile());
|
||||
} catch (Throwable e) {
|
||||
LOG.error("Unable to open " + path + " by java.awt.Desktop.getDesktop()::open", e);
|
||||
}
|
||||
@@ -518,11 +543,11 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
// Fallback to open folder
|
||||
openFolder(file.getParent().toFile());
|
||||
openFolder(file.getParent());
|
||||
});
|
||||
} else {
|
||||
// We do not have a universal method to show file in file manager.
|
||||
openFolder(file.getParent().toFile());
|
||||
openFolder(file.getParent());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1190,7 +1215,6 @@ public final class FXUtils {
|
||||
public static JFXButton newBorderButton(String text) {
|
||||
JFXButton button = new JFXButton(text);
|
||||
button.getStyleClass().add("jfx-button-border");
|
||||
button.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
return button;
|
||||
}
|
||||
|
||||
@@ -1234,14 +1258,14 @@ public final class FXUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static void applyDragListener(Node node, FileFilter filter, Consumer<List<File>> callback) {
|
||||
public static void applyDragListener(Node node, PathMatcher filter, Consumer<List<Path>> callback) {
|
||||
applyDragListener(node, filter, callback, null);
|
||||
}
|
||||
|
||||
public static void applyDragListener(Node node, FileFilter filter, Consumer<List<File>> callback, Runnable dragDropped) {
|
||||
public static void applyDragListener(Node node, PathMatcher filter, Consumer<List<Path>> callback, Runnable dragDropped) {
|
||||
node.setOnDragOver(event -> {
|
||||
if (event.getGestureSource() != node && event.getDragboard().hasFiles()) {
|
||||
if (event.getDragboard().getFiles().stream().anyMatch(filter::accept))
|
||||
if (event.getDragboard().getFiles().stream().map(File::toPath).anyMatch(filter::matches))
|
||||
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
|
||||
}
|
||||
event.consume();
|
||||
@@ -1250,7 +1274,7 @@ public final class FXUtils {
|
||||
node.setOnDragDropped(event -> {
|
||||
List<File> files = event.getDragboard().getFiles();
|
||||
if (files != null) {
|
||||
List<File> acceptFiles = files.stream().filter(filter::accept).collect(Collectors.toList());
|
||||
List<Path> acceptFiles = files.stream().map(File::toPath).filter(filter::matches).toList();
|
||||
if (!acceptFiles.isEmpty()) {
|
||||
callback.accept(acceptFiles);
|
||||
event.setDropCompleted(true);
|
||||
@@ -1342,6 +1366,27 @@ public final class FXUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static <T> void onScroll(Node node, List<T> list,
|
||||
ToIntFunction<List<T>> finder,
|
||||
Consumer<T> updater
|
||||
) {
|
||||
node.addEventHandler(ScrollEvent.SCROLL, event -> {
|
||||
double deltaY = event.getDeltaY();
|
||||
if (deltaY == 0)
|
||||
return;
|
||||
|
||||
int index = finder.applyAsInt(list);
|
||||
if (index < 0) return;
|
||||
if (deltaY > 0) // up
|
||||
index--;
|
||||
else // down
|
||||
index++;
|
||||
|
||||
updater.accept(list.get((index + list.size()) % list.size()));
|
||||
event.consume();
|
||||
});
|
||||
}
|
||||
|
||||
public static void copyOnDoubleClick(Labeled label) {
|
||||
label.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
|
||||
if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 2) {
|
||||
|
||||
@@ -129,7 +129,7 @@ public class GameCrashWindow extends Stage {
|
||||
|
||||
return pair(CrashReportAnalyzer.analyze(rawLog), crashReport != null ? CrashReportAnalyzer.findKeywordsFromCrashReport(crashReport) : new HashSet<>());
|
||||
}), Task.supplyAsync(() -> {
|
||||
Path latestLog = repository.getRunDirectory(version.getId()).toPath().resolve("logs/latest.log");
|
||||
Path latestLog = repository.getRunDirectory(version.getId()).resolve("logs/latest.log");
|
||||
if (!Files.isReadable(latestLog)) {
|
||||
return pair(new HashSet<CrashReportAnalyzer.Result>(), new HashSet<String>());
|
||||
}
|
||||
@@ -379,7 +379,7 @@ public class GameCrashWindow extends Stage {
|
||||
TwoLineListItem gameDir = new TwoLineListItem();
|
||||
gameDir.getStyleClass().setAll("two-line-item-second-large");
|
||||
gameDir.setTitle(i18n("game.directory"));
|
||||
gameDir.setSubtitle(launchOptions.getGameDir().getAbsolutePath());
|
||||
gameDir.setSubtitle(launchOptions.getGameDir().toAbsolutePath().toString());
|
||||
FXUtils.installFastTooltip(gameDir, i18n("game.directory"));
|
||||
|
||||
TwoLineListItem javaDir = new TwoLineListItem();
|
||||
|
||||
@@ -22,6 +22,7 @@ import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
@@ -318,7 +319,9 @@ public class InstallerItem extends Control {
|
||||
private static final class InstallerItemSkin extends SkinBase<InstallerItem> {
|
||||
private static final PseudoClass LIST_ITEM = PseudoClass.getPseudoClass("list-item");
|
||||
private static final PseudoClass CARD = PseudoClass.getPseudoClass("card");
|
||||
private static final WeakListenerHolder holder = new WeakListenerHolder();
|
||||
|
||||
@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
||||
private final ChangeListener<Number> holder;
|
||||
|
||||
InstallerItemSkin(InstallerItem control) {
|
||||
super(control);
|
||||
@@ -326,9 +329,10 @@ public class InstallerItem extends Control {
|
||||
Pane pane;
|
||||
if (control.style == Style.CARD) {
|
||||
pane = new VBox();
|
||||
holder.add(FXUtils.onWeakChange(pane.widthProperty(), v -> FXUtils.setLimitHeight(pane, v.doubleValue() * 0.7)));
|
||||
holder = FXUtils.onWeakChangeAndOperate(pane.widthProperty(), v -> FXUtils.setLimitHeight(pane, v.doubleValue() * 0.7));
|
||||
} else {
|
||||
pane = new HBox();
|
||||
holder = null;
|
||||
}
|
||||
pane.getStyleClass().add("installer-item");
|
||||
RipplerContainer container = new RipplerContainer(pane);
|
||||
|
||||
@@ -64,11 +64,9 @@ public class ListPageSkin extends SkinBase<ListPage<?>> {
|
||||
vBox.setAlignment(Pos.BOTTOM_RIGHT);
|
||||
vBox.setPickOnBounds(false);
|
||||
|
||||
JFXButton btnAdd = new JFXButton();
|
||||
JFXButton btnAdd = FXUtils.newRaisedButton("");
|
||||
FXUtils.setLimitWidth(btnAdd, 40);
|
||||
FXUtils.setLimitHeight(btnAdd, 40);
|
||||
btnAdd.getStyleClass().add("jfx-button-raised-round");
|
||||
btnAdd.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
btnAdd.setGraphic(SVG.ADD.createIcon(Theme.whiteFill(), -1));
|
||||
btnAdd.setOnAction(e -> skinnable.add());
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.game.GameDumpGenerator;
|
||||
import org.jackhuang.hmcl.game.Log;
|
||||
import org.jackhuang.hmcl.setting.StyleSheets;
|
||||
import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.CircularArrayList;
|
||||
@@ -311,6 +312,7 @@ public final class LogWindow extends Stage {
|
||||
Holder<Object> lastCell = new Holder<>();
|
||||
listView.setCellFactory(x -> new ListCell<Log>() {
|
||||
{
|
||||
x.setSelectionModel(new NoneMultipleSelectionModel<>());
|
||||
getStyleClass().add("log-window-list-cell");
|
||||
Region clippedContainer = (Region) listView.lookup(".clipped-container");
|
||||
if (clippedContainer != null) {
|
||||
|
||||
@@ -21,7 +21,6 @@ import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
@@ -77,25 +76,9 @@ public class AccountAdvancedListItem extends AdvancedListItem {
|
||||
|
||||
setActionButtonVisible(false);
|
||||
|
||||
setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY();
|
||||
if (deltaY == 0)
|
||||
return;
|
||||
|
||||
Account current = account.get();
|
||||
if (current == null) return;
|
||||
|
||||
ObservableList<Account> accounts = Accounts.getAccounts();
|
||||
int currentIndex = accounts.indexOf(current);
|
||||
if (currentIndex < 0) return;
|
||||
|
||||
if (deltaY > 0) // up
|
||||
currentIndex--;
|
||||
else // down
|
||||
currentIndex++;
|
||||
|
||||
Accounts.setSelectedAccount(accounts.get((currentIndex + accounts.size()) % accounts.size()));
|
||||
});
|
||||
FXUtils.onScroll(this, Accounts.getAccounts(),
|
||||
accounts -> accounts.indexOf(account.get()),
|
||||
Accounts::setSelectedAccount);
|
||||
}
|
||||
|
||||
public ObjectProperty<Account> accountProperty() {
|
||||
|
||||
@@ -41,13 +41,14 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.DialogController;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.skin.InvalidSkinException;
|
||||
import org.jackhuang.hmcl.util.skin.NormalizedSkin;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CancellationException;
|
||||
@@ -116,8 +117,7 @@ public class AccountListItem extends RadioButton {
|
||||
}
|
||||
|
||||
public ObservableBooleanValue canUploadSkin() {
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
AuthlibInjectorAccount aiAccount = (AuthlibInjectorAccount) account;
|
||||
if (account instanceof AuthlibInjectorAccount aiAccount) {
|
||||
ObjectBinding<Optional<CompleteGameProfile>> profile = aiAccount.getYggdrasilService().getProfileRepository().binding(aiAccount.getUUID());
|
||||
return createBooleanBinding(() -> {
|
||||
Set<TextureType> uploadableTextures = profile.get()
|
||||
@@ -148,7 +148,7 @@ public class AccountListItem extends RadioButton {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("account.skin.upload"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("account.skin.file"), "*.png"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
Path selectedFile = FileUtils.toPath(chooser.showOpenDialog(Controllers.getStage()));
|
||||
if (selectedFile == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -156,7 +156,7 @@ public class AccountListItem extends RadioButton {
|
||||
return refreshAsync()
|
||||
.thenRunAsync(() -> {
|
||||
Image skinImg;
|
||||
try (FileInputStream input = new FileInputStream(selectedFile)) {
|
||||
try (var input = Files.newInputStream(selectedFile)) {
|
||||
skinImg = new Image(input);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidSkinException("Failed to read skin image", e);
|
||||
@@ -167,7 +167,7 @@ public class AccountListItem extends RadioButton {
|
||||
NormalizedSkin skin = new NormalizedSkin(skinImg);
|
||||
String model = skin.isSlim() ? "slim" : "";
|
||||
LOG.info("Uploading skin [" + selectedFile + "], model [" + model + "]");
|
||||
account.uploadSkin(skin.isSlim(), selectedFile.toPath());
|
||||
account.uploadSkin(skin.isSlim(), selectedFile);
|
||||
})
|
||||
.thenComposeAsync(refreshAsync())
|
||||
.whenComplete(Schedulers.javafx(), e -> {
|
||||
|
||||
@@ -39,8 +39,9 @@ import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -81,16 +82,16 @@ public class OfflineAccountSkinPane extends StackPane {
|
||||
|
||||
canvas.addEventHandler(DragEvent.DRAG_OVER, e -> {
|
||||
if (e.getDragboard().hasFiles()) {
|
||||
File file = e.getDragboard().getFiles().get(0);
|
||||
if (file.getAbsolutePath().endsWith(".png"))
|
||||
Path file = e.getDragboard().getFiles().get(0).toPath();
|
||||
if (FileUtils.getName(file).endsWith(".png"))
|
||||
e.acceptTransferModes(TransferMode.COPY);
|
||||
}
|
||||
});
|
||||
canvas.addEventHandler(DragEvent.DRAG_DROPPED, e -> {
|
||||
if (e.isAccepted()) {
|
||||
File skin = e.getDragboard().getFiles().get(0);
|
||||
Path skin = e.getDragboard().getFiles().get(0).toPath();
|
||||
Platform.runLater(() -> {
|
||||
skinSelector.setValue(skin.getAbsolutePath());
|
||||
skinSelector.setValue(FileUtils.getAbsolutePath(skin));
|
||||
skinItem.setSelectedData(Skin.Type.LOCAL_FILE);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.ui.animation;
|
||||
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
/**
|
||||
@@ -37,7 +36,7 @@ public final class AnimationUtils {
|
||||
public static void init() {
|
||||
}
|
||||
|
||||
private static final boolean ENABLED = !ConfigHolder.config().isAnimationDisabled() && FXUtils.REDUCED_MOTION != Boolean.TRUE;
|
||||
private static final boolean ENABLED = !ConfigHolder.config().isAnimationDisabled();
|
||||
private static final boolean PLAY_WINDOW_ANIMATION = ENABLED && !OperatingSystem.CURRENT_OS.isLinuxOrBSD();
|
||||
|
||||
public static boolean isAnimationEnabled() {
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
@@ -40,8 +41,16 @@ public class AdvancedListBox extends ScrollPane {
|
||||
setFitToHeight(true);
|
||||
setFitToWidth(true);
|
||||
setHbarPolicy(ScrollBarPolicy.NEVER);
|
||||
setVbarPolicy(ScrollBarPolicy.NEVER);
|
||||
|
||||
container.getStyleClass().add("advanced-list-box-content");
|
||||
|
||||
this.addEventFilter(MouseEvent.MOUSE_ENTERED, event -> {
|
||||
if (container.getHeight() > getHeight())
|
||||
setVbarPolicy(ScrollBarPolicy.AS_NEEDED);
|
||||
});
|
||||
this.addEventFilter(MouseEvent.MOUSE_EXITED,
|
||||
event -> setVbarPolicy(ScrollBarPolicy.NEVER));
|
||||
}
|
||||
|
||||
public AdvancedListBox add(Node child) {
|
||||
|
||||
@@ -27,17 +27,19 @@ import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public class FileItem extends BorderPane {
|
||||
private final Label lblPath = new Label();
|
||||
@@ -77,14 +79,14 @@ public class FileItem extends BorderPane {
|
||||
private String processPath(String path) {
|
||||
Path given;
|
||||
try {
|
||||
given = Paths.get(path).toAbsolutePath();
|
||||
given = Path.of(path).toAbsolutePath().normalize();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return path;
|
||||
}
|
||||
|
||||
if (isConvertToRelativePath()) {
|
||||
try {
|
||||
return Paths.get(".").normalize().toAbsolutePath().relativize(given).normalize().toString();
|
||||
return Metadata.CURRENT_DIRECTORY.relativize(given).normalize().toString();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// the given path can't be relativized against current path
|
||||
}
|
||||
@@ -95,17 +97,22 @@ public class FileItem extends BorderPane {
|
||||
public void onExplore() {
|
||||
DirectoryChooser chooser = new DirectoryChooser();
|
||||
if (path.get() != null) {
|
||||
File file = new File(path.get());
|
||||
if (file.exists()) {
|
||||
if (file.isFile())
|
||||
file = file.getAbsoluteFile().getParentFile();
|
||||
else if (file.isDirectory())
|
||||
file = file.getAbsoluteFile();
|
||||
chooser.setInitialDirectory(file);
|
||||
Path file;
|
||||
try {
|
||||
file = Path.of(path.get());
|
||||
if (Files.exists(file)) {
|
||||
if (Files.isRegularFile(file))
|
||||
file = file.toAbsolutePath().normalize().getParent();
|
||||
else if (Files.isDirectory(file))
|
||||
file = file.toAbsolutePath().normalize();
|
||||
chooser.setInitialDirectory(file.toFile());
|
||||
}
|
||||
} catch (InvalidPathException e) {
|
||||
LOG.warning("Failed to resolve path: " + path.get());
|
||||
}
|
||||
}
|
||||
chooser.titleProperty().bind(titleProperty());
|
||||
File selectedDir = chooser.showDialog(Controllers.getStage());
|
||||
var selectedDir = chooser.showDialog(Controllers.getStage());
|
||||
if (selectedDir != null) {
|
||||
path.set(processPath(selectedDir.toString()));
|
||||
}
|
||||
|
||||
@@ -31,8 +31,9 @@ import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -86,9 +87,9 @@ public class FileSelector extends HBox {
|
||||
if (directory) {
|
||||
DirectoryChooser chooser = new DirectoryChooser();
|
||||
chooser.setTitle(chooserTitle);
|
||||
File dir = chooser.showDialog(Controllers.getStage());
|
||||
Path dir = FileUtils.toPath(chooser.showDialog(Controllers.getStage()));
|
||||
if (dir != null) {
|
||||
String path = dir.getAbsolutePath();
|
||||
String path = FileUtils.getAbsolutePath(dir);
|
||||
customField.setText(path);
|
||||
value.setValue(path);
|
||||
}
|
||||
@@ -96,9 +97,9 @@ public class FileSelector extends HBox {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.getExtensionFilters().addAll(getExtensionFilters());
|
||||
chooser.setTitle(chooserTitle);
|
||||
File file = chooser.showOpenDialog(Controllers.getStage());
|
||||
Path file = FileUtils.toPath(chooser.showOpenDialog(Controllers.getStage()));
|
||||
if (file != null) {
|
||||
String path = file.getAbsolutePath();
|
||||
String path = FileUtils.getAbsolutePath(file);
|
||||
customField.setText(path);
|
||||
value.setValue(path);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ import javafx.beans.property.StringProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
@@ -61,7 +63,12 @@ public class OptionToggleButton extends StackPane {
|
||||
toggleButton.setSize(8);
|
||||
FXUtils.setLimitHeight(toggleButton, 30);
|
||||
|
||||
FXUtils.onClicked(container, () -> toggleButton.setSelected(!toggleButton.isSelected()));
|
||||
container.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
|
||||
if (e.getButton() == MouseButton.PRIMARY) {
|
||||
toggleButton.setSelected(!toggleButton.isSelected());
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
|
||||
FXUtils.onChangeAndOperate(subtitleProperty(), subtitle -> {
|
||||
if (StringUtils.isNotBlank(subtitle)) {
|
||||
|
||||
@@ -191,7 +191,7 @@ public class RipplerContainer extends StackPane {
|
||||
return StyleableProperties.FACTORY.getCssMetaData();
|
||||
}
|
||||
|
||||
private static class StyleableProperties {
|
||||
private final static class StyleableProperties {
|
||||
private static final StyleablePropertyFactory<RipplerContainer> FACTORY = new StyleablePropertyFactory<>(StackPane.getClassCssMetaData());
|
||||
|
||||
private static final CssMetaData<RipplerContainer, Paint> RIPPLER_FILL = FACTORY.createPaintCssMetaData("-jfx-rippler-fill", s -> s.ripplerFill, Color.rgb(0, 200, 255));
|
||||
|
||||
@@ -225,7 +225,7 @@ public class TabHeader extends Control implements TabControl, PageAware {
|
||||
}
|
||||
}
|
||||
|
||||
private class HeadersRegion extends StackPane {
|
||||
private final class HeadersRegion extends StackPane {
|
||||
private SideAction action;
|
||||
private final ObjectProperty<Side> side = new SimpleObjectProperty<Side>() {
|
||||
@Override
|
||||
@@ -379,7 +379,7 @@ public class TabHeader extends Control implements TabControl, PageAware {
|
||||
}
|
||||
}
|
||||
|
||||
private class Top extends Horizontal {
|
||||
private final class Top extends Horizontal {
|
||||
@Override
|
||||
public void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
@@ -404,7 +404,7 @@ public class TabHeader extends Control implements TabControl, PageAware {
|
||||
}
|
||||
}
|
||||
|
||||
private class Bottom extends Horizontal {
|
||||
private final class Bottom extends Horizontal {
|
||||
@Override
|
||||
public void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
@@ -519,7 +519,7 @@ public class TabHeader extends Control implements TabControl, PageAware {
|
||||
}
|
||||
}
|
||||
|
||||
private class Left extends Vertical {
|
||||
private final class Left extends Vertical {
|
||||
@Override
|
||||
public void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
@@ -543,7 +543,7 @@ public class TabHeader extends Control implements TabControl, PageAware {
|
||||
}
|
||||
}
|
||||
|
||||
private class Right extends Vertical {
|
||||
private final class Right extends Vertical {
|
||||
@Override
|
||||
public void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
|
||||
@@ -40,6 +40,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
public class TaskExecutorDialogPane extends BorderPane {
|
||||
private TaskExecutor executor;
|
||||
private TaskCancellationAction onCancel;
|
||||
@SuppressWarnings({"unused", "FieldCanBeLocal"})
|
||||
private final Consumer<FetchTask.SpeedEvent> speedEventHandler;
|
||||
|
||||
private final Label lblTitle;
|
||||
@@ -84,7 +85,7 @@ public class TaskExecutorDialogPane extends BorderPane {
|
||||
}
|
||||
});
|
||||
|
||||
speedEventHandler = speedEvent -> {
|
||||
speedEventHandler = FetchTask.SPEED_EVENT.registerWeak(speedEvent -> {
|
||||
String unit = "B/s";
|
||||
double speed = speedEvent.getSpeed();
|
||||
if (speed > 1024) {
|
||||
@@ -95,11 +96,10 @@ public class TaskExecutorDialogPane extends BorderPane {
|
||||
speed /= 1024;
|
||||
unit = "MiB/s";
|
||||
}
|
||||
double finalSpeed = speed;
|
||||
String finalUnit = unit;
|
||||
Platform.runLater(() -> lblProgress.setText(String.format("%.1f %s", finalSpeed, finalUnit)));
|
||||
};
|
||||
FileDownloadTask.speedEvent.channel(FetchTask.SpeedEvent.class).registerWeak(speedEventHandler);
|
||||
|
||||
String message = String.format("%.1f %s", speed, unit);
|
||||
Platform.runLater(() -> lblProgress.setText(message));
|
||||
});
|
||||
|
||||
onEscPressed(this, btnCancel::fire);
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.construct.Navigator;
|
||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.wizard.*;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public class DecoratorWizardDisplayer extends DecoratorTransitionPage implements WizardDisplayer {
|
||||
@@ -94,7 +94,7 @@ public class DecoratorWizardDisplayer extends DecoratorTransitionPage implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTask(Map<String, Object> settings, Task<?> task) {
|
||||
public void handleTask(SettingsMap settings, Task<?> task) {
|
||||
displayer.handleTask(settings, task);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,8 +35,7 @@ import org.jackhuang.hmcl.ui.InstallerItem;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -80,12 +79,12 @@ public abstract class AbstractInstallersPage extends Control implements WizardPa
|
||||
protected abstract void reload();
|
||||
|
||||
@Override
|
||||
public void onNavigate(Map<String, Object> settings) {
|
||||
public void onNavigate(SettingsMap settings) {
|
||||
reload();
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract void cleanup(Map<String, Object> settings);
|
||||
public abstract void cleanup(SettingsMap settings);
|
||||
|
||||
protected abstract void onInstall();
|
||||
|
||||
@@ -120,20 +119,15 @@ public abstract class AbstractInstallersPage extends Control implements WizardPa
|
||||
{
|
||||
InstallerItem[] libraries = control.group.getLibraries();
|
||||
|
||||
FlowPane libraryPane = new FlowPane(libraries);
|
||||
libraryPane.setVgap(16);
|
||||
libraryPane.setHgap(16);
|
||||
FlowPane libraryPane = new FlowPane(16, 16, libraries);
|
||||
ScrollPane scrollPane = new ScrollPane(libraryPane);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setFitToHeight(true);
|
||||
BorderPane.setMargin(scrollPane, new Insets(16, 0, 16, 0));
|
||||
root.setCenter(scrollPane);
|
||||
|
||||
if (libraries.length <= 8) {
|
||||
BorderPane.setMargin(libraryPane, new Insets(16, 0, 16, 0));
|
||||
root.setCenter(libraryPane);
|
||||
} else {
|
||||
ScrollPane scrollPane = new ScrollPane(libraryPane);
|
||||
scrollPane.setFitToWidth(true);
|
||||
scrollPane.setFitToHeight(true);
|
||||
BorderPane.setMargin(scrollPane, new Insets(16, 0, 16, 0));
|
||||
root.setCenter(scrollPane);
|
||||
}
|
||||
if (libraries.length <= 8)
|
||||
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -29,8 +29,8 @@ import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.ui.InstallerItem;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.MINECRAFT;
|
||||
@@ -109,6 +109,6 @@ class AdditionalInstallersPage extends AbstractInstallersPage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
public void cleanup(SettingsMap settings) {
|
||||
}
|
||||
}
|
||||
@@ -51,14 +51,13 @@ import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.ui.wizard.Navigation;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -145,10 +144,10 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
public static void download(Profile profile, @Nullable String version, RemoteMod.Version file, String subdirectoryName) {
|
||||
if (version == null) version = profile.getSelectedVersion();
|
||||
|
||||
Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version).toPath() : profile.getRepository().getBaseDirectory().toPath();
|
||||
Path runDirectory = profile.getRepository().hasVersion(version) ? profile.getRepository().getRunDirectory(version) : profile.getRepository().getBaseDirectory();
|
||||
|
||||
Controllers.prompt(i18n("archive.file.name"), (result, resolve, reject) -> {
|
||||
if (!OperatingSystem.isNameValid(result)) {
|
||||
if (!FileUtils.isNameValid(result)) {
|
||||
reject.accept(i18n("install.new_game.malformed"));
|
||||
return;
|
||||
}
|
||||
@@ -223,7 +222,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
|
||||
private static final class DownloadNavigator implements Navigation {
|
||||
private final Map<String, Object> settings = new HashMap<>();
|
||||
private final SettingsMap settings = new SettingsMap();
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
@@ -260,7 +259,7 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getSettings() {
|
||||
public SettingsMap getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
@@ -287,37 +286,39 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Map<String, Object> settings) {
|
||||
settings.put(PROFILE, profile);
|
||||
public void start(SettingsMap settings) {
|
||||
settings.put(ModpackPage.PROFILE, profile);
|
||||
settings.put(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), gameVersion);
|
||||
}
|
||||
|
||||
private Task<Void> finishVersionDownloadingAsync(Map<String, Object> settings) {
|
||||
private Task<Void> finishVersionDownloadingAsync(SettingsMap settings) {
|
||||
GameBuilder builder = dependencyManager.gameBuilder();
|
||||
|
||||
String name = (String) settings.get("name");
|
||||
builder.name(name);
|
||||
builder.gameVersion(((RemoteVersion) settings.get(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId())).getGameVersion());
|
||||
|
||||
for (Map.Entry<String, Object> entry : settings.entrySet())
|
||||
if (!LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId().equals(entry.getKey()) && entry.getValue() instanceof RemoteVersion)
|
||||
builder.version((RemoteVersion) entry.getValue());
|
||||
settings.asStringMap().forEach((key, value) -> {
|
||||
if (!LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId().equals(key)
|
||||
&& value instanceof RemoteVersion remoteVersion)
|
||||
builder.version(remoteVersion);
|
||||
});
|
||||
|
||||
return builder.buildAsync().whenComplete(any -> profile.getRepository().refreshVersions())
|
||||
.thenRunAsync(Schedulers.javafx(), () -> profile.setSelectedVersion(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
public Object finish(SettingsMap settings) {
|
||||
settings.put("title", i18n("install.new_game.installation"));
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_callback", (FailureCallback) (settings1, exception, next) -> UpdateInstallerWizardProvider.alertFailureMessage(exception, next));
|
||||
settings.put(FailureCallback.KEY, (settings1, exception, next) -> UpdateInstallerWizardProvider.alertFailureMessage(exception, next));
|
||||
|
||||
return finishVersionDownloadingAsync(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
|
||||
public Node createPage(WizardController controller, int step, SettingsMap settings) {
|
||||
switch (step) {
|
||||
case 0:
|
||||
return new InstallersPage(controller, profile.getRepository(), ((RemoteVersion) controller.getSettings().get("game")).getGameVersion(), downloadProvider);
|
||||
@@ -330,7 +331,5 @@ public class DownloadPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
public boolean cancel() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static final String PROFILE = "PROFILE";
|
||||
}
|
||||
}
|
||||
@@ -27,8 +27,7 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.RequiredValidator;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
|
||||
import static javafx.beans.binding.Bindings.createBooleanBinding;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -73,7 +72,7 @@ public class InstallersPage extends AbstractInstallersPage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
public void cleanup(SettingsMap settings) {
|
||||
}
|
||||
|
||||
private static boolean checkName(String name) {
|
||||
|
||||
@@ -36,16 +36,14 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.RequiredValidator;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -58,11 +56,11 @@ public final class LocalModpackPage extends ModpackPage {
|
||||
public LocalModpackPage(WizardController controller) {
|
||||
super(controller);
|
||||
|
||||
Profile profile = (Profile) controller.getSettings().get("PROFILE");
|
||||
Profile profile = controller.getSettings().get(ModpackPage.PROFILE);
|
||||
|
||||
Optional<String> name = tryCast(controller.getSettings().get(MODPACK_NAME), String.class);
|
||||
if (name.isPresent()) {
|
||||
txtModpackName.setText(name.get());
|
||||
String name = controller.getSettings().get(MODPACK_NAME);
|
||||
if (name != null) {
|
||||
txtModpackName.setText(name);
|
||||
txtModpackName.setDisable(true);
|
||||
} else {
|
||||
FXUtils.onChangeAndOperate(installAsVersion, installAsVersion -> {
|
||||
@@ -82,15 +80,15 @@ public final class LocalModpackPage extends ModpackPage {
|
||||
|
||||
btnDescription.setVisible(false);
|
||||
|
||||
File selectedFile;
|
||||
Optional<File> filePath = tryCast(controller.getSettings().get(MODPACK_FILE), File.class);
|
||||
if (filePath.isPresent()) {
|
||||
selectedFile = filePath.get();
|
||||
Path selectedFile;
|
||||
Path filePath = controller.getSettings().get(MODPACK_FILE);
|
||||
if (filePath != null) {
|
||||
selectedFile = filePath;
|
||||
} else {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("modpack.choose"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
|
||||
selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
selectedFile = FileUtils.toPath(chooser.showOpenDialog(Controllers.getStage()));
|
||||
if (selectedFile == null) {
|
||||
controller.onEnd();
|
||||
return;
|
||||
@@ -100,19 +98,19 @@ public final class LocalModpackPage extends ModpackPage {
|
||||
}
|
||||
|
||||
showSpinner();
|
||||
Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(selectedFile.toPath()))
|
||||
Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(selectedFile))
|
||||
.thenApplyAsync(encoding -> {
|
||||
charset = encoding;
|
||||
manifest = ModpackHelper.readModpackManifest(selectedFile.toPath(), encoding);
|
||||
manifest = ModpackHelper.readModpackManifest(selectedFile, encoding);
|
||||
return manifest;
|
||||
})
|
||||
.whenComplete(Schedulers.javafx(), (manifest, exception) -> {
|
||||
if (exception instanceof ManuallyCreatedModpackException) {
|
||||
hideSpinner();
|
||||
lblName.setText(selectedFile.getName());
|
||||
lblName.setText(FileUtils.getName(selectedFile));
|
||||
installAsVersion.set(false);
|
||||
|
||||
if (!name.isPresent()) {
|
||||
if (name == null) {
|
||||
// trim: https://github.com/HMCL-dev/HMCL/issues/962
|
||||
txtModpackName.setText(FileUtils.getNameWithoutExtension(selectedFile));
|
||||
}
|
||||
@@ -133,7 +131,7 @@ public final class LocalModpackPage extends ModpackPage {
|
||||
lblVersion.setText(manifest.getVersion());
|
||||
lblAuthor.setText(manifest.getAuthor());
|
||||
|
||||
if (!name.isPresent()) {
|
||||
if (name == null) {
|
||||
// trim: https://github.com/HMCL-dev/HMCL/issues/962
|
||||
txtModpackName.setText(manifest.getName().trim());
|
||||
}
|
||||
@@ -144,7 +142,7 @@ public final class LocalModpackPage extends ModpackPage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
public void cleanup(SettingsMap settings) {
|
||||
settings.remove(MODPACK_FILE);
|
||||
}
|
||||
|
||||
@@ -177,9 +175,9 @@ public final class LocalModpackPage extends ModpackPage {
|
||||
Controllers.navigate(new WebPage(i18n("modpack.description"), manifest.getDescription()));
|
||||
}
|
||||
|
||||
public static final String MODPACK_FILE = "MODPACK_FILE";
|
||||
public static final String MODPACK_NAME = "MODPACK_NAME";
|
||||
public static final String MODPACK_MANIFEST = "MODPACK_MANIFEST";
|
||||
public static final String MODPACK_CHARSET = "MODPACK_CHARSET";
|
||||
public static final String MODPACK_MANUALLY_CREATED = "MODPACK_MANUALLY_CREATED";
|
||||
public static final SettingsMap.Key<Path> MODPACK_FILE = new SettingsMap.Key<>("MODPACK_FILE");
|
||||
public static final SettingsMap.Key<String> MODPACK_NAME = new SettingsMap.Key<>("MODPACK_NAME");
|
||||
public static final SettingsMap.Key<Modpack> MODPACK_MANIFEST = new SettingsMap.Key<>("MODPACK_MANIFEST");
|
||||
public static final SettingsMap.Key<Charset> MODPACK_CHARSET = new SettingsMap.Key<>("MODPACK_CHARSET");
|
||||
public static final SettingsMap.Key<Boolean> MODPACK_MANUALLY_CREATED = new SettingsMap.Key<>("MODPACK_MANUALLY_CREATED");
|
||||
}
|
||||
+24
-30
@@ -32,26 +32,25 @@ import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Map;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class ModpackInstallWizardProvider implements WizardProvider {
|
||||
public final class ModpackInstallWizardProvider implements WizardProvider {
|
||||
private final Profile profile;
|
||||
private final File file;
|
||||
private final Path file;
|
||||
private final String updateVersion;
|
||||
|
||||
public ModpackInstallWizardProvider(Profile profile) {
|
||||
this(profile, null, null);
|
||||
}
|
||||
|
||||
public ModpackInstallWizardProvider(Profile profile, File modpackFile) {
|
||||
public ModpackInstallWizardProvider(Profile profile, Path modpackFile) {
|
||||
this(profile, modpackFile, null);
|
||||
}
|
||||
|
||||
@@ -59,28 +58,28 @@ public class ModpackInstallWizardProvider implements WizardProvider {
|
||||
this(profile, null, updateVersion);
|
||||
}
|
||||
|
||||
public ModpackInstallWizardProvider(Profile profile, File modpackFile, String updateVersion) {
|
||||
public ModpackInstallWizardProvider(Profile profile, Path modpackFile, String updateVersion) {
|
||||
this.profile = profile;
|
||||
this.file = modpackFile;
|
||||
this.updateVersion = updateVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Map<String, Object> settings) {
|
||||
public void start(SettingsMap settings) {
|
||||
if (file != null)
|
||||
settings.put(LocalModpackPage.MODPACK_FILE, file);
|
||||
if (updateVersion != null)
|
||||
settings.put(LocalModpackPage.MODPACK_NAME, updateVersion);
|
||||
settings.put(PROFILE, profile);
|
||||
settings.put(ModpackPage.PROFILE, profile);
|
||||
}
|
||||
|
||||
private Task<?> finishModpackInstallingAsync(Map<String, Object> settings) {
|
||||
File selected = tryCast(settings.get(LocalModpackPage.MODPACK_FILE), File.class).orElse(null);
|
||||
ServerModpackManifest serverModpackManifest = tryCast(settings.get(RemoteModpackPage.MODPACK_SERVER_MANIFEST), ServerModpackManifest.class).orElse(null);
|
||||
Modpack modpack = tryCast(settings.get(LocalModpackPage.MODPACK_MANIFEST), Modpack.class).orElse(null);
|
||||
String name = tryCast(settings.get(LocalModpackPage.MODPACK_NAME), String.class).orElse(null);
|
||||
Charset charset = tryCast(settings.get(LocalModpackPage.MODPACK_CHARSET), Charset.class).orElse(null);
|
||||
boolean isManuallyCreated = tryCast(settings.get(LocalModpackPage.MODPACK_MANUALLY_CREATED), Boolean.class).orElse(false);
|
||||
private Task<?> finishModpackInstallingAsync(SettingsMap settings) {
|
||||
Path selected = settings.get(LocalModpackPage.MODPACK_FILE);
|
||||
ServerModpackManifest serverModpackManifest = settings.get(RemoteModpackPage.MODPACK_SERVER_MANIFEST);
|
||||
Modpack modpack = settings.get(LocalModpackPage.MODPACK_MANIFEST);
|
||||
String name = settings.get(LocalModpackPage.MODPACK_NAME);
|
||||
Charset charset = settings.get(LocalModpackPage.MODPACK_CHARSET);
|
||||
boolean isManuallyCreated = settings.getOrDefault(LocalModpackPage.MODPACK_MANUALLY_CREATED, false);
|
||||
|
||||
if (isManuallyCreated) {
|
||||
return ModpackHelper.getInstallManuallyCreatedModpackTask(profile, selected, name, charset);
|
||||
@@ -119,21 +118,18 @@ public class ModpackInstallWizardProvider implements WizardProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
public Object finish(SettingsMap settings) {
|
||||
settings.put("title", i18n("install.modpack.installation"));
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_callback", new FailureCallback() {
|
||||
@Override
|
||||
public void onFail(Map<String, Object> settings, Exception exception, Runnable next) {
|
||||
if (exception instanceof ModpackCompletionException) {
|
||||
if (exception.getCause() instanceof FileNotFoundException) {
|
||||
Controllers.dialog(i18n("modpack.type.curse.not_found"), i18n("install.failed"), MessageType.ERROR, next);
|
||||
} else {
|
||||
Controllers.dialog(i18n("install.success"), i18n("install.success"), MessageType.SUCCESS, next);
|
||||
}
|
||||
settings.put(FailureCallback.KEY, (ignored, exception, next) -> {
|
||||
if (exception instanceof ModpackCompletionException) {
|
||||
if (exception.getCause() instanceof FileNotFoundException) {
|
||||
Controllers.dialog(i18n("modpack.type.curse.not_found"), i18n("install.failed"), MessageType.ERROR, next);
|
||||
} else {
|
||||
UpdateInstallerWizardProvider.alertFailureMessage(exception, next);
|
||||
Controllers.dialog(i18n("install.success"), i18n("install.success"), MessageType.SUCCESS, next);
|
||||
}
|
||||
} else {
|
||||
UpdateInstallerWizardProvider.alertFailureMessage(exception, next);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -141,7 +137,7 @@ public class ModpackInstallWizardProvider implements WizardProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
|
||||
public Node createPage(WizardController controller, int step, SettingsMap settings) {
|
||||
switch (step) {
|
||||
case 0:
|
||||
return new ModpackSelectionPage(controller);
|
||||
@@ -161,6 +157,4 @@ public class ModpackInstallWizardProvider implements WizardProvider {
|
||||
public boolean cancel() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static final String PROFILE = "PROFILE";
|
||||
}
|
||||
@@ -7,16 +7,20 @@ import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
|
||||
import static javafx.beans.binding.Bindings.createBooleanBinding;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public abstract class ModpackPage extends SpinnerPane implements WizardPage {
|
||||
public static final SettingsMap.Key<Profile> PROFILE = new SettingsMap.Key<>("PROFILE");
|
||||
|
||||
protected final WizardController controller;
|
||||
|
||||
protected final Label lblName;
|
||||
@@ -78,8 +82,7 @@ public abstract class ModpackPage extends SpinnerPane implements WizardPage {
|
||||
|
||||
BorderPane descriptionPane = new BorderPane();
|
||||
{
|
||||
btnDescription = new JFXButton(i18n("modpack.description"));
|
||||
btnDescription.getStyleClass().add("jfx-button-border");
|
||||
btnDescription = FXUtils.newBorderButton(i18n("modpack.description"));
|
||||
btnDescription.setOnAction(e -> onDescribe());
|
||||
descriptionPane.setLeft(btnDescription);
|
||||
|
||||
|
||||
@@ -36,20 +36,18 @@ import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.download.LocalModpackPage.MODPACK_FILE;
|
||||
import static org.jackhuang.hmcl.ui.download.LocalModpackPage.MODPACK_NAME;
|
||||
import static org.jackhuang.hmcl.ui.download.RemoteModpackPage.MODPACK_SERVER_MANIFEST;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class ModpackSelectionPage extends VBox implements WizardPage {
|
||||
@@ -71,14 +69,14 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
|
||||
createButton("repository", this::onChooseRepository)
|
||||
);
|
||||
|
||||
Optional<File> filePath = tryCast(controller.getSettings().get(MODPACK_FILE), File.class);
|
||||
if (filePath.isPresent()) {
|
||||
controller.getSettings().put(MODPACK_FILE, filePath.get());
|
||||
Path filePath = controller.getSettings().get(MODPACK_FILE);
|
||||
if (filePath != null) {
|
||||
controller.getSettings().put(MODPACK_FILE, filePath);
|
||||
Platform.runLater(controller::onNext);
|
||||
}
|
||||
|
||||
FXUtils.applyDragListener(this, ModpackHelper::isFileModpackByExtension, modpacks -> {
|
||||
File modpack = modpacks.get(0);
|
||||
Path modpack = modpacks.get(0);
|
||||
controller.getSettings().put(MODPACK_FILE, modpack);
|
||||
controller.onNext();
|
||||
});
|
||||
@@ -112,7 +110,7 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("modpack.choose"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip", "*.mrpack"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
Path selectedFile = FileUtils.toPath(chooser.showOpenDialog(Controllers.getStage()));
|
||||
if (selectedFile == null) {
|
||||
Platform.runLater(controller::onEnd);
|
||||
return;
|
||||
@@ -150,7 +148,7 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
|
||||
.whenComplete(Schedulers.javafx(), e -> {
|
||||
if (e == null) {
|
||||
resolve.run();
|
||||
controller.getSettings().put(MODPACK_FILE, modpack.toFile());
|
||||
controller.getSettings().put(MODPACK_FILE, modpack);
|
||||
controller.onNext();
|
||||
} else {
|
||||
reject.accept(e.getMessage());
|
||||
@@ -167,14 +165,14 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
|
||||
}
|
||||
|
||||
public void onChooseRepository() {
|
||||
String modPackName = (String) controller.getSettings().get(MODPACK_NAME);
|
||||
String modPackName = controller.getSettings().get(MODPACK_NAME);
|
||||
DownloadPage downloadPage = new DownloadPage(modPackName);
|
||||
downloadPage.showModpackDownloads();
|
||||
Controllers.navigate(downloadPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
public void cleanup(SettingsMap settings) {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.server.ServerModpackManifest;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -27,13 +28,11 @@ import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.RequiredValidator;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class RemoteModpackPage extends ModpackPage {
|
||||
@@ -42,8 +41,9 @@ public final class RemoteModpackPage extends ModpackPage {
|
||||
public RemoteModpackPage(WizardController controller) {
|
||||
super(controller);
|
||||
|
||||
manifest = tryCast(controller.getSettings().get(MODPACK_SERVER_MANIFEST), ServerModpackManifest.class)
|
||||
.orElseThrow(() -> new IllegalStateException("MODPACK_SERVER_MANIFEST should exist"));
|
||||
manifest = controller.getSettings().get(MODPACK_SERVER_MANIFEST);
|
||||
if (manifest == null)
|
||||
throw new IllegalStateException("MODPACK_SERVER_MANIFEST should exist");
|
||||
|
||||
try {
|
||||
controller.getSettings().put(MODPACK_MANIFEST, manifest.toModpack(null));
|
||||
@@ -57,10 +57,10 @@ public final class RemoteModpackPage extends ModpackPage {
|
||||
lblVersion.setText(manifest.getVersion());
|
||||
lblAuthor.setText(manifest.getAuthor());
|
||||
|
||||
Profile profile = (Profile) controller.getSettings().get("PROFILE");
|
||||
Optional<String> name = tryCast(controller.getSettings().get(MODPACK_NAME), String.class);
|
||||
if (name.isPresent()) {
|
||||
txtModpackName.setText(name.get());
|
||||
Profile profile = controller.getSettings().get(ModpackPage.PROFILE);
|
||||
String name = controller.getSettings().get(MODPACK_NAME);
|
||||
if (name != null) {
|
||||
txtModpackName.setText(name);
|
||||
txtModpackName.setDisable(true);
|
||||
} else {
|
||||
// trim: https://github.com/HMCL-dev/HMCL/issues/962
|
||||
@@ -75,7 +75,7 @@ public final class RemoteModpackPage extends ModpackPage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
public void cleanup(SettingsMap settings) {
|
||||
settings.remove(MODPACK_SERVER_MANIFEST);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public final class RemoteModpackPage extends ModpackPage {
|
||||
Controllers.navigate(new WebPage(i18n("modpack.description"), manifest.getDescription()));
|
||||
}
|
||||
|
||||
public static final String MODPACK_SERVER_MANIFEST = "MODPACK_SERVER_MANIFEST";
|
||||
public static final String MODPACK_NAME = "MODPACK_NAME";
|
||||
public static final String MODPACK_MANIFEST = "MODPACK_MANIFEST";
|
||||
public static final SettingsMap.Key<ServerModpackManifest> MODPACK_SERVER_MANIFEST = new SettingsMap.Key<>("MODPACK_SERVER_MANIFEST");
|
||||
public static final SettingsMap.Key<String> MODPACK_NAME = new SettingsMap.Key<>("MODPACK_NAME");
|
||||
public static final SettingsMap.Key<Modpack> MODPACK_MANIFEST = new SettingsMap.Key<>("MODPACK_MANIFEST");
|
||||
}
|
||||
+9
-10
@@ -30,6 +30,7 @@ import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
||||
@@ -40,7 +41,6 @@ import java.net.SocketTimeoutException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.zip.ZipException;
|
||||
|
||||
@@ -66,30 +66,29 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Map<String, Object> settings) {
|
||||
public void start(SettingsMap settings) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
public Object finish(SettingsMap settings) {
|
||||
settings.put("title", i18n("install.change_version.process"));
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_callback", (FailureCallback) (settings1, exception, next) -> alertFailureMessage(exception, next));
|
||||
settings.put(FailureCallback.KEY, (settings1, exception, next) -> alertFailureMessage(exception, next));
|
||||
|
||||
// We remove library but not save it,
|
||||
// so if installation failed will not break down current version.
|
||||
Task<Version> ret = Task.supplyAsync(() -> version);
|
||||
List<String> stages = new ArrayList<>();
|
||||
for (Object value : settings.values()) {
|
||||
if (value instanceof RemoteVersion) {
|
||||
RemoteVersion remoteVersion = (RemoteVersion) value;
|
||||
for (Object value : settings.asStringMap().values()) {
|
||||
if (value instanceof RemoteVersion remoteVersion) {
|
||||
ret = ret.thenComposeAsync(version -> dependencyManager.installLibraryAsync(version, remoteVersion));
|
||||
stages.add(String.format("hmcl.install.%s:%s", remoteVersion.getLibraryId(), remoteVersion.getSelfVersion()));
|
||||
if ("game".equals(remoteVersion.getLibraryId())) {
|
||||
stages.add("hmcl.install.libraries");
|
||||
stages.add("hmcl.install.assets");
|
||||
}
|
||||
} else if (value instanceof RemoveVersionAction) {
|
||||
ret = ret.thenComposeAsync(version -> dependencyManager.removeLibraryAsync(version, ((RemoveVersionAction) value).libraryId));
|
||||
} else if (value instanceof RemoveVersionAction removeVersionAction) {
|
||||
ret = ret.thenComposeAsync(version -> dependencyManager.removeLibraryAsync(version, removeVersionAction.libraryId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +96,7 @@ public final class UpdateInstallerWizardProvider implements WizardProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
|
||||
public Node createPage(WizardController controller, int step, SettingsMap settings) {
|
||||
switch (step) {
|
||||
case 0:
|
||||
return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer." + libraryId)), gameVersion, downloadProvider, libraryId, () -> {
|
||||
|
||||
+11
-13
@@ -28,8 +28,7 @@ import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -45,36 +44,37 @@ public final class VanillaInstallWizardProvider implements WizardProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Map<String, Object> settings) {
|
||||
settings.put(PROFILE, profile);
|
||||
public void start(SettingsMap settings) {
|
||||
settings.put(ModpackPage.PROFILE, profile);
|
||||
}
|
||||
|
||||
private Task<Void> finishVersionDownloadingAsync(Map<String, Object> settings) {
|
||||
private Task<Void> finishVersionDownloadingAsync(SettingsMap settings) {
|
||||
GameBuilder builder = dependencyManager.gameBuilder();
|
||||
|
||||
String name = (String) settings.get("name");
|
||||
builder.name(name);
|
||||
builder.gameVersion(((RemoteVersion) settings.get("game")).getGameVersion());
|
||||
|
||||
for (Map.Entry<String, Object> entry : settings.entrySet())
|
||||
if (!"game".equals(entry.getKey()) && entry.getValue() instanceof RemoteVersion)
|
||||
builder.version((RemoteVersion) entry.getValue());
|
||||
settings.asStringMap().forEach((key, value) -> {
|
||||
if (!"game".equals(key) && value instanceof RemoteVersion remoteVersion)
|
||||
builder.version(remoteVersion);
|
||||
});
|
||||
|
||||
return builder.buildAsync().whenComplete(any -> profile.getRepository().refreshVersions())
|
||||
.thenRunAsync(Schedulers.javafx(), () -> profile.setSelectedVersion(name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
public Object finish(SettingsMap settings) {
|
||||
settings.put("title", i18n("install.new_game.installation"));
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_callback", (FailureCallback) (settings1, exception, next) -> UpdateInstallerWizardProvider.alertFailureMessage(exception, next));
|
||||
settings.put(FailureCallback.KEY, (settings1, exception, next) -> UpdateInstallerWizardProvider.alertFailureMessage(exception, next));
|
||||
|
||||
return finishVersionDownloadingAsync(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
|
||||
public Node createPage(WizardController controller, int step, SettingsMap settings) {
|
||||
switch (step) {
|
||||
case 0:
|
||||
return new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.game")), "", downloadProvider, "game",
|
||||
@@ -88,6 +88,4 @@ public final class VanillaInstallWizardProvider implements WizardProvider {
|
||||
public boolean cancel() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static final String PROFILE = "PROFILE";
|
||||
}
|
||||
@@ -54,12 +54,12 @@ 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.Holder;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -124,7 +124,7 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
public void cleanup(SettingsMap settings) {
|
||||
settings.remove(libraryId);
|
||||
}
|
||||
|
||||
|
||||
@@ -35,10 +35,10 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.io.Zipper;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -55,26 +55,25 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Map<String, Object> settings) {
|
||||
public void start(SettingsMap settings) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> whitelist = (List<String>) settings.get(ModpackFileSelectionPage.MODPACK_FILE_SELECTION);
|
||||
File modpackFile = (File) settings.get(ModpackInfoPage.MODPACK_FILE);
|
||||
ModpackExportInfo exportInfo = (ModpackExportInfo) settings.get(ModpackInfoPage.MODPACK_INFO);
|
||||
public Object finish(SettingsMap settings) {
|
||||
List<String> whitelist = settings.get(ModpackFileSelectionPage.MODPACK_FILE_SELECTION);
|
||||
Path modpackFile = settings.get(ModpackInfoPage.MODPACK_FILE);
|
||||
ModpackExportInfo exportInfo = settings.get(ModpackInfoPage.MODPACK_INFO);
|
||||
exportInfo.setWhitelist(whitelist);
|
||||
String modpackType = (String) settings.get(ModpackTypeSelectionPage.MODPACK_TYPE);
|
||||
String modpackType = settings.get(ModpackTypeSelectionPage.MODPACK_TYPE);
|
||||
|
||||
return exportWithLauncher(modpackType, exportInfo, modpackFile);
|
||||
}
|
||||
|
||||
private Task<?> exportWithLauncher(String modpackType, ModpackExportInfo exportInfo, File modpackFile) {
|
||||
private Task<?> exportWithLauncher(String modpackType, ModpackExportInfo exportInfo, Path modpackFile) {
|
||||
Path launcherJar = JarUtils.thisJarPath();
|
||||
boolean packWithLauncher = exportInfo.isPackWithLauncher() && launcherJar != null;
|
||||
return new Task<Object>() {
|
||||
File tempModpack;
|
||||
return new Task<>() {
|
||||
Path tempModpack;
|
||||
Task<?> exportTask;
|
||||
|
||||
{
|
||||
@@ -88,9 +87,9 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
|
||||
@Override
|
||||
public void preExecute() throws Exception {
|
||||
File dest;
|
||||
Path dest;
|
||||
if (packWithLauncher) {
|
||||
dest = tempModpack = Files.createTempFile("hmcl", ".zip").toFile();
|
||||
dest = tempModpack = Files.createTempFile("hmcl", ".zip");
|
||||
} else {
|
||||
dest = modpackFile;
|
||||
}
|
||||
@@ -122,7 +121,7 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
if (!packWithLauncher) return;
|
||||
try (Zipper zip = new Zipper(modpackFile.toPath())) {
|
||||
try (Zipper zip = new Zipper(modpackFile)) {
|
||||
Config exported = new Config();
|
||||
|
||||
exported.setBackgroundImageType(config().getBackgroundImageType());
|
||||
@@ -165,7 +164,7 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
};
|
||||
}
|
||||
|
||||
private Task<?> exportAsMcbbs(ModpackExportInfo exportInfo, File modpackFile) {
|
||||
private Task<?> exportAsMcbbs(ModpackExportInfo exportInfo, Path modpackFile) {
|
||||
return new Task<Void>() {
|
||||
Task<?> dependency = null;
|
||||
|
||||
@@ -185,7 +184,7 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
};
|
||||
}
|
||||
|
||||
private Task<?> exportAsMultiMC(ModpackExportInfo exportInfo, File modpackFile) {
|
||||
private Task<?> exportAsMultiMC(ModpackExportInfo exportInfo, Path modpackFile) {
|
||||
return new Task<Void>() {
|
||||
Task<?> dependency;
|
||||
|
||||
@@ -233,7 +232,7 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
};
|
||||
}
|
||||
|
||||
private Task<?> exportAsServer(ModpackExportInfo exportInfo, File modpackFile) {
|
||||
private Task<?> exportAsServer(ModpackExportInfo exportInfo, Path modpackFile) {
|
||||
return new Task<Void>() {
|
||||
Task<?> dependency;
|
||||
|
||||
@@ -253,7 +252,7 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
};
|
||||
}
|
||||
|
||||
private Task<?> exportAsModrinth(ModpackExportInfo exportInfo, File modpackFile) {
|
||||
private Task<?> exportAsModrinth(ModpackExportInfo exportInfo, Path modpackFile) {
|
||||
return new Task<Void>() {
|
||||
Task<?> dependency;
|
||||
|
||||
@@ -279,17 +278,13 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
|
||||
switch (step) {
|
||||
case 0:
|
||||
return new ModpackTypeSelectionPage(controller);
|
||||
case 1:
|
||||
return new ModpackInfoPage(controller, profile.getRepository(), version);
|
||||
case 2:
|
||||
return new ModpackFileSelectionPage(controller, profile, version, ModAdviser::suggestMod);
|
||||
default:
|
||||
throw new IllegalArgumentException("step");
|
||||
}
|
||||
public Node createPage(WizardController controller, int step, SettingsMap settings) {
|
||||
return switch (step) {
|
||||
case 0 -> new ModpackTypeSelectionPage(controller);
|
||||
case 1 -> new ModpackInfoPage(controller, profile.getRepository(), version);
|
||||
case 2 -> new ModpackFileSelectionPage(controller, profile, version, ModAdviser::suggestMod);
|
||||
default -> throw new IllegalArgumentException("step");
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -33,10 +33,13 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -45,6 +48,7 @@ import java.util.Objects;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
@@ -80,16 +84,18 @@ public final class ModpackFileSelectionPage extends BorderPane implements Wizard
|
||||
this.setBottom(nextPane);
|
||||
}
|
||||
|
||||
private CheckBoxTreeItem<String> getTreeItem(File file, String basePath) {
|
||||
if (!file.exists())
|
||||
private CheckBoxTreeItem<String> getTreeItem(Path file, String basePath) {
|
||||
if (Files.notExists(file))
|
||||
return null;
|
||||
|
||||
boolean isDirectory = Files.isDirectory(file);
|
||||
|
||||
ModAdviser.ModSuggestion state = ModAdviser.ModSuggestion.SUGGESTED;
|
||||
if (basePath.length() > "minecraft/".length()) {
|
||||
state = adviser.advise(StringUtils.substringAfter(basePath, "minecraft/") + (file.isDirectory() ? "/" : ""), file.isDirectory());
|
||||
if (file.isFile() && Objects.equals(FileUtils.getNameWithoutExtension(file), version)) // Ignore <version>.json, <version>.jar
|
||||
state = adviser.advise(StringUtils.substringAfter(basePath, "minecraft/") + (isDirectory ? "/" : ""), isDirectory);
|
||||
if (!isDirectory && Objects.equals(FileUtils.getNameWithoutExtension(file), version))
|
||||
state = ModAdviser.ModSuggestion.HIDDEN;
|
||||
if (file.isDirectory() && Objects.equals(file.getName(), version + "-natives")) // Ignore <version>-natives
|
||||
if (isDirectory && Objects.equals(FileUtils.getName(file), version + "-natives")) // Ignore <version>-natives
|
||||
state = ModAdviser.ModSuggestion.HIDDEN;
|
||||
if (state == ModAdviser.ModSuggestion.HIDDEN)
|
||||
return null;
|
||||
@@ -99,11 +105,10 @@ public final class ModpackFileSelectionPage extends BorderPane implements Wizard
|
||||
if (state == ModAdviser.ModSuggestion.SUGGESTED)
|
||||
node.setSelected(true);
|
||||
|
||||
if (file.isDirectory()) {
|
||||
File[] files = file.listFiles();
|
||||
if (files != null) {
|
||||
for (File it : files) {
|
||||
CheckBoxTreeItem<String> subNode = getTreeItem(it, basePath + "/" + it.getName());
|
||||
if (isDirectory) {
|
||||
try (var stream = Files.list(file)) {
|
||||
stream.forEach(it -> {
|
||||
CheckBoxTreeItem<String> subNode = getTreeItem(it, basePath + "/" + FileUtils.getName(it));
|
||||
if (subNode != null) {
|
||||
node.setSelected(subNode.isSelected() || node.isSelected());
|
||||
if (!subNode.isSelected()) {
|
||||
@@ -111,8 +116,11 @@ public final class ModpackFileSelectionPage extends BorderPane implements Wizard
|
||||
}
|
||||
node.getChildren().add(subNode);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to list contents of " + file, e);
|
||||
}
|
||||
|
||||
if (!node.isSelected()) node.setIndeterminate(false);
|
||||
|
||||
// Empty folder need not to be displayed.
|
||||
@@ -155,7 +163,7 @@ public final class ModpackFileSelectionPage extends BorderPane implements Wizard
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
public void cleanup(SettingsMap settings) {
|
||||
controller.getSettings().remove(MODPACK_FILE_SELECTION);
|
||||
}
|
||||
|
||||
@@ -171,7 +179,7 @@ public final class ModpackFileSelectionPage extends BorderPane implements Wizard
|
||||
return i18n("modpack.wizard.step.2.title");
|
||||
}
|
||||
|
||||
public static final String MODPACK_FILE_SELECTION = "modpack.accepted";
|
||||
public static final SettingsMap.Key<List<String>> MODPACK_FILE_SELECTION = new SettingsMap.Key<>("modpack.accepted");
|
||||
private static final Map<String, String> TRANSLATION = mapOf(
|
||||
pair("minecraft/hmclversion.cfg", i18n("modpack.files.hmclversion_cfg")),
|
||||
pair("minecraft/servers.dat", i18n("modpack.files.servers_dat")),
|
||||
|
||||
@@ -41,11 +41,13 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.SystemInfo;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@@ -56,7 +58,6 @@ import static org.jackhuang.hmcl.ui.export.ModpackTypeSelectionPage.MODPACK_TYPE
|
||||
import static org.jackhuang.hmcl.ui.export.ModpackTypeSelectionPage.MODPACK_TYPE_MODRINTH;
|
||||
import static org.jackhuang.hmcl.ui.export.ModpackTypeSelectionPage.MODPACK_TYPE_SERVER;
|
||||
import static org.jackhuang.hmcl.util.DataSizeUnit.MEGABYTES;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class ModpackInfoPage extends Control implements WizardPage {
|
||||
@@ -87,10 +88,12 @@ public final class ModpackInfoPage extends Control implements WizardPage {
|
||||
public ModpackInfoPage(WizardController controller, HMCLGameRepository gameRepository, String version) {
|
||||
this.controller = controller;
|
||||
this.gameRepository = gameRepository;
|
||||
this.options = tryCast(controller.getSettings().get(MODPACK_INFO_OPTION), ModpackExportInfo.Options.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Settings.MODPACK_INFO_OPTION is required"));
|
||||
this.options = controller.getSettings().get(MODPACK_INFO_OPTION);
|
||||
this.versionName = version;
|
||||
|
||||
if (this.options == null)
|
||||
throw new IllegalArgumentException("Settings.MODPACK_INFO_OPTION is required");
|
||||
|
||||
name.set(version);
|
||||
author.set(Optional.ofNullable(Accounts.getSelectedAccount()).map(Account::getUsername).orElse(""));
|
||||
|
||||
@@ -110,9 +113,9 @@ public final class ModpackInfoPage extends Control implements WizardPage {
|
||||
fileChooser.setInitialFileName(name.get() + ".mrpack");
|
||||
} else {
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
|
||||
fileChooser.setInitialFileName(name.get() + ".zip");
|
||||
fileChooser.setInitialFileName(name.get() + ".zip");
|
||||
}
|
||||
File file = fileChooser.showSaveDialog(Controllers.getStage());
|
||||
Path file = FileUtils.toPath(fileChooser.showSaveDialog(Controllers.getStage()));
|
||||
if (file == null) {
|
||||
controller.onEnd();
|
||||
return;
|
||||
@@ -146,7 +149,7 @@ public final class ModpackInfoPage extends Control implements WizardPage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
public void cleanup(SettingsMap settings) {
|
||||
controller.getSettings().remove(MODPACK_INFO);
|
||||
}
|
||||
|
||||
@@ -160,9 +163,9 @@ public final class ModpackInfoPage extends Control implements WizardPage {
|
||||
return new ModpackInfoPageSkin(this);
|
||||
}
|
||||
|
||||
public static final String MODPACK_INFO = "modpack.info";
|
||||
public static final String MODPACK_FILE = "modpack.file";
|
||||
public static final String MODPACK_INFO_OPTION = "modpack.info.option";
|
||||
public static final SettingsMap.Key<ModpackExportInfo> MODPACK_INFO = new SettingsMap.Key<>("modpack.info");
|
||||
public static final SettingsMap.Key<Path> MODPACK_FILE = new SettingsMap.Key<>("modpack.file");
|
||||
public static final SettingsMap.Key<ModpackExportInfo.Options> MODPACK_INFO_OPTION = new SettingsMap.Key<>("modpack.info.option");
|
||||
|
||||
public static class ModpackInfoPageSkin extends SkinBase<ModpackInfoPage> {
|
||||
private ObservableList<Node> originList;
|
||||
@@ -189,7 +192,8 @@ public final class ModpackInfoPage extends Control implements WizardPage {
|
||||
Hyperlink hyperlink = new Hyperlink(i18n("modpack.wizard.step.initialization.server"));
|
||||
hyperlink.setOnAction(e -> FXUtils.openLink(Metadata.DOCS_URL + "/modpack/serverpack.html"));
|
||||
borderPane.setTop(hyperlink);
|
||||
} if (skinnable.controller.getSettings().get(MODPACK_TYPE) == MODPACK_TYPE_MODRINTH) {
|
||||
}
|
||||
if (skinnable.controller.getSettings().get(MODPACK_TYPE) == MODPACK_TYPE_MODRINTH) {
|
||||
HintPane pane = new HintPane(MessageDialogPane.MessageType.INFO);
|
||||
pane.setText(i18n("modpack.wizard.step.initialization.modrinth.info"));
|
||||
borderPane.setTop(pane);
|
||||
@@ -381,7 +385,7 @@ public final class ModpackInfoPage extends Control implements WizardPage {
|
||||
button.setMaxHeight(16);
|
||||
pane.setRight(button);
|
||||
}
|
||||
|
||||
|
||||
if (skinnable.options.isRequireNoCreateRemoteFiles()) {
|
||||
BorderPane noCreateRemoteFiles = new BorderPane();
|
||||
noCreateRemoteFiles.setLeft(new Label(i18n("modpack.wizard.step.initialization.no_create_remote_files")));
|
||||
|
||||
@@ -33,8 +33,7 @@ import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.export.ModpackInfoPage.MODPACK_INFO_OPTION;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -89,7 +88,7 @@ public final class ModpackTypeSelectionPage extends VBox implements WizardPage {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
public void cleanup(SettingsMap settings) {
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -97,7 +96,7 @@ public final class ModpackTypeSelectionPage extends VBox implements WizardPage {
|
||||
return i18n("modpack.wizard.step.3.title");
|
||||
}
|
||||
|
||||
public static final String MODPACK_TYPE = "modpack.type";
|
||||
public static final SettingsMap.Key<String> MODPACK_TYPE = new SettingsMap.Key<>("modpack.type");
|
||||
|
||||
public static final String MODPACK_TYPE_MCBBS = "mcbbs";
|
||||
public static final String MODPACK_TYPE_MULTIMC = "multimc";
|
||||
|
||||
+2
-2
@@ -287,7 +287,7 @@ public class PartialInflaterInputStream extends FilterInputStream {
|
||||
* the mark position becomes invalid.
|
||||
* @see InputStream#reset()
|
||||
*/
|
||||
public synchronized void mark(int readlimit) {
|
||||
public void mark(int readlimit) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -302,7 +302,7 @@ public class PartialInflaterInputStream extends FilterInputStream {
|
||||
* @see InputStream#mark(int)
|
||||
* @see IOException
|
||||
*/
|
||||
public synchronized void reset() throws IOException {
|
||||
public void reset() throws IOException {
|
||||
throw new IOException("mark/reset not supported");
|
||||
}
|
||||
}
|
||||
@@ -52,8 +52,9 @@ import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.function.Consumer;
|
||||
@@ -340,8 +341,8 @@ public final class JavaDownloadDialog extends StackPane {
|
||||
if (StringUtils.isBlank(fileInfo.getDirectDownloadUri()))
|
||||
throw new IOException("Missing download URI: " + json);
|
||||
|
||||
File targetFile = File.createTempFile("hmcl-java-", "." + version.getArchiveType());
|
||||
targetFile.deleteOnExit();
|
||||
Path targetFile = Files.createTempFile("hmcl-java-", "." + version.getArchiveType());
|
||||
targetFile.toFile().deleteOnExit();
|
||||
|
||||
Task<FileDownloadTask.IntegrityCheck> getIntegrityCheck;
|
||||
if (StringUtils.isNotBlank(fileInfo.getChecksum()))
|
||||
@@ -363,8 +364,8 @@ public final class JavaDownloadDialog extends StackPane {
|
||||
return getIntegrityCheck
|
||||
.thenComposeAsync(integrityCheck ->
|
||||
new FileDownloadTask(downloadProvider.injectURLWithCandidates(fileInfo.getDirectDownloadUri()),
|
||||
targetFile.toPath(), integrityCheck).setName(fileInfo.getFileName()))
|
||||
.thenSupplyAsync(targetFile::toPath);
|
||||
targetFile, integrityCheck).setName(fileInfo.getFileName()))
|
||||
.thenSupplyAsync(() -> targetFile);
|
||||
})
|
||||
.whenComplete(Schedulers.javafx(), ((result, exception) -> {
|
||||
if (exception == null) {
|
||||
|
||||
@@ -45,13 +45,14 @@ import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.UnsupportedPlatformException;
|
||||
import org.jackhuang.hmcl.util.tree.ArchiveFileTree;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
@@ -79,19 +80,19 @@ public final class JavaManagementPage extends ListPageBase<JavaManagementPage.Ja
|
||||
}
|
||||
|
||||
FXUtils.applyDragListener(this, it -> {
|
||||
String name = it.getName();
|
||||
return it.isDirectory() || name.endsWith(".zip") || name.endsWith(".tar.gz") || name.equals(OperatingSystem.CURRENT_OS.getJavaExecutable());
|
||||
String name = FileUtils.getName(it);
|
||||
return Files.isDirectory(it) || name.endsWith(".zip") || name.endsWith(".tar.gz") || name.equals(OperatingSystem.CURRENT_OS.getJavaExecutable());
|
||||
}, files -> {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
onAddJavaHome(file.toPath());
|
||||
for (Path file : files) {
|
||||
if (Files.isDirectory(file)) {
|
||||
onAddJavaHome(file);
|
||||
} else {
|
||||
String fileName = file.getName();
|
||||
String fileName = FileUtils.getName(file);
|
||||
|
||||
if (fileName.equals(OperatingSystem.CURRENT_OS.getJavaExecutable())) {
|
||||
onAddJavaBinary(file.toPath());
|
||||
onAddJavaBinary(file);
|
||||
} else if (fileName.endsWith(".zip") || fileName.endsWith(".tar.gz")) {
|
||||
onInstallArchive(file.toPath());
|
||||
onInstallArchive(file);
|
||||
} else {
|
||||
throw new AssertionError("Unreachable code");
|
||||
}
|
||||
@@ -110,9 +111,9 @@ public final class JavaManagementPage extends ListPageBase<JavaManagementPage.Ja
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Java", "java.exe"));
|
||||
chooser.setTitle(i18n("settings.game.java_directory.choose"));
|
||||
File file = chooser.showOpenDialog(Controllers.getStage());
|
||||
Path file = FileUtils.toPath(chooser.showOpenDialog(Controllers.getStage()));
|
||||
if (file != null) {
|
||||
JavaManager.getAddJavaTask(file.toPath()).whenComplete(Schedulers.javafx(), exception -> {
|
||||
JavaManager.getAddJavaTask(file).whenComplete(Schedulers.javafx(), exception -> {
|
||||
if (exception != null) {
|
||||
LOG.warning("Failed to add java", exception);
|
||||
Controllers.dialog(i18n("java.add.failed"), i18n("message.error"), MessageDialogPane.MessageType.ERROR);
|
||||
|
||||
@@ -72,6 +72,7 @@ import org.jackhuang.hmcl.util.Holder;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
|
||||
@@ -115,6 +116,10 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
ImageView titleIcon = new ImageView(FXUtils.newBuiltinImage("/assets/img/icon-title.png"));
|
||||
Label titleLabel = new Label(Metadata.FULL_TITLE);
|
||||
if (I18n.isUpsideDown()) {
|
||||
titleIcon.setRotate(180);
|
||||
titleLabel.setRotate(180);
|
||||
}
|
||||
titleLabel.getStyleClass().add("jfx-decorator-title");
|
||||
titleNode.getChildren().setAll(titleIcon, titleLabel);
|
||||
|
||||
@@ -208,20 +213,11 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
launchPane.getStyleClass().add("launch-pane");
|
||||
launchPane.setMaxWidth(230);
|
||||
launchPane.setMaxHeight(55);
|
||||
launchPane.setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY();
|
||||
if (deltaY == 0)
|
||||
return;
|
||||
|
||||
FXUtils.onScroll(launchPane, versions, list -> {
|
||||
String currentId = getCurrentGame();
|
||||
int index = Lang.indexWhere(versions, instance -> instance.getId().equals(currentId));
|
||||
if (index < 0) return;
|
||||
if (deltaY > 0) // up
|
||||
index--;
|
||||
else // down
|
||||
index++;
|
||||
profile.setSelectedVersion(versions.get((index + versions.size()) % versions.size()).getId());
|
||||
});
|
||||
return Lang.indexWhere(list, instance -> instance.getId().equals(currentId));
|
||||
}, it -> profile.setSelectedVersion(it.getId()));
|
||||
|
||||
StackPane.setAlignment(launchPane, Pos.BOTTOM_RIGHT);
|
||||
{
|
||||
JFXButton launchButton = new JFXButton();
|
||||
@@ -435,6 +431,10 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
return state;
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
public String getCurrentGame() {
|
||||
return currentGame.get();
|
||||
}
|
||||
@@ -447,6 +447,10 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
this.currentGame.set(currentGame);
|
||||
}
|
||||
|
||||
public ObservableList<Version> getVersions() {
|
||||
return versions;
|
||||
}
|
||||
|
||||
public boolean isShowUpdate() {
|
||||
return showUpdate.get();
|
||||
}
|
||||
|
||||
@@ -174,6 +174,7 @@ public class PersonalizationPage extends StackPane {
|
||||
}
|
||||
|
||||
Label textOpacity = new Label();
|
||||
FXUtils.setLimitWidth(textOpacity, 35);
|
||||
|
||||
StringBinding valueBinding = Bindings.createStringBinding(() -> ((int) slider.getValue()) + "%", slider.valueProperty());
|
||||
textOpacity.textProperty().bind(valueBinding);
|
||||
|
||||
@@ -51,7 +51,8 @@ import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
@@ -92,16 +93,16 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
if (mainPage == null) {
|
||||
MainPage mainPage = new MainPage();
|
||||
FXUtils.applyDragListener(mainPage,
|
||||
file -> ModpackHelper.isFileModpackByExtension(file) || NBTFileType.isNBTFileByExtension(file.toPath()),
|
||||
file -> ModpackHelper.isFileModpackByExtension(file) || NBTFileType.isNBTFileByExtension(file),
|
||||
modpacks -> {
|
||||
File file = modpacks.get(0);
|
||||
Path file = modpacks.get(0);
|
||||
if (ModpackHelper.isFileModpackByExtension(file)) {
|
||||
Controllers.getDecorator().startWizard(
|
||||
new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), file),
|
||||
i18n("install.modpack"));
|
||||
} else if (NBTFileType.isNBTFileByExtension(file.toPath())) {
|
||||
} else if (NBTFileType.isNBTFileByExtension(file)) {
|
||||
try {
|
||||
Controllers.navigate(new NBTEditorPage(file.toPath()));
|
||||
Controllers.navigate(new NBTEditorPage(file));
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Fail to open nbt file", e);
|
||||
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e),
|
||||
@@ -153,6 +154,10 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
Versions.modifyGameSettings(profile, version);
|
||||
}
|
||||
});
|
||||
FXUtils.onScroll(gameListItem, getSkinnable().getMainPage().getVersions(), list -> {
|
||||
String currentId = getSkinnable().getMainPage().getCurrentGame();
|
||||
return Lang.indexWhere(list, instance -> instance.getId().equals(currentId));
|
||||
}, it -> getSkinnable().getMainPage().getProfile().setSelectedVersion(it.getId()));
|
||||
|
||||
// third item in left sidebar
|
||||
AdvancedListItem gameItem = new AdvancedListItem();
|
||||
@@ -193,8 +198,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
.add(downloadItem)
|
||||
.startCategory(i18n("settings.launcher.general").toUpperCase(Locale.ROOT))
|
||||
.add(launcherSettingsItem)
|
||||
.add(chatItem)
|
||||
;
|
||||
.add(chatItem);
|
||||
|
||||
// the root page, with the sidebar in left, navigator in center.
|
||||
setLeft(sideBar);
|
||||
@@ -211,14 +215,13 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
checkedModpack = true;
|
||||
|
||||
if (repository.getVersionCount() == 0) {
|
||||
File modpackFile = new File("modpack.zip").getAbsoluteFile();
|
||||
if (modpackFile.exists()) {
|
||||
Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(modpackFile.toPath()))
|
||||
Path modpackFile = Metadata.CURRENT_DIRECTORY.resolve("modpack.zip");
|
||||
if (Files.exists(modpackFile)) {
|
||||
Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(modpackFile))
|
||||
.thenApplyAsync(
|
||||
encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding))
|
||||
encoding -> ModpackHelper.readModpackManifest(modpackFile, encoding))
|
||||
.thenApplyAsync(modpack -> ModpackHelper
|
||||
.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(),
|
||||
modpack)
|
||||
.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
|
||||
.executor())
|
||||
.thenAcceptAsync(Schedulers.javafx(), executor -> {
|
||||
Controllers.taskDialog(executor, i18n("modpack.installing"), TaskCancellationAction.NO_CANCEL);
|
||||
|
||||
@@ -33,12 +33,11 @@ import org.jackhuang.hmcl.upgrade.UpdateChannel;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
import org.jackhuang.hmcl.util.i18n.SupportedLocale;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.tukaani.xz.XZInputStream;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -66,7 +65,7 @@ public final class SettingsPage extends SettingsView {
|
||||
FXUtils.smoothScrolling(scroll);
|
||||
|
||||
// ==== Languages ====
|
||||
cboLanguage.getItems().setAll(Locales.LOCALES);
|
||||
cboLanguage.getItems().setAll(SupportedLocale.getSupportedLocales());
|
||||
selectedItemPropertyFor(cboLanguage).bindBidirectional(config().localizationProperty());
|
||||
|
||||
disableAutoGameOptionsPane.selectedProperty().bindBidirectional(config().disableAutoGameOptionsProperty());
|
||||
@@ -254,6 +253,9 @@ public final class SettingsPage extends SettingsView {
|
||||
|
||||
@Override
|
||||
protected void clearCacheDirectory() {
|
||||
FileUtils.cleanDirectoryQuietly(new File(Settings.instance().getCommonDirectory(), "cache"));
|
||||
String commonDirectory = Settings.instance().getCommonDirectory();
|
||||
if (commonDirectory != null) {
|
||||
FileUtils.cleanDirectoryQuietly(Path.of(commonDirectory, "cache"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ import org.jackhuang.hmcl.ui.construct.ComponentSublist;
|
||||
import org.jackhuang.hmcl.ui.construct.MultiFileItem;
|
||||
import org.jackhuang.hmcl.ui.construct.OptionToggleButton;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
import org.jackhuang.hmcl.util.i18n.SupportedLocale;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -162,9 +162,8 @@ public abstract class SettingsView extends StackPane {
|
||||
));
|
||||
|
||||
{
|
||||
JFXButton cleanButton = new JFXButton(i18n("launcher.cache_directory.clean"));
|
||||
JFXButton cleanButton = FXUtils.newBorderButton(i18n("launcher.cache_directory.clean"));
|
||||
cleanButton.setOnAction(e -> clearCacheDirectory());
|
||||
cleanButton.getStyleClass().add("jfx-button-border");
|
||||
|
||||
fileCommonLocationSublist.setHeaderRight(cleanButton);
|
||||
}
|
||||
@@ -216,9 +215,8 @@ public abstract class SettingsView extends StackPane {
|
||||
if (LOG.getLogFile() == null)
|
||||
openLogFolderButton.setDisable(true);
|
||||
|
||||
JFXButton logButton = new JFXButton(i18n("settings.launcher.launcher_log.export"));
|
||||
JFXButton logButton = FXUtils.newBorderButton(i18n("settings.launcher.launcher_log.export"));
|
||||
logButton.setOnAction(e -> onExportLogs());
|
||||
logButton.getStyleClass().add("jfx-button-border");
|
||||
|
||||
HBox buttonBox = new HBox();
|
||||
buttonBox.setSpacing(10);
|
||||
@@ -236,7 +234,7 @@ public abstract class SettingsView extends StackPane {
|
||||
}
|
||||
|
||||
public void openLogFolder() {
|
||||
FXUtils.openFolder(LOG.getLogFile().getParent().toFile());
|
||||
FXUtils.openFolder(LOG.getLogFile().getParent());
|
||||
}
|
||||
|
||||
protected abstract void onUpdate();
|
||||
|
||||
@@ -27,13 +27,14 @@ import javafx.scene.layout.HBox;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
@@ -68,9 +69,7 @@ public final class NBTEditorPage extends SpinnerPane implements DecoratorPage {
|
||||
actions.setPadding(new Insets(8));
|
||||
actions.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
JFXButton saveButton = new JFXButton(i18n("button.save"));
|
||||
saveButton.getStyleClass().add("jfx-button-raised");
|
||||
saveButton.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
JFXButton saveButton = FXUtils.newRaisedButton(i18n("button.save"));
|
||||
saveButton.setOnAction(e -> {
|
||||
try {
|
||||
save();
|
||||
@@ -80,9 +79,7 @@ public final class NBTEditorPage extends SpinnerPane implements DecoratorPage {
|
||||
}
|
||||
});
|
||||
|
||||
JFXButton cancelButton = new JFXButton(i18n("button.cancel"));
|
||||
cancelButton.getStyleClass().add("jfx-button-raised");
|
||||
cancelButton.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
JFXButton cancelButton = FXUtils.newRaisedButton(i18n("button.cancel"));
|
||||
cancelButton.setOnAction(e -> fireEvent(new PageCloseEvent()));
|
||||
onEscPressed(this, cancelButton::fire);
|
||||
|
||||
|
||||
@@ -25,7 +25,11 @@ import com.github.steveice10.opennbt.tag.builtin.Tag;
|
||||
import kala.compress.utils.BoundedInputStream;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -42,8 +42,9 @@ import org.jackhuang.hmcl.ui.construct.OptionToggleButton;
|
||||
import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -68,7 +69,7 @@ public final class ProfilePage extends BorderPane implements DecoratorPage {
|
||||
|
||||
state.set(State.fromTitle(profile == null ? i18n("profile.new") : i18n("profile") + " - " + profileDisplayName));
|
||||
location = new SimpleStringProperty(this, "location",
|
||||
Optional.ofNullable(profile).map(Profile::getGameDir).map(File::getAbsolutePath).orElse(".minecraft"));
|
||||
Optional.ofNullable(profile).map(Profile::getGameDir).map(FileUtils::getAbsolutePath).orElse(".minecraft"));
|
||||
|
||||
ScrollPane scroll = new ScrollPane();
|
||||
this.setCenter(scroll);
|
||||
@@ -153,13 +154,13 @@ public final class ProfilePage extends BorderPane implements DecoratorPage {
|
||||
profile.setName(txtProfileName.getText());
|
||||
profile.setUseRelativePath(toggleUseRelativePath.isSelected());
|
||||
if (StringUtils.isNotBlank(getLocation())) {
|
||||
profile.setGameDir(new File(getLocation()));
|
||||
profile.setGameDir(Path.of(getLocation()));
|
||||
}
|
||||
} else {
|
||||
if (StringUtils.isBlank(getLocation())) {
|
||||
gameDir.onExplore();
|
||||
}
|
||||
Profile newProfile = new Profile(txtProfileName.getText(), new File(getLocation()));
|
||||
Profile newProfile = new Profile(txtProfileName.getText(), Path.of(getLocation()));
|
||||
newProfile.setUseRelativePath(toggleUseRelativePath.isSelected());
|
||||
Profiles.getProfiles().add(newProfile);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
private final JFXTextField txtPreLaunchCommand;
|
||||
private final JFXTextField txtPostExitCommand;
|
||||
private final OptionToggleButton noJVMArgsPane;
|
||||
private final OptionToggleButton noOptimizingJVMArgsPane;
|
||||
private final OptionToggleButton noGameCheckPane;
|
||||
private final OptionToggleButton noJVMCheckPane;
|
||||
private final OptionToggleButton noNativesPatchPane;
|
||||
@@ -183,6 +184,10 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
noJVMArgsPane = new OptionToggleButton();
|
||||
noJVMArgsPane.setTitle(i18n("settings.advanced.no_jvm_args"));
|
||||
|
||||
noOptimizingJVMArgsPane = new OptionToggleButton();
|
||||
noOptimizingJVMArgsPane.setTitle(i18n("settings.advanced.no_optimizing_jvm_args"));
|
||||
noOptimizingJVMArgsPane.disableProperty().bind(noJVMArgsPane.selectedProperty());
|
||||
|
||||
noGameCheckPane = new OptionToggleButton();
|
||||
noGameCheckPane.setTitle(i18n("settings.advanced.dont_check_game_completeness"));
|
||||
|
||||
@@ -199,7 +204,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
useNativeOpenALPane.setTitle(i18n("settings.advanced.use_native_openal"));
|
||||
|
||||
workaroundPane.getContent().setAll(
|
||||
nativesDirSublist, rendererPane, noJVMArgsPane, noGameCheckPane,
|
||||
nativesDirSublist, rendererPane, noJVMArgsPane, noOptimizingJVMArgsPane, noGameCheckPane,
|
||||
noJVMCheckPane, noNativesPatchPane
|
||||
);
|
||||
|
||||
@@ -235,6 +240,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
noGameCheckPane.selectedProperty().bindBidirectional(versionSetting.notCheckGameProperty());
|
||||
noJVMCheckPane.selectedProperty().bindBidirectional(versionSetting.notCheckJVMProperty());
|
||||
noJVMArgsPane.selectedProperty().bindBidirectional(versionSetting.noJVMArgsProperty());
|
||||
noOptimizingJVMArgsPane.selectedProperty().bindBidirectional(versionSetting.noOptimizingJVMArgsProperty());
|
||||
noNativesPatchPane.selectedProperty().bindBidirectional(versionSetting.notPatchNativesProperty());
|
||||
useNativeGLFWPane.selectedProperty().bindBidirectional(versionSetting.useNativeGLFWProperty());
|
||||
useNativeOpenALPane.selectedProperty().bindBidirectional(versionSetting.useNativeOpenALProperty());
|
||||
@@ -245,12 +251,12 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
String nativesDirName = "natives-" + Platform.SYSTEM_PLATFORM;
|
||||
if (versionId == null) {
|
||||
return String.format("%s/%s/%s",
|
||||
profile.getRepository().getBaseDirectory().toPath().resolve("versions").toAbsolutePath().normalize(),
|
||||
profile.getRepository().getBaseDirectory().resolve("versions").toAbsolutePath().normalize(),
|
||||
i18n("settings.advanced.natives_directory.default.version_id"),
|
||||
nativesDirName
|
||||
);
|
||||
} else {
|
||||
return profile.getRepository().getVersionRoot(versionId).toPath()
|
||||
return profile.getRepository().getVersionRoot(versionId)
|
||||
.toAbsolutePath().normalize()
|
||||
.resolve(nativesDirName)
|
||||
.toString();
|
||||
@@ -276,6 +282,7 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
|
||||
noGameCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckGameProperty());
|
||||
noJVMCheckPane.selectedProperty().unbindBidirectional(versionSetting.notCheckJVMProperty());
|
||||
noJVMArgsPane.selectedProperty().unbindBidirectional(versionSetting.noJVMArgsProperty());
|
||||
noOptimizingJVMArgsPane.selectedProperty().unbindBidirectional(versionSetting.noOptimizingJVMArgsProperty());
|
||||
noNativesPatchPane.selectedProperty().unbindBidirectional(versionSetting.notPatchNativesProperty());
|
||||
useNativeGLFWPane.selectedProperty().unbindBidirectional(versionSetting.useNativeGLFWProperty());
|
||||
useNativeOpenALPane.selectedProperty().unbindBidirectional(versionSetting.useNativeOpenALProperty());
|
||||
|
||||
@@ -29,7 +29,6 @@ import org.jackhuang.hmcl.ui.ListPageBase;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
@@ -54,9 +53,9 @@ public final class DatapackListPage extends ListPageBase<DatapackListPageSkin.Da
|
||||
mods -> mods.forEach(this::installSingleDatapack), this::refresh);
|
||||
}
|
||||
|
||||
private void installSingleDatapack(File datapack) {
|
||||
private void installSingleDatapack(Path datapack) {
|
||||
try {
|
||||
Datapack zip = new Datapack(datapack.toPath());
|
||||
Datapack zip = new Datapack(datapack);
|
||||
zip.loadFromZip();
|
||||
zip.installTo(worldDir);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
@@ -82,7 +81,7 @@ public final class DatapackListPage extends ListPageBase<DatapackListPageSkin.Da
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("datapack.choose_datapack"));
|
||||
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("datapack.extension"), "*.zip"));
|
||||
List<File> res = chooser.showOpenMultipleDialog(Controllers.getStage());
|
||||
List<Path> res = FileUtils.toPaths(chooser.showOpenMultipleDialog(Controllers.getStage()));
|
||||
|
||||
if (res != null)
|
||||
res.forEach(this::installSingleDatapack);
|
||||
|
||||
@@ -49,11 +49,12 @@ import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -172,14 +173,14 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
fileChooser.setTitle(i18n("button.save_as"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("file"), "*." + extension));
|
||||
fileChooser.setInitialFileName(file.getFile().getFilename());
|
||||
File dest = fileChooser.showSaveDialog(Controllers.getStage());
|
||||
Path dest = FileUtils.toPath(fileChooser.showSaveDialog(Controllers.getStage()));
|
||||
if (dest == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Controllers.taskDialog(
|
||||
Task.composeAsync(() -> {
|
||||
var task = new FileDownloadTask(file.getFile().getUrl(), dest.toPath(), file.getFile().getIntegrityCheck());
|
||||
var task = new FileDownloadTask(file.getFile().getUrl(), dest, file.getFile().getIntegrityCheck());
|
||||
task.setName(file.getName());
|
||||
return task;
|
||||
}),
|
||||
|
||||
@@ -33,7 +33,7 @@ import org.jackhuang.hmcl.ui.download.UpdateInstallerWizardProvider;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -137,12 +137,12 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
|
||||
public void installOffline() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("install.installer.install_offline.extension"), "*.jar", "*.exe"));
|
||||
File file = chooser.showOpenDialog(Controllers.getStage());
|
||||
Path file = FileUtils.toPath(chooser.showOpenDialog(Controllers.getStage()));
|
||||
if (file != null) doInstallOffline(file);
|
||||
}
|
||||
|
||||
private void doInstallOffline(File file) {
|
||||
Task<?> task = profile.getDependency().installLibraryAsync(version, file.toPath())
|
||||
private void doInstallOffline(Path file) {
|
||||
Task<?> task = profile.getDependency().installLibraryAsync(version, file)
|
||||
.thenComposeAsync(profile.getRepository()::saveAsync)
|
||||
.thenComposeAsync(profile.getRepository().refreshVersionsAsync());
|
||||
task.setName(i18n("install.installer.install_offline"));
|
||||
|
||||
@@ -39,9 +39,9 @@ import org.jackhuang.hmcl.ui.construct.PageAware;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -62,7 +62,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)), mods -> {
|
||||
mods.forEach(it -> {
|
||||
try {
|
||||
modManager.addMod(it.toPath());
|
||||
modManager.addMod(it);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
LOG.warning("Unable to parse mod file " + it, e);
|
||||
}
|
||||
@@ -117,7 +117,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("mods.choose_mod"));
|
||||
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("extension.mod"), "*.jar", "*.zip", "*.litemod"));
|
||||
List<File> res = chooser.showOpenMultipleDialog(Controllers.getStage());
|
||||
List<Path> res = FileUtils.toPaths(chooser.showOpenMultipleDialog(Controllers.getStage()));
|
||||
|
||||
if (res == null) return;
|
||||
|
||||
@@ -126,13 +126,13 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
List<String> failed = new ArrayList<>();
|
||||
|
||||
Task.runAsync(() -> {
|
||||
for (File file : res) {
|
||||
for (Path file : res) {
|
||||
try {
|
||||
modManager.addMod(file.toPath());
|
||||
succeeded.add(file.getName());
|
||||
modManager.addMod(file);
|
||||
succeeded.add(FileUtils.getName(file));
|
||||
} catch (Exception e) {
|
||||
LOG.warning("Unable to add mod " + file, e);
|
||||
failed.add(file.getName());
|
||||
failed.add(FileUtils.getName(file));
|
||||
|
||||
// Actually addMod will not throw exceptions because FileChooser has already filtered files.
|
||||
}
|
||||
@@ -175,7 +175,7 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
}
|
||||
|
||||
public void openModFolder() {
|
||||
FXUtils.openFolder(new File(profile.getRepository().getRunDirectory(versionId), "mods"));
|
||||
FXUtils.openFolder(profile.getRepository().getRunDirectory(versionId).resolve("mods"));
|
||||
}
|
||||
|
||||
public void checkUpdates() {
|
||||
|
||||
@@ -42,18 +42,15 @@ import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
@@ -77,8 +74,8 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
|
||||
public SchematicsPage() {
|
||||
FXUtils.applyDragListener(this,
|
||||
file -> currentDirectory != null && file.isFile() && file.getName().endsWith(".litematic"),
|
||||
files -> addFiles(files.stream().map(File::toPath).collect(Collectors.toList()))
|
||||
file -> currentDirectory != null && Files.isRegularFile(file) && FileUtils.getName(file).endsWith(".litematic"),
|
||||
this::addFiles
|
||||
);
|
||||
}
|
||||
|
||||
@@ -150,9 +147,9 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
fileChooser.setTitle(i18n("schematics.add"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(
|
||||
i18n("schematics"), "*.litematic"));
|
||||
List<File> files = fileChooser.showOpenMultipleDialog(Controllers.getStage());
|
||||
List<Path> files = FileUtils.toPaths(fileChooser.showOpenMultipleDialog(Controllers.getStage()));
|
||||
if (files != null && !files.isEmpty()) {
|
||||
addFiles(files.stream().map(File::toPath).collect(Collectors.toList()));
|
||||
addFiles(files);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +167,7 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.contains("/") || result.contains("\\") || !OperatingSystem.isNameValid(result)) {
|
||||
if (result.contains("/") || result.contains("\\") || !FileUtils.isNameValid(result)) {
|
||||
reject.accept(i18n("schematics.create_directory.failed.invalid_name"));
|
||||
return;
|
||||
}
|
||||
@@ -368,13 +365,13 @@ public final class SchematicsPage extends ListPageBase<SchematicsPage.Item> impl
|
||||
|
||||
@Override
|
||||
void onReveal() {
|
||||
FXUtils.openFolder(path.toFile());
|
||||
FXUtils.openFolder(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDelete() {
|
||||
try {
|
||||
FileUtils.cleanDirectory(path.toFile());
|
||||
FileUtils.cleanDirectory(path);
|
||||
Files.deleteIfExists(path);
|
||||
refresh();
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -31,9 +31,10 @@ 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.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -75,7 +76,7 @@ public class VersionIconDialog extends DialogPane {
|
||||
private void exploreIcon() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.getExtensionFilters().add(FXUtils.getImageExtensionFilter());
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
Path selectedFile = FileUtils.toPath(chooser.showOpenDialog(Controllers.getStage()));
|
||||
if (selectedFile != null) {
|
||||
try {
|
||||
profile.getRepository().setVersionIconFile(versionId, selectedFile);
|
||||
|
||||
@@ -45,7 +45,6 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -158,7 +157,7 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
|
||||
private void onBrowse(String sub) {
|
||||
FXUtils.openFolder(new File(getProfile().getRepository().getRunDirectory(getVersion()), sub));
|
||||
FXUtils.openFolder(getProfile().getRepository().getRunDirectory(getVersion()).resolve(sub));
|
||||
}
|
||||
|
||||
private void redownloadAssetIndex() {
|
||||
@@ -166,14 +165,14 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
|
||||
private void clearLibraries() {
|
||||
FileUtils.deleteDirectoryQuietly(new File(getProfile().getRepository().getBaseDirectory(), "libraries"));
|
||||
FileUtils.deleteDirectoryQuietly(getProfile().getRepository().getBaseDirectory().resolve("libraries"));
|
||||
}
|
||||
|
||||
private void clearAssets() {
|
||||
HMCLGameRepository baseDirectory = getProfile().getRepository();
|
||||
FileUtils.deleteDirectoryQuietly(new File(baseDirectory.getBaseDirectory(), "assets"));
|
||||
FileUtils.deleteDirectoryQuietly(baseDirectory.getBaseDirectory().resolve("assets"));
|
||||
if (version.get() != null) {
|
||||
FileUtils.deleteDirectoryQuietly(new File(baseDirectory.getRunDirectory(version.get().getVersion()), "resources"));
|
||||
FileUtils.deleteDirectoryQuietly(baseDirectory.getRunDirectory(version.get().getVersion()).resolve("resources"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,6 @@ import org.jackhuang.hmcl.util.platform.SystemInfo;
|
||||
import org.jackhuang.hmcl.util.platform.hardware.PhysicalMemoryStatus;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@@ -199,7 +198,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
copyGlobalPane.setLeft(label);
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
|
||||
JFXButton copyAll = new JFXButton(i18n("settings.game.copy_global.copy_all"));
|
||||
JFXButton copyAll = FXUtils.newBorderButton(i18n("settings.game.copy_global.copy_all"));
|
||||
copyAll.disableProperty().bind(modpack);
|
||||
copyGlobalPane.setRight(copyAll);
|
||||
copyAll.setOnAction(e -> Controllers.confirm(i18n("settings.game.copy_global.copy_all.confirm"), null, () -> {
|
||||
@@ -210,7 +209,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
PropertyUtils.copyProperties(profile.getGlobal(), lastVersionSetting, name -> !ignored.contains(name));
|
||||
}, null));
|
||||
copyAll.getStyleClass().add("jfx-button-border");
|
||||
BorderPane.setAlignment(copyAll, Pos.CENTER_RIGHT);
|
||||
}
|
||||
|
||||
@@ -457,7 +455,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
showAdvancedSettingPane.setLeft(label);
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
|
||||
JFXButton button = new JFXButton(i18n("settings.advanced.modify"));
|
||||
JFXButton button = FXUtils.newBorderButton(i18n("settings.advanced.modify"));
|
||||
button.setOnAction(e -> {
|
||||
if (lastVersionSetting != null) {
|
||||
if (advancedVersionSettingPage == null)
|
||||
@@ -466,7 +464,6 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
Controllers.navigate(advancedVersionSettingPage);
|
||||
}
|
||||
});
|
||||
button.getStyleClass().add("jfx-button-border");
|
||||
showAdvancedSettingPane.setRight(button);
|
||||
}
|
||||
|
||||
@@ -635,7 +632,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
versionSetting.javaVersionProperty().addListener(javaListener);
|
||||
|
||||
gameDirItem.selectedDataProperty().bindBidirectional(versionSetting.gameDirTypeProperty());
|
||||
gameDirSublist.subtitleProperty().bind(Bindings.createStringBinding(() -> Paths.get(profile.getRepository().getRunDirectory(versionId).getAbsolutePath()).normalize().toString(),
|
||||
gameDirSublist.subtitleProperty().bind(Bindings.createStringBinding(() -> profile.getRepository().getRunDirectory(versionId).toAbsolutePath().normalize().toString(),
|
||||
versionSetting.gameDirProperty(), versionSetting.gameDirTypeProperty()));
|
||||
|
||||
lastVersionSetting = versionSetting;
|
||||
|
||||
@@ -40,10 +40,10 @@ import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
@@ -88,9 +88,9 @@ public final class Versions {
|
||||
.whenComplete(Schedulers.javafx(), e -> {
|
||||
if (e == null) {
|
||||
if (version != null) {
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile, modpack.toFile(), version));
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile, modpack, version));
|
||||
} else {
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile, modpack.toFile()));
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(profile, modpack));
|
||||
}
|
||||
} else if (e instanceof CancellationException) {
|
||||
Controllers.showToast(i18n("message.cancelled"));
|
||||
@@ -119,7 +119,7 @@ public final class Versions {
|
||||
|
||||
public static CompletableFuture<String> renameVersion(Profile profile, String version) {
|
||||
return Controllers.prompt(i18n("version.manage.rename.message"), (newName, resolve, reject) -> {
|
||||
if (!OperatingSystem.isNameValid(newName)) {
|
||||
if (!FileUtils.isNameValid(newName)) {
|
||||
reject.accept(i18n("install.new_game.malformed"));
|
||||
return;
|
||||
}
|
||||
@@ -192,14 +192,14 @@ public final class Versions {
|
||||
ensureSelectedAccount(account -> {
|
||||
GameRepository repository = profile.getRepository();
|
||||
FileChooser chooser = new FileChooser();
|
||||
if (repository.getRunDirectory(id).isDirectory())
|
||||
chooser.setInitialDirectory(repository.getRunDirectory(id));
|
||||
if (Files.isDirectory(repository.getRunDirectory(id)))
|
||||
chooser.setInitialDirectory(repository.getRunDirectory(id).toFile());
|
||||
chooser.setTitle(i18n("version.launch_script.save"));
|
||||
chooser.getExtensionFilters().add(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
|
||||
? new FileChooser.ExtensionFilter(i18n("extension.bat"), "*.bat")
|
||||
: new FileChooser.ExtensionFilter(i18n("extension.sh"), "*.sh"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.ps1"), "*.ps1"));
|
||||
File file = chooser.showSaveDialog(Controllers.getStage());
|
||||
Path file = FileUtils.toPath(chooser.showSaveDialog(Controllers.getStage()));
|
||||
if (file != null)
|
||||
new LauncherHelper(profile, account, id).makeLaunchScript(file);
|
||||
});
|
||||
|
||||
@@ -147,7 +147,7 @@ public final class WorldBackupsPage extends ListPageBase<WorldBackupsPage.Backup
|
||||
WorldBackupsPage.this.getItems().sort(Comparator.naturalOrder());
|
||||
Controllers.dialog(i18n("world.backup.create.success", result.getKey()), null, MessageDialogPane.MessageType.INFO);
|
||||
} else if (exception instanceof WorldLockedException) {
|
||||
Controllers.dialog(i18n("world.backup.create.locked"), null, MessageDialogPane.MessageType.WARNING);
|
||||
Controllers.dialog(i18n("world.locked.failed"), null, MessageDialogPane.MessageType.WARNING);
|
||||
} else {
|
||||
LOG.warning("Failed to create backup", exception);
|
||||
Controllers.dialog(i18n("world.backup.create.failed", StringUtils.getStackTrace(exception)), null, MessageDialogPane.MessageType.WARNING);
|
||||
|
||||
@@ -21,11 +21,16 @@ import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.game.WorldLockedException;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -33,10 +38,12 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
public final class WorldListItem extends Control {
|
||||
private final World world;
|
||||
private final Path backupsDir;
|
||||
private final WorldListPage parent;
|
||||
|
||||
public WorldListItem(World world, Path backupsDir) {
|
||||
public WorldListItem(WorldListPage parent, World world, Path backupsDir) {
|
||||
this.world = world;
|
||||
this.backupsDir = backupsDir;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -53,16 +60,34 @@ public final class WorldListItem extends Control {
|
||||
fileChooser.setTitle(i18n("world.export.title"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("world"), "*.zip"));
|
||||
fileChooser.setInitialFileName(world.getWorldName());
|
||||
File file = fileChooser.showSaveDialog(Controllers.getStage());
|
||||
Path file = FileUtils.toPath(fileChooser.showSaveDialog(Controllers.getStage()));
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Controllers.getDecorator().startWizard(new SinglePageWizardProvider(controller -> new WorldExportPage(world, file.toPath(), controller::onFinish)));
|
||||
Controllers.getDecorator().startWizard(new SinglePageWizardProvider(controller -> new WorldExportPage(world, file, controller::onFinish)));
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
Controllers.confirm(
|
||||
i18n("button.remove.confirm"),
|
||||
i18n("world.delete"),
|
||||
() -> Task.runAsync(world::delete)
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
parent.remove(this);
|
||||
} else if (exception instanceof WorldLockedException) {
|
||||
Controllers.dialog(i18n("world.locked.failed"), null, MessageType.WARNING);
|
||||
} else {
|
||||
Controllers.dialog(i18n("world.delete.failed", StringUtils.getStackTrace(exception)), null, MessageType.WARNING);
|
||||
}
|
||||
}).start(),
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
public void reveal() {
|
||||
FXUtils.openFolder(world.getFile().toFile());
|
||||
FXUtils.openFolder(world.getFile());
|
||||
}
|
||||
|
||||
public void showManagePage() {
|
||||
|
||||
@@ -136,6 +136,7 @@ public final class WorldListItemSkin extends SkinBase<WorldListItem> {
|
||||
popupMenu.getContent().addAll(
|
||||
new MenuSeparator(),
|
||||
new IconedMenuItem(SVG.OUTPUT, i18n("world.export"), item::export, popup),
|
||||
new IconedMenuItem(SVG.DELETE, i18n("world.delete"), item::delete, popup),
|
||||
new IconedMenuItem(SVG.FOLDER_OPEN, i18n("folder.world"), item::reveal, popup));
|
||||
|
||||
JFXPopup.PopupVPosition vPosition = determineOptimalPopupPosition(root, popup);
|
||||
|
||||
@@ -29,7 +29,6 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.*;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.InvalidPathException;
|
||||
@@ -39,8 +38,8 @@ import java.util.List;
|
||||
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;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class WorldListPage extends ListPageBase<WorldListItem> implements VersionPage.VersionLoadable {
|
||||
private final BooleanProperty showAll = new SimpleBooleanProperty(this, "showAll", false);
|
||||
@@ -61,7 +60,7 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
|
||||
if (worlds != null)
|
||||
itemsProperty().setAll(worlds.stream()
|
||||
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
|
||||
.map(world -> new WorldListItem(world, backupsDir)).collect(Collectors.toList()));
|
||||
.map(world -> new WorldListItem(this, world, backupsDir)).toList());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -79,6 +78,10 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
|
||||
refresh();
|
||||
}
|
||||
|
||||
public void remove(WorldListItem item) {
|
||||
itemsProperty().remove(item);
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
if (profile == null || id == null)
|
||||
return;
|
||||
@@ -94,8 +97,10 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
|
||||
worlds = result;
|
||||
setLoading(false);
|
||||
if (exception == null) {
|
||||
itemsProperty().setAll(result.stream().filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
|
||||
.map(world -> new WorldListItem(world, backupsDir)).collect(Collectors.toList()));
|
||||
itemsProperty().setAll(result.stream()
|
||||
.filter(world -> isShowAll() || world.getGameVersion() == null || world.getGameVersion().equals(gameVersion))
|
||||
.map(world -> new WorldListItem(this, world, backupsDir))
|
||||
.collect(Collectors.toList()));
|
||||
} else {
|
||||
LOG.warning("Failed to load world list page", exception);
|
||||
}
|
||||
@@ -106,7 +111,7 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(i18n("world.import.choose"));
|
||||
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("world.extension"), "*.zip"));
|
||||
List<File> res = chooser.showOpenMultipleDialog(Controllers.getStage());
|
||||
List<Path> res = FileUtils.toPaths(chooser.showOpenMultipleDialog(Controllers.getStage()));
|
||||
|
||||
if (res == null || res.isEmpty()) return;
|
||||
installWorld(res.get(0));
|
||||
@@ -117,15 +122,15 @@ public final class WorldListPage extends ListPageBase<WorldListItem> implements
|
||||
Controllers.navigate(Controllers.getDownloadPage());
|
||||
}
|
||||
|
||||
private void installWorld(File zipFile) {
|
||||
private void installWorld(Path zipFile) {
|
||||
// Only accept one world file because user is required to confirm the new world name
|
||||
// Or too many input dialogs are popped.
|
||||
Task.supplyAsync(() -> new World(zipFile.toPath()))
|
||||
Task.supplyAsync(() -> new World(zipFile))
|
||||
.whenComplete(Schedulers.javafx(), world -> {
|
||||
Controllers.prompt(i18n("world.name.enter"), (name, resolve, reject) -> {
|
||||
Task.runAsync(() -> world.install(savesDir, name))
|
||||
.whenComplete(Schedulers.javafx(), () -> {
|
||||
itemsProperty().add(new WorldListItem(new World(savesDir.resolve(name)), backupsDir));
|
||||
itemsProperty().add(new WorldListItem(this, new World(savesDir.resolve(name)), backupsDir));
|
||||
resolve.run();
|
||||
}, e -> {
|
||||
if (e instanceof FileAlreadyExistsException)
|
||||
|
||||
@@ -117,7 +117,7 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
|
||||
JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.LEFT,
|
||||
chunkBaseMenuItem.getWidth(), 0)));
|
||||
}
|
||||
toolbar.addNavigationDrawerItem(i18n("settings.game.exploration"), SVG.FOLDER_OPEN, () -> FXUtils.openFolder(world.getFile().toFile()), null);
|
||||
toolbar.addNavigationDrawerItem(i18n("settings.game.exploration"), SVG.FOLDER_OPEN, () -> FXUtils.openFolder(world.getFile()), null);
|
||||
|
||||
BorderPane.setMargin(toolbar, new Insets(0, 0, 12, 0));
|
||||
left.setBottom(toolbar);
|
||||
|
||||
@@ -22,8 +22,8 @@ import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskListPane;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
|
||||
public abstract class AbstractWizardDisplayer implements WizardDisplayer {
|
||||
@@ -34,7 +34,7 @@ public abstract class AbstractWizardDisplayer implements WizardDisplayer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTask(Map<String, Object> settings, Task<?> task) {
|
||||
public void handleTask(SettingsMap settings, Task<?> task) {
|
||||
TaskExecutor executor = task.withRunAsync(Schedulers.javafx(), this::navigateToSuccess).executor();
|
||||
TaskListPane pane = new TaskListPane();
|
||||
pane.setExecutor(executor);
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.wizard;
|
||||
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
|
||||
public interface Navigation {
|
||||
|
||||
@@ -37,7 +36,7 @@ public interface Navigation {
|
||||
|
||||
void onCancel();
|
||||
|
||||
Map<String, Object> getSettings();
|
||||
SettingsMap getSettings();
|
||||
|
||||
enum NavigationDirection {
|
||||
START(ContainerAnimations.NONE),
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
package org.jackhuang.hmcl.ui.wizard;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class SinglePageWizardProvider implements WizardProvider {
|
||||
@@ -32,16 +32,16 @@ public class SinglePageWizardProvider implements WizardProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Map<String, Object> settings) {
|
||||
public void start(SettingsMap settings) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
public Object finish(SettingsMap settings) {
|
||||
return page.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
|
||||
public Node createPage(WizardController controller, int step, SettingsMap settings) {
|
||||
if (step != 0) throw new IllegalStateException("Step must be 0");
|
||||
|
||||
return page = provider.apply(controller);
|
||||
|
||||
+12
-12
@@ -25,10 +25,10 @@ import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CancellationException;
|
||||
|
||||
@@ -42,7 +42,7 @@ public abstract class TaskExecutorDialogWizardDisplayer extends AbstractWizardDi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTask(Map<String, Object> settings, Task<?> task) {
|
||||
public void handleTask(SettingsMap settings, Task<?> task) {
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(new TaskCancellationAction(it -> {
|
||||
it.fireEvent(new DialogCloseEvent());
|
||||
onEnd();
|
||||
@@ -51,10 +51,10 @@ public abstract class TaskExecutorDialogWizardDisplayer extends AbstractWizardDi
|
||||
pane.setTitle(i18n("message.doing"));
|
||||
if (settings.containsKey("title")) {
|
||||
Object title = settings.get("title");
|
||||
if (title instanceof StringProperty)
|
||||
pane.titleProperty().bind((StringProperty) title);
|
||||
else if (title instanceof String)
|
||||
pane.setTitle((String) title);
|
||||
if (title instanceof StringProperty titleProperty)
|
||||
pane.titleProperty().bind(titleProperty);
|
||||
else if (title instanceof String titleMessage)
|
||||
pane.setTitle(titleMessage);
|
||||
}
|
||||
|
||||
runInFX(() -> {
|
||||
@@ -63,8 +63,8 @@ public abstract class TaskExecutorDialogWizardDisplayer extends AbstractWizardDi
|
||||
public void onStop(boolean success, TaskExecutor executor) {
|
||||
runInFX(() -> {
|
||||
if (success) {
|
||||
if (settings.containsKey("success_message") && settings.get("success_message") instanceof String)
|
||||
Controllers.dialog((String) settings.get("success_message"), null, MessageType.SUCCESS, () -> onEnd());
|
||||
if (settings.get("success_message") instanceof String successMessage)
|
||||
Controllers.dialog(successMessage, null, MessageType.SUCCESS, () -> onEnd());
|
||||
else if (!settings.containsKey("forbid_success_message"))
|
||||
Controllers.dialog(i18n("message.success"), null, MessageType.SUCCESS, () -> onEnd());
|
||||
} else {
|
||||
@@ -77,10 +77,10 @@ public abstract class TaskExecutorDialogWizardDisplayer extends AbstractWizardDi
|
||||
}
|
||||
|
||||
String appendix = StringUtils.getStackTrace(executor.getException());
|
||||
if (settings.get("failure_callback") instanceof WizardProvider.FailureCallback)
|
||||
((WizardProvider.FailureCallback) settings.get("failure_callback")).onFail(settings, executor.getException(), () -> onEnd());
|
||||
else if (settings.get("failure_message") instanceof String)
|
||||
Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageType.ERROR, () -> onEnd());
|
||||
if (settings.get(WizardProvider.FailureCallback.KEY) != null)
|
||||
settings.get(WizardProvider.FailureCallback.KEY).onFail(settings, executor.getException(), () -> onEnd());
|
||||
else if (settings.get("failure_message") instanceof String failureMessage)
|
||||
Controllers.dialog(appendix, failureMessage, MessageType.ERROR, () -> onEnd());
|
||||
else if (!settings.containsKey("forbid_failure_message"))
|
||||
Controllers.dialog(appendix, i18n("wizard.failed"), MessageType.ERROR, () -> onEnd());
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui.wizard;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -27,7 +28,7 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
public class WizardController implements Navigation {
|
||||
private final WizardDisplayer displayer;
|
||||
private WizardProvider provider = null;
|
||||
private final Map<String, Object> settings = new HashMap<>();
|
||||
private final SettingsMap settings = new SettingsMap();
|
||||
private final Stack<Node> pages = new Stack<>();
|
||||
private boolean stopped = false;
|
||||
|
||||
@@ -36,7 +37,7 @@ public class WizardController implements Navigation {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getSettings() {
|
||||
public SettingsMap getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,7 @@ package org.jackhuang.hmcl.ui.wizard;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
|
||||
public interface WizardDisplayer {
|
||||
default void onStart() {
|
||||
@@ -34,5 +33,5 @@ public interface WizardDisplayer {
|
||||
|
||||
void navigateTo(Node page, Navigation.NavigationDirection nav);
|
||||
|
||||
void handleTask(Map<String, Object> settings, Task<?> task);
|
||||
void handleTask(SettingsMap settings, Task<?> task);
|
||||
}
|
||||
@@ -17,13 +17,13 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.wizard;
|
||||
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
|
||||
public interface WizardPage {
|
||||
default void onNavigate(Map<String, Object> settings) {
|
||||
default void onNavigate(SettingsMap settings) {
|
||||
}
|
||||
|
||||
default void cleanup(Map<String, Object> settings) {
|
||||
default void cleanup(SettingsMap settings) {
|
||||
}
|
||||
|
||||
String getTitle();
|
||||
|
||||
@@ -18,15 +18,14 @@
|
||||
package org.jackhuang.hmcl.ui.wizard;
|
||||
|
||||
import javafx.scene.Node;
|
||||
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
|
||||
public interface WizardProvider {
|
||||
void start(Map<String, Object> settings);
|
||||
void start(SettingsMap settings);
|
||||
|
||||
Object finish(Map<String, Object> settings);
|
||||
Object finish(SettingsMap settings);
|
||||
|
||||
Node createPage(WizardController controller, int step, Map<String, Object> settings);
|
||||
Node createPage(WizardController controller, int step, SettingsMap settings);
|
||||
|
||||
boolean cancel();
|
||||
|
||||
@@ -35,6 +34,8 @@ public interface WizardProvider {
|
||||
}
|
||||
|
||||
interface FailureCallback {
|
||||
void onFail(Map<String, Object> settings, Exception exception, Runnable next);
|
||||
SettingsMap.Key<FailureCallback> KEY = new SettingsMap.Key<>("failure_callback");
|
||||
|
||||
void onFail(SettingsMap settings, Exception exception, Runnable next);
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.wizard;
|
||||
|
||||
import javafx.scene.control.Control;
|
||||
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hmcl.util.SettingsMap;
|
||||
|
||||
public abstract class WizardSinglePage extends Control implements WizardPage {
|
||||
protected final Runnable onFinish;
|
||||
@@ -31,6 +30,6 @@ public abstract class WizardSinglePage extends Control implements WizardPage {
|
||||
protected abstract Object finish();
|
||||
|
||||
@Override
|
||||
public void cleanup(Map<String, Object> settings) {
|
||||
public void cleanup(SettingsMap settings) {
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,9 @@ import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.java.JavaRuntime;
|
||||
import org.jackhuang.hmcl.util.platform.OSVersion;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Platform;
|
||||
import org.jackhuang.hmcl.util.platform.windows.WindowsVersion;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -181,13 +181,13 @@ public final class NativePatcher {
|
||||
return version.setLibraries(newLibraries);
|
||||
}
|
||||
|
||||
public static @Nullable Library getWindowsMesaLoader(@NotNull JavaRuntime javaVersion, @NotNull Renderer renderer, @NotNull WindowsVersion windowsVersion) {
|
||||
public static @Nullable Library getWindowsMesaLoader(@NotNull JavaRuntime javaVersion, @NotNull Renderer renderer, @NotNull OSVersion windowsVersion) {
|
||||
if (renderer == Renderer.DEFAULT)
|
||||
return null;
|
||||
|
||||
if (windowsVersion.compareTo(WindowsVersion.WINDOWS_10) >= 0) {
|
||||
if (windowsVersion.isAtLeast(OSVersion.WINDOWS_10)) {
|
||||
return getNatives(javaVersion.getPlatform()).get("mesa-loader");
|
||||
} else if (windowsVersion.compareTo(WindowsVersion.WINDOWS_7) >= 0) {
|
||||
} else if (windowsVersion.isAtLeast(OSVersion.WINDOWS_7)) {
|
||||
if (renderer == Renderer.LLVMPIPE)
|
||||
return getNatives(javaVersion.getPlatform()).get("software-renderer-loader");
|
||||
else
|
||||
|
||||
@@ -92,10 +92,10 @@ public final class SelfDependencyPatcher {
|
||||
} else {
|
||||
defaultRepository = Repository.MAVEN_CENTRAL;
|
||||
}
|
||||
repositories = Collections.unmodifiableList(Arrays.asList(Repository.MAVEN_CENTRAL, Repository.TENCENTCLOUD_MIRROR));
|
||||
repositories = List.of(Repository.MAVEN_CENTRAL, Repository.TENCENTCLOUD_MIRROR);
|
||||
} else {
|
||||
defaultRepository = new Repository(String.format(i18n("repositories.custom"), customUrl), customUrl);
|
||||
repositories = Collections.unmodifiableList(Arrays.asList(Repository.MAVEN_CENTRAL, Repository.TENCENTCLOUD_MIRROR, defaultRepository));
|
||||
repositories = List.of(Repository.MAVEN_CENTRAL, Repository.TENCENTCLOUD_MIRROR, defaultRepository);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ public final class SelfDependencyPatcher {
|
||||
if (platform == null)
|
||||
return null;
|
||||
|
||||
if (JavaRuntime.CURRENT_VERSION >= 22) {
|
||||
if (JavaRuntime.CURRENT_VERSION >= 23) {
|
||||
List<DependencyDescriptor> modernDependencies = platform.get("modern");
|
||||
if (modernDependencies != null)
|
||||
return modernDependencies;
|
||||
@@ -143,18 +143,10 @@ public final class SelfDependencyPatcher {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Repository {
|
||||
private record Repository(String name, String url) {
|
||||
public static final Repository MAVEN_CENTRAL = new Repository(i18n("repositories.maven_central"), "https://repo1.maven.org/maven2");
|
||||
public static final Repository TENCENTCLOUD_MIRROR = new Repository(i18n("repositories.tencentcloud_mirror"), "https://mirrors.cloud.tencent.com/nexus/repository/maven-public");
|
||||
|
||||
private final String name;
|
||||
private final String url;
|
||||
|
||||
Repository(String name, String url) {
|
||||
this.name = name;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String resolveDependencyURL(DependencyDescriptor descriptor) {
|
||||
return String.format("%s/%s/%s/%s/%s",
|
||||
url,
|
||||
|
||||
@@ -19,7 +19,6 @@ package org.jackhuang.hmcl.util.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -34,7 +33,7 @@ public final class I18n {
|
||||
private I18n() {
|
||||
}
|
||||
|
||||
private static volatile SupportedLocale locale = Locales.DEFAULT;
|
||||
private static volatile SupportedLocale locale = SupportedLocale.DEFAULT;
|
||||
|
||||
public static void setLocale(SupportedLocale locale) {
|
||||
I18n.locale = locale;
|
||||
@@ -44,6 +43,10 @@ public final class I18n {
|
||||
return locale;
|
||||
}
|
||||
|
||||
public static boolean isUpsideDown() {
|
||||
return LocaleUtils.getScript(locale.getLocale()).equals("Qabs");
|
||||
}
|
||||
|
||||
public static boolean isUseChinese() {
|
||||
return LocaleUtils.isChinese(locale.getLocale());
|
||||
}
|
||||
@@ -71,6 +74,11 @@ public final class I18n {
|
||||
else
|
||||
return WenyanUtils.translateGenericVersion(version.getSelfVersion());
|
||||
}
|
||||
|
||||
if (LocaleUtils.isEnglish(locale.getLocale()) && "Qabs".equals(LocaleUtils.getScript(locale.getLocale()))) {
|
||||
return UpsideDownUtils.translate(version.getSelfVersion());
|
||||
}
|
||||
|
||||
return version.getSelfVersion();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,273 +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.i18n;
|
||||
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.*;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
public final class Locales {
|
||||
private Locales() {
|
||||
}
|
||||
|
||||
|
||||
public static final SupportedLocale DEFAULT;
|
||||
|
||||
static {
|
||||
String language = System.getenv("HMCL_LANGUAGE");
|
||||
DEFAULT = new SupportedLocale(true, "def",
|
||||
StringUtils.isBlank(language) ? LocaleUtils.SYSTEM_DEFAULT : Locale.forLanguageTag(language));
|
||||
}
|
||||
|
||||
/**
|
||||
* English
|
||||
*/
|
||||
public static final SupportedLocale EN = new SupportedLocale("en");
|
||||
|
||||
/**
|
||||
* Spanish
|
||||
*/
|
||||
public static final SupportedLocale ES = new SupportedLocale("es");
|
||||
|
||||
/**
|
||||
* Russian
|
||||
*/
|
||||
public static final SupportedLocale RU = new SupportedLocale("ru");
|
||||
|
||||
/**
|
||||
* Ukrainian
|
||||
*/
|
||||
public static final SupportedLocale UK = new SupportedLocale("uk");
|
||||
|
||||
/**
|
||||
* Japanese
|
||||
*/
|
||||
public static final SupportedLocale JA = new SupportedLocale("ja");
|
||||
|
||||
/**
|
||||
* Chinese (Simplified)
|
||||
*/
|
||||
public static final SupportedLocale ZH_HANS = new SupportedLocale("zh_CN", LocaleUtils.LOCALE_ZH_HANS);
|
||||
|
||||
/**
|
||||
* Chinese (Traditional)
|
||||
*/
|
||||
public static final SupportedLocale ZH_HANT = new SupportedLocale("zh", LocaleUtils.LOCALE_ZH_HANT);
|
||||
|
||||
/**
|
||||
* Wenyan (Classical Chinese)
|
||||
*/
|
||||
public static final SupportedLocale WENYAN = new SupportedLocale("lzh");
|
||||
|
||||
public static final List<SupportedLocale> LOCALES = List.of(DEFAULT, EN, ES, JA, RU, UK, ZH_HANS, ZH_HANT, WENYAN);
|
||||
|
||||
public static SupportedLocale getLocaleByName(String name) {
|
||||
if (name == null) return DEFAULT;
|
||||
|
||||
for (SupportedLocale locale : LOCALES) {
|
||||
if (locale.getName().equalsIgnoreCase(name))
|
||||
return locale;
|
||||
}
|
||||
|
||||
return DEFAULT;
|
||||
}
|
||||
|
||||
@JsonAdapter(SupportedLocale.TypeAdapter.class)
|
||||
public static final class SupportedLocale {
|
||||
private final boolean isDefault;
|
||||
private final String name;
|
||||
private final Locale locale;
|
||||
private ResourceBundle resourceBundle;
|
||||
private DateTimeFormatter dateTimeFormatter;
|
||||
private List<Locale> candidateLocales;
|
||||
|
||||
SupportedLocale(boolean isDefault, String name, Locale locale) {
|
||||
this.isDefault = isDefault;
|
||||
this.name = name;
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
SupportedLocale(String name) {
|
||||
this(false, name, Locale.forLanguageTag(name));
|
||||
}
|
||||
|
||||
SupportedLocale(String name, Locale locale) {
|
||||
this(false, name, locale);
|
||||
}
|
||||
|
||||
public boolean isDefault() {
|
||||
return isDefault;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Locale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
public String getDisplayName(SupportedLocale inLocale) {
|
||||
if (isDefault()) {
|
||||
try {
|
||||
return inLocale.getResourceBundle().getString("lang.default");
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to get localized name for default locale", e);
|
||||
return "Default";
|
||||
}
|
||||
}
|
||||
|
||||
Locale inJavaLocale = inLocale.getLocale();
|
||||
if (LocaleUtils.isISO3Language(inJavaLocale.getLanguage())) {
|
||||
String iso1 = LocaleUtils.getISO1Language(inJavaLocale);
|
||||
if (LocaleUtils.isISO1Language(iso1)) {
|
||||
Locale.Builder builder = new Locale.Builder()
|
||||
.setLocale(inJavaLocale)
|
||||
.setLanguage(iso1);
|
||||
|
||||
if (inJavaLocale.getScript().isEmpty())
|
||||
builder.setScript(LocaleUtils.getScript(inJavaLocale));
|
||||
|
||||
inJavaLocale = builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.locale.getLanguage().equals("lzh")) {
|
||||
if (inJavaLocale.getLanguage().equals("zh"))
|
||||
return "文言";
|
||||
|
||||
String name = locale.getDisplayName(inJavaLocale);
|
||||
return name.equals("lzh") || name.equals("Literary Chinese")
|
||||
? "Chinese (Classical)"
|
||||
: name;
|
||||
}
|
||||
|
||||
return locale.getDisplayName(inJavaLocale);
|
||||
}
|
||||
|
||||
public ResourceBundle getResourceBundle() {
|
||||
ResourceBundle bundle = resourceBundle;
|
||||
if (resourceBundle == null)
|
||||
resourceBundle = bundle = ResourceBundle.getBundle("assets.lang.I18N", locale,
|
||||
DefaultResourceBundleControl.INSTANCE);
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public List<Locale> getCandidateLocales() {
|
||||
if (candidateLocales == null)
|
||||
candidateLocales = List.copyOf(LocaleUtils.getCandidateLocales(locale));
|
||||
return candidateLocales;
|
||||
}
|
||||
|
||||
public String i18n(String key, Object... formatArgs) {
|
||||
try {
|
||||
return String.format(getResourceBundle().getString(key), formatArgs);
|
||||
} catch (MissingResourceException e) {
|
||||
LOG.error("Cannot find key " + key + " in resource bundle", e);
|
||||
} catch (IllegalFormatException e) {
|
||||
LOG.error("Illegal format string, key=" + key + ", args=" + Arrays.toString(formatArgs), e);
|
||||
}
|
||||
|
||||
return key + Arrays.toString(formatArgs);
|
||||
}
|
||||
|
||||
public String i18n(String key) {
|
||||
try {
|
||||
return getResourceBundle().getString(key);
|
||||
} catch (MissingResourceException e) {
|
||||
LOG.error("Cannot find key " + key + " in resource bundle", e);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
public String formatDateTime(TemporalAccessor time) {
|
||||
DateTimeFormatter formatter = dateTimeFormatter;
|
||||
if (formatter == null) {
|
||||
if (locale.getLanguage().equals("lzh"))
|
||||
return WenyanUtils.formatDateTime(time);
|
||||
|
||||
formatter = dateTimeFormatter = DateTimeFormatter.ofPattern(getResourceBundle().getString("datetime.format"))
|
||||
.withZone(ZoneId.systemDefault());
|
||||
}
|
||||
return formatter.format(time);
|
||||
}
|
||||
|
||||
public String getFcMatchPattern() {
|
||||
String language = locale.getLanguage();
|
||||
String region = locale.getCountry();
|
||||
|
||||
if (LocaleUtils.isEnglish(locale))
|
||||
return "";
|
||||
|
||||
if (LocaleUtils.isChinese(locale)) {
|
||||
String lang;
|
||||
String charset;
|
||||
|
||||
String script = LocaleUtils.getScript(locale);
|
||||
switch (script) {
|
||||
case "Hans":
|
||||
lang = region.equals("SG") || region.equals("MY")
|
||||
? "zh-" + region
|
||||
: "zh-CN";
|
||||
charset = "0x6e38,0x620f";
|
||||
break;
|
||||
case "Hant":
|
||||
lang = region.equals("HK") || region.equals("MO")
|
||||
? "zh-" + region
|
||||
: "zh-TW";
|
||||
charset = "0x904a,0x6232";
|
||||
break;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
return ":lang=" + lang + ":charset=" + charset;
|
||||
}
|
||||
|
||||
return region.isEmpty() ? language : language + "-" + region;
|
||||
}
|
||||
|
||||
public boolean isSameLanguage(SupportedLocale other) {
|
||||
return LocaleUtils.getISO1Language(this.getLocale())
|
||||
.equals(LocaleUtils.getISO1Language(other.getLocale()));
|
||||
}
|
||||
|
||||
public static final class TypeAdapter extends com.google.gson.TypeAdapter<SupportedLocale> {
|
||||
@Override
|
||||
public void write(JsonWriter out, SupportedLocale value) throws IOException {
|
||||
out.value(value.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SupportedLocale read(JsonReader in) throws IOException {
|
||||
return getLocaleByName(in.nextString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,7 +27,7 @@ public final class MinecraftWiki {
|
||||
|
||||
private static final Pattern SNAPSHOT_PATTERN = Pattern.compile("^[0-9]{2}w[0-9]{2}.+$");
|
||||
|
||||
public static String getWikiLink(Locales.SupportedLocale locale, GameRemoteVersion version) {
|
||||
public static String getWikiLink(SupportedLocale locale, GameRemoteVersion version) {
|
||||
String wikiVersion = version.getSelfVersion();
|
||||
var gameVersion = GameVersionNumber.asGameVersion(wikiVersion);
|
||||
|
||||
@@ -41,7 +41,7 @@ public final class MinecraftWiki {
|
||||
translatedVersion = WenyanUtils.translateGameVersion(gameVersion);
|
||||
|
||||
if (translatedVersion.equals(gameVersion.toString()) || gameVersion instanceof GameVersionNumber.Old) {
|
||||
return getWikiLink(Locales.ZH_HANT, version);
|
||||
return getWikiLink(SupportedLocale.getLocale(LocaleUtils.LOCALE_ZH_HANT), version);
|
||||
} else if (SNAPSHOT_PATTERN.matcher(wikiVersion).matches()) {
|
||||
return locale.i18n("wiki.version.game.snapshot", translatedVersion);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 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.i18n;
|
||||
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
@JsonAdapter(SupportedLocale.TypeAdapter.class)
|
||||
public final class SupportedLocale {
|
||||
public static final SupportedLocale DEFAULT = new SupportedLocale();
|
||||
|
||||
private static final ConcurrentMap<Locale, SupportedLocale> LOCALES = new ConcurrentHashMap<>();
|
||||
|
||||
public static List<SupportedLocale> getSupportedLocales() {
|
||||
List<SupportedLocale> list = new ArrayList<>();
|
||||
list.add(DEFAULT);
|
||||
|
||||
InputStream locales = SupportedLocale.class.getResourceAsStream("/assets/lang/languages.json");
|
||||
if (locales != null) {
|
||||
try (locales) {
|
||||
list.addAll(JsonUtils.fromNonNullJsonFully(locales, JsonUtils.listTypeOf(SupportedLocale.class)));
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to load languages.json", e);
|
||||
}
|
||||
}
|
||||
return List.copyOf(list);
|
||||
}
|
||||
|
||||
public static SupportedLocale getLocale(Locale locale) {
|
||||
return LOCALES.computeIfAbsent(locale, SupportedLocale::new);
|
||||
}
|
||||
|
||||
public static SupportedLocale getLocaleByName(String name) {
|
||||
if (name == null || name.isBlank() || "def".equals(name) || "default".equals(name))
|
||||
return DEFAULT;
|
||||
|
||||
return getLocale(Locale.forLanguageTag(name.trim().replace('_', '-')));
|
||||
}
|
||||
|
||||
private final boolean isDefault;
|
||||
private final String name;
|
||||
private final Locale locale;
|
||||
private ResourceBundle resourceBundle;
|
||||
private ResourceBundle localeNamesBundle;
|
||||
private DateTimeFormatter dateTimeFormatter;
|
||||
private List<Locale> candidateLocales;
|
||||
|
||||
SupportedLocale() {
|
||||
this.isDefault = true;
|
||||
this.name = "def"; // TODO: Change to "default" after updating the Config format
|
||||
|
||||
String language = System.getenv("HMCL_LANGUAGE");
|
||||
this.locale = StringUtils.isBlank(language)
|
||||
? LocaleUtils.SYSTEM_DEFAULT
|
||||
: Locale.forLanguageTag(language);
|
||||
}
|
||||
|
||||
SupportedLocale(Locale locale) {
|
||||
this.isDefault = false;
|
||||
this.name = locale.toLanguageTag();
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
public boolean isDefault() {
|
||||
return isDefault;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Locale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
public String getDisplayName(SupportedLocale inLocale) {
|
||||
if (isDefault()) {
|
||||
try {
|
||||
return inLocale.getResourceBundle().getString("lang.default");
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to get localized name for default locale", e);
|
||||
return "Default";
|
||||
}
|
||||
}
|
||||
|
||||
Locale currentLocale = this.getLocale();
|
||||
|
||||
String language = currentLocale.getLanguage();
|
||||
String subLanguage;
|
||||
{
|
||||
String parentLanguage = LocaleUtils.getParentLanguage(language);
|
||||
if (parentLanguage != null) {
|
||||
subLanguage = language;
|
||||
language = parentLanguage;
|
||||
} else {
|
||||
subLanguage = "";
|
||||
}
|
||||
}
|
||||
|
||||
ResourceBundle localeNames = inLocale.getLocaleNamesBundle();
|
||||
|
||||
String languageDisplayName = language;
|
||||
try {
|
||||
languageDisplayName = localeNames.getString(language);
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to get localized name for language " + language, e);
|
||||
}
|
||||
|
||||
// Currently, HMCL does not support any locales with regions or variants, so they are not handled for now
|
||||
List<String> subTags = Stream.of(subLanguage, currentLocale.getScript())
|
||||
.filter(it -> !it.isEmpty())
|
||||
.map(it -> {
|
||||
try {
|
||||
return localeNames.getString(it);
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to get localized name of " + it, e);
|
||||
}
|
||||
return it;
|
||||
}).toList();
|
||||
|
||||
return subTags.isEmpty()
|
||||
? languageDisplayName
|
||||
: languageDisplayName + " (" + String.join(", ", subTags) + ")";
|
||||
}
|
||||
|
||||
public ResourceBundle getResourceBundle() {
|
||||
ResourceBundle bundle = resourceBundle;
|
||||
if (resourceBundle == null)
|
||||
resourceBundle = bundle = ResourceBundle.getBundle("assets.lang.I18N", locale,
|
||||
DefaultResourceBundleControl.INSTANCE);
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public ResourceBundle getLocaleNamesBundle() {
|
||||
ResourceBundle bundle = localeNamesBundle;
|
||||
if (localeNamesBundle == null)
|
||||
localeNamesBundle = bundle = ResourceBundle.getBundle("assets.lang.LocaleNames", locale,
|
||||
DefaultResourceBundleControl.INSTANCE);
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public List<Locale> getCandidateLocales() {
|
||||
if (candidateLocales == null)
|
||||
candidateLocales = List.copyOf(LocaleUtils.getCandidateLocales(locale));
|
||||
return candidateLocales;
|
||||
}
|
||||
|
||||
public String i18n(String key, Object... formatArgs) {
|
||||
try {
|
||||
return String.format(getResourceBundle().getString(key), formatArgs);
|
||||
} catch (MissingResourceException e) {
|
||||
LOG.error("Cannot find key " + key + " in resource bundle", e);
|
||||
} catch (IllegalFormatException e) {
|
||||
LOG.error("Illegal format string, key=" + key + ", args=" + Arrays.toString(formatArgs), e);
|
||||
}
|
||||
|
||||
return key + Arrays.toString(formatArgs);
|
||||
}
|
||||
|
||||
public String i18n(String key) {
|
||||
try {
|
||||
return getResourceBundle().getString(key);
|
||||
} catch (MissingResourceException e) {
|
||||
LOG.error("Cannot find key " + key + " in resource bundle", e);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
public String formatDateTime(TemporalAccessor time) {
|
||||
DateTimeFormatter formatter = dateTimeFormatter;
|
||||
if (formatter == null) {
|
||||
if (LocaleUtils.isEnglish(locale) && "Qabs".equals(locale.getScript())) {
|
||||
return UpsideDownUtils.formatDateTime(time);
|
||||
}
|
||||
|
||||
if (locale.getLanguage().equals("lzh")) {
|
||||
return WenyanUtils.formatDateTime(time);
|
||||
}
|
||||
|
||||
formatter = dateTimeFormatter = DateTimeFormatter.ofPattern(getResourceBundle().getString("datetime.format"))
|
||||
.withZone(ZoneId.systemDefault());
|
||||
}
|
||||
return formatter.format(time);
|
||||
}
|
||||
|
||||
public String getFcMatchPattern() {
|
||||
String language = locale.getLanguage();
|
||||
String region = locale.getCountry();
|
||||
|
||||
if (LocaleUtils.isEnglish(locale))
|
||||
return "";
|
||||
|
||||
if (LocaleUtils.isChinese(locale)) {
|
||||
String lang;
|
||||
String charset;
|
||||
|
||||
String script = LocaleUtils.getScript(locale);
|
||||
switch (script) {
|
||||
case "Hans":
|
||||
lang = region.equals("SG") || region.equals("MY")
|
||||
? "zh-" + region
|
||||
: "zh-CN";
|
||||
charset = "0x6e38,0x620f";
|
||||
break;
|
||||
case "Hant":
|
||||
lang = region.equals("HK") || region.equals("MO")
|
||||
? "zh-" + region
|
||||
: "zh-TW";
|
||||
charset = "0x904a,0x6232";
|
||||
break;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
return ":lang=" + lang + ":charset=" + charset;
|
||||
}
|
||||
|
||||
return region.isEmpty() ? language : language + "-" + region;
|
||||
}
|
||||
|
||||
public boolean isSameLanguage(SupportedLocale other) {
|
||||
return LocaleUtils.getRootLanguage(this.getLocale())
|
||||
.equals(LocaleUtils.getRootLanguage(other.getLocale()));
|
||||
}
|
||||
|
||||
public static final class TypeAdapter extends com.google.gson.TypeAdapter<SupportedLocale> {
|
||||
@Override
|
||||
public void write(JsonWriter out, SupportedLocale value) throws IOException {
|
||||
out.value(value.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SupportedLocale read(JsonReader in) throws IOException {
|
||||
if (in.peek() == JsonToken.NULL)
|
||||
return DEFAULT;
|
||||
|
||||
String language = in.nextString();
|
||||
return getLocaleByName(switch (language) {
|
||||
// TODO: Remove these compatibility codes after updating the Config format
|
||||
case "zh_CN" -> "zh-Hans"; // For compatibility
|
||||
case "zh" -> "zh-Hant"; // For compatibility
|
||||
default -> language;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 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.i18n;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/// @author Glavo
|
||||
public final class UpsideDownUtils {
|
||||
private static final Map<Integer, Integer> MAPPER = loadMap();
|
||||
|
||||
private static Map<Integer, Integer> loadMap() {
|
||||
var map = new LinkedHashMap<Integer, Integer>();
|
||||
|
||||
InputStream inputStream = UpsideDownUtils.class.getResourceAsStream("/assets/lang/upside_down.txt");
|
||||
if (inputStream != null) {
|
||||
try (inputStream) {
|
||||
new String(inputStream.readAllBytes(), StandardCharsets.UTF_8).lines().forEach(line -> {
|
||||
if (line.isBlank() || line.startsWith("#"))
|
||||
return;
|
||||
|
||||
if (line.length() != 2) {
|
||||
LOG.warning("Invalid line: " + line);
|
||||
return;
|
||||
}
|
||||
|
||||
map.put((int) line.charAt(0), (int) line.charAt(1));
|
||||
});
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to load upside_down.txt", e);
|
||||
}
|
||||
} else {
|
||||
LOG.warning("upside_down.txt not found");
|
||||
}
|
||||
return Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
public static String translate(String str) {
|
||||
StringBuilder builder = new StringBuilder(str.length());
|
||||
str.codePoints().forEach(ch -> builder.appendCodePoint(MAPPER.getOrDefault(ch, ch)));
|
||||
return builder.reverse().toString();
|
||||
}
|
||||
|
||||
private static final DateTimeFormatter BASE_FORMATTER = DateTimeFormatter.ofPattern("MMM d, yyyy, h:mm:ss a")
|
||||
.withZone(ZoneId.systemDefault());
|
||||
|
||||
public static String formatDateTime(TemporalAccessor time) {
|
||||
return translate(BASE_FORMATTER.format(time));
|
||||
}
|
||||
|
||||
private UpsideDownUtils() {
|
||||
}
|
||||
}
|
||||
Loaded 100 of 272 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user