Compare commits
52
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31568a5806 | ||
|
|
9aa73f5a5c | ||
|
|
35943feb08 | ||
|
|
73de21f936 | ||
|
|
4f1af87ba0 | ||
|
|
d129bd3e6b | ||
|
|
c2c52e8747 | ||
|
|
7e2ab49f89 | ||
|
|
3192d41fe3 | ||
|
|
ad5b260460 | ||
|
|
e57cb0ce51 | ||
|
|
9514357391 | ||
|
|
6fd0a40cc6 | ||
|
|
350d63231f | ||
|
|
cc28f1d5b5 | ||
|
|
4b73af9250 | ||
|
|
6e0efe521d | ||
|
|
041ab30778 | ||
|
|
678be4b92f | ||
|
|
74d759a45b | ||
|
|
295499c17b | ||
|
|
043d1c88c8 | ||
|
|
55e9b93b1c | ||
|
|
aa5e3e9697 | ||
|
|
fbf7ff6ebd | ||
|
|
3a5e8a827c | ||
|
|
6176dfaaf7 | ||
|
|
76c361e9bc | ||
|
|
ccb8981634 | ||
|
|
99b55d03c1 | ||
|
|
651fa6e2d1 | ||
|
|
4b258da0a0 | ||
|
|
9a66605616 | ||
|
|
da13895b72 | ||
|
|
c657a1d2cc | ||
|
|
eac8896f3e | ||
|
|
113f4f948b | ||
|
|
1b8dc1f75e | ||
|
|
001236e8e6 | ||
|
|
8025b8e24b | ||
|
|
e48bf012a6 | ||
|
|
94e32694b2 | ||
|
|
cbcf033b15 | ||
|
|
7da2671ec5 | ||
|
|
b6ea4c7003 | ||
|
|
49378061bf | ||
|
|
38013a9298 | ||
|
|
5ffbd16800 | ||
|
|
bcb29ef792 | ||
|
|
d20adb20d3 | ||
|
|
4f28cdd58f | ||
|
|
4b6b5c2f0e |
No files matched your search
@@ -1,16 +1,14 @@
|
||||
name: Check Update
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- javafx
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '30 * * * *'
|
||||
|
||||
jobs:
|
||||
check-update:
|
||||
if: ${{ github.repository_owner == 'huanghongxun' }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
HMCL_BASE_VERSION: '3.4'
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Fetch tags
|
||||
@@ -20,15 +18,15 @@ jobs:
|
||||
- name: Fetch last version
|
||||
run: |
|
||||
wget -O ci.json https://ci.huangyuhui.net/job/HMCL/lastSuccessfulBuild/api/json
|
||||
|
||||
export HMCL_BUILD_NUMBER=`cat ci.json | jq -M -r '.id'`
|
||||
export HMCL_VERSION="$HMCL_BASE_VERSION.$HMCL_BUILD_NUMBER"
|
||||
|
||||
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_BUILD_NUMBER}" != 3 ]; then exit 1; fi
|
||||
if [ "${#HMCL_COMMIT_SHA}" != 40 ]; then exit 1; fi
|
||||
|
||||
echo "HMCL_BUILD_NUMBER=$HMCL_BUILD_NUMBER" >> $GITHUB_ENV
|
||||
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
|
||||
@@ -42,7 +40,7 @@ jobs:
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar.sha1"
|
||||
env:
|
||||
DOWNLOAD_BASE_URL: https://ci.huangyuhui.net/job/HMCL/${{ env.HMCL_BUILD_NUMBER }}/artifact/HMCL/build/libs
|
||||
DOWNLOAD_BASE_URL: https://ci.huangyuhui.net/job/HMCL/lastSuccessfulBuild/artifact/HMCL/build/libs
|
||||
- name: Generate release note
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
@@ -63,5 +61,3 @@ jobs:
|
||||
target_commitish: ${{ env.HMCL_COMMIT_SHA }}
|
||||
name: ${{ env.HMCL_TAG_NAME }}
|
||||
tag_name: ${{ env.HMCL_TAG_NAME }}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
name: Sync to Gitee
|
||||
|
||||
on:
|
||||
push
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Mirror Github to Gitee
|
||||
uses: Yikun/hub-mirror-action@v1.1
|
||||
with:
|
||||
src: github/huanghongxun
|
||||
dst: gitee/huanghongxun
|
||||
static_list: 'HMCL'
|
||||
force_update: true
|
||||
debug: true
|
||||
dst_key: ${{ secrets.GITEE_SYNC_BOT_PRIVATE_KEY }}
|
||||
dst_token: ${{ secrets.GITEE_SYNC_BOT_TOKEN }}
|
||||
cache_path: /github/workspace/hub-mirror-cache
|
||||
@@ -1,44 +0,0 @@
|
||||
name: Create Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check and set HMCL version
|
||||
run: |
|
||||
export HMCL_VERSION=`echo ${{ github.ref }} | awk '{print substr($0,12)}'`
|
||||
echo "HMCL_VERSION=$HMCL_VERSION" >> $GITHUB_ENV
|
||||
|
||||
if [ -z `echo $HMCL_VERSION | grep "^[0-9]\.[0-9]\.[0-9]\{3\}\$"` ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "HMCL_BUILD_NUMBER=`echo $HMCL_VERSION | awk '{print substr($0,5)}'`" >> $GITHUB_ENV
|
||||
- name: Download artifacts
|
||||
run: |
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.exe"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.exe.sha1"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar.sha1"
|
||||
env:
|
||||
DOWNLOAD_BASE_URL: https://ci.huangyuhui.net/job/HMCL/${{ env.HMCL_BUILD_NUMBER }}/artifact/HMCL/build/libs
|
||||
- name: Generate release note
|
||||
run: |
|
||||
echo "HMCL v$HMCL_VERSION" >> RELEASE_NOTE
|
||||
echo "" >> RELEASE_NOTE
|
||||
echo "The full changelogs can be found on the website: https://hmcl.huangyuhui.net/changelog/dev.html" >> RELEASE_NOTE
|
||||
echo "Notice: changelogs are written in Chinese." >> RELEASE_NOTE
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
body_path: RELEASE_NOTE
|
||||
files: |
|
||||
HMCL-${{ env.HMCL_VERSION }}.exe
|
||||
HMCL-${{ env.HMCL_VERSION }}.exe.sha1
|
||||
HMCL-${{ env.HMCL_VERSION }}.jar
|
||||
HMCL-${{ env.HMCL_VERSION }}.jar.sha1
|
||||
+6
-3
@@ -17,20 +17,23 @@ NVIDIA
|
||||
/build/
|
||||
/HMCL/build/
|
||||
/HMCLCore/build/
|
||||
/HMCLTransformerDiscoveryService/build/
|
||||
/minecraft/libraries/HMCLTransformerDiscoveryService/build/
|
||||
/minecraft/libraries/log4j-patch/build/
|
||||
|
||||
# idea
|
||||
.idea
|
||||
/out/
|
||||
/HMCL/out/
|
||||
/HMCLCore/out/
|
||||
/HMCLTransformerDiscoveryService/out/
|
||||
/minecraft/libraries/HMCLTransformerDiscoveryService/out/
|
||||
/minecraft/libraries/log4j-patch/out/
|
||||
|
||||
# eclipse
|
||||
/bin/
|
||||
/HMCL/bin/
|
||||
/HMCLCore/bin/
|
||||
/HMCLTransformerDiscoveryService/bin/
|
||||
/minecraft/libraries/HMCLTransformerDiscoveryService/bin/
|
||||
/minecraft/libraries/log4j-patch/bin/
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
|
||||
@@ -129,7 +129,6 @@ shadowJar {
|
||||
exclude(dependency('com.google.code.gson:.*:.*'))
|
||||
exclude(dependency('com.github.steveice10:.*:.*'))
|
||||
exclude(dependency('libs:JFoenix:.*'))
|
||||
exclude(project(":HMCLTransformerDiscoveryService"))
|
||||
}
|
||||
|
||||
manifest {
|
||||
|
||||
@@ -328,7 +328,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
vs.setUsesGlobal(true);
|
||||
}
|
||||
|
||||
public LaunchOptions getLaunchOptions(String version, JavaVersion javaVersion, File gameDir) {
|
||||
public LaunchOptions getLaunchOptions(String version, JavaVersion javaVersion, File gameDir, boolean makeLaunchScript) {
|
||||
VersionSetting vs = getVersionSetting(version);
|
||||
|
||||
LaunchOptions.Builder builder = new LaunchOptions.Builder()
|
||||
@@ -358,7 +358,8 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
.setNativesDir(vs.getNativesDir())
|
||||
.setProcessPriority(vs.getProcessPriority())
|
||||
.setUseNativeGLFW(vs.isUseNativeGLFW())
|
||||
.setUseNativeOpenAL(vs.isUseNativeOpenAL());
|
||||
.setUseNativeOpenAL(vs.isUseNativeOpenAL())
|
||||
.setDaemon(!makeLaunchScript && vs.getLauncherVisibility().isDaemon());
|
||||
if (config().hasProxy()) {
|
||||
builder.setProxy(ProxyManager.getProxy());
|
||||
if (config().hasProxyAuth()) {
|
||||
|
||||
@@ -172,7 +172,7 @@ public final class LauncherHelper {
|
||||
}
|
||||
}).withStage("launch.state.logging_in"))
|
||||
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
|
||||
LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir());
|
||||
LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir(), scriptFile != null);
|
||||
return new HMCLGameLauncher(
|
||||
repository,
|
||||
version,
|
||||
@@ -494,7 +494,7 @@ public final class LauncherHelper {
|
||||
|
||||
// Cannot allocate too much memory exceeding free space.
|
||||
if (!suggested && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
|
||||
Controllers.confirm(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), continueAction, null);
|
||||
Controllers.confirm(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), continueAction, breakAction);
|
||||
suggested = true;
|
||||
}
|
||||
|
||||
@@ -506,7 +506,7 @@ public final class LauncherHelper {
|
||||
VersionNumber.VERSION_COMPARATOR.compare(it.getVersion(), "1.12.2-14.23.5.2773") < 0);
|
||||
boolean hasLiteLoader = version.getLibraries().stream().anyMatch(it -> it.is("com.mumfrey", "liteloader"));
|
||||
if (hasForge2760 && hasLiteLoader && gameVersion.compareTo(VersionNumber.asVersion("1.12.2")) == 0) {
|
||||
Controllers.confirm(i18n("launch.advice.forge2760_liteloader"), i18n("message.error"), continueAction, null);
|
||||
Controllers.confirm(i18n("launch.advice.forge2760_liteloader"), i18n("message.error"), continueAction, breakAction);
|
||||
suggested = true;
|
||||
}
|
||||
}
|
||||
@@ -518,12 +518,12 @@ public final class LauncherHelper {
|
||||
VersionNumber.VERSION_COMPARATOR.compare("1.14.4-28.2.2", it.getVersion()) <= 0);
|
||||
boolean hasOptiFine = version.getLibraries().stream().anyMatch(it -> it.is("optifine", "OptiFine"));
|
||||
if (hasForge28_2_2 && hasOptiFine && gameVersion.compareTo(VersionNumber.asVersion("1.14.4")) == 0) {
|
||||
Controllers.confirm(i18n("launch.advice.forge28_2_2_optifine"), i18n("message.error"), continueAction, null);
|
||||
Controllers.confirm(i18n("launch.advice.forge28_2_2_optifine"), i18n("message.error"), continueAction, breakAction);
|
||||
suggested = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!suggested) {
|
||||
if (!future.isDone()) {
|
||||
future.complete(javaVersion);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ import java.nio.file.*;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public final class ConfigHolder {
|
||||
@@ -175,7 +174,7 @@ public final class ConfigHolder {
|
||||
private static void writeToConfig(String content) throws IOException {
|
||||
LOG.info("Saving config");
|
||||
synchronized (configLocation) {
|
||||
Files.write(configLocation, content.getBytes(UTF_8));
|
||||
FileUtils.saveSafely(configLocation, content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +218,7 @@ public final class ConfigHolder {
|
||||
private static void writeToGlobalConfig(String content) throws IOException {
|
||||
LOG.info("Saving global config");
|
||||
synchronized (GLOBAL_CONFIG_PATH) {
|
||||
Files.write(GLOBAL_CONFIG_PATH, content.getBytes(UTF_8));
|
||||
FileUtils.saveSafely(GLOBAL_CONFIG_PATH, content);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,5 +43,9 @@ public enum LauncherVisibility {
|
||||
/**
|
||||
* Hide the launcher and reopen it when game closes.
|
||||
*/
|
||||
HIDE_AND_REOPEN
|
||||
HIDE_AND_REOPEN;
|
||||
|
||||
public boolean isDaemon() {
|
||||
return this != CLOSE;
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ public final class VersionSetting implements Cloneable {
|
||||
this.global = global;
|
||||
}
|
||||
|
||||
private final BooleanProperty usesGlobalProperty = new SimpleBooleanProperty(this, "usesGlobal", false);
|
||||
private final BooleanProperty usesGlobalProperty = new SimpleBooleanProperty(this, "usesGlobal", true);
|
||||
|
||||
public BooleanProperty usesGlobalProperty() {
|
||||
return usesGlobalProperty;
|
||||
|
||||
@@ -221,7 +221,7 @@ public class GameCrashWindow extends Stage {
|
||||
LogWindow logWindow = new LogWindow();
|
||||
|
||||
logWindow.logLine("Command: " + new CommandBuilder().addAll(managedProcess.getCommands()).toString(), Log4jLevel.INFO);
|
||||
logWindow.logLine("ClassPath: " + managedProcess.getClasspath(), Log4jLevel.INFO);
|
||||
if (managedProcess.getClasspath() != null) logWindow.logLine("ClassPath: " + managedProcess.getClasspath(), Log4jLevel.INFO);
|
||||
for (Map.Entry<String, Log4jLevel> entry : logs)
|
||||
logWindow.logLine(entry.getKey(), entry.getValue());
|
||||
|
||||
@@ -315,13 +315,15 @@ public class GameCrashWindow extends Stage {
|
||||
moddedPane.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
for (LibraryAnalyzer.LibraryType type : LibraryAnalyzer.LibraryType.values()) {
|
||||
analyzer.getVersion(type).ifPresent(ver -> {
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
item.getStyleClass().setAll("two-line-item-second-large");
|
||||
item.setTitle(i18n("install.installer." + type.getPatchId()));
|
||||
item.setSubtitle(ver);
|
||||
moddedPane.getChildren().add(item);
|
||||
});
|
||||
if (!type.getPatchId().isEmpty()) {
|
||||
analyzer.getVersion(type).ifPresent(ver -> {
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
item.getStyleClass().setAll("two-line-item-second-large");
|
||||
item.setTitle(i18n("install.installer." + type.getPatchId()));
|
||||
item.setSubtitle(ver);
|
||||
moddedPane.getChildren().add(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import javafx.concurrent.Worker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.web.WebEngine;
|
||||
import javafx.scene.web.WebView;
|
||||
@@ -41,23 +42,23 @@ public class UpgradeDialog extends JFXDialogLayout {
|
||||
|
||||
{
|
||||
String url = CHANGELOG_URL + remoteVersion.getChannel().channelName + ".html";
|
||||
WebView webView = new WebView();
|
||||
webView.getEngine().setUserDataDirectory(Metadata.HMCL_DIRECTORY.toFile());
|
||||
try {
|
||||
WebView webView = new WebView();
|
||||
webView.getEngine().setUserDataDirectory(Metadata.HMCL_DIRECTORY.toFile());
|
||||
WebEngine engine = webView.getEngine();
|
||||
engine.load(CHANGELOG_URL + remoteVersion.getChannel().channelName);
|
||||
engine.load(url);
|
||||
engine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {
|
||||
String viewURL = engine.getLoadWorker().getMessage().trim();
|
||||
if (!viewURL.startsWith(CHANGELOG_URL)) {
|
||||
engine.getLoadWorker().cancel();
|
||||
FXUtils.openLink(viewURL);
|
||||
if (newValue == Worker.State.FAILED) {
|
||||
LOG.warning("Failed to load update log, trying to open it in browser");
|
||||
FXUtils.openLink(url);
|
||||
setBody();
|
||||
}
|
||||
});
|
||||
setBody(webView);
|
||||
} catch (NoClassDefFoundError | UnsatisfiedLinkError e) {
|
||||
LOG.log(Level.WARNING, "WebView is missing or initialization failed", e);
|
||||
FXUtils.openLink(url);
|
||||
}
|
||||
setBody(webView);
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import com.jfoenix.controls.JFXSpinner;
|
||||
@@ -39,7 +40,9 @@ import org.jackhuang.hmcl.download.forge.ForgeRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersion;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionPane;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
@@ -58,6 +61,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.ToolbarListPageSkin.wrap;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
@@ -84,6 +88,8 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
@FXML
|
||||
private JFXCheckBox chkOld;
|
||||
@FXML
|
||||
private JFXButton btnRefresh;
|
||||
@FXML
|
||||
private HBox checkPane;
|
||||
@FXML
|
||||
private ComponentList centrePane;
|
||||
@@ -114,6 +120,8 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
chkSnapshot.selectedProperty().addListener(listener);
|
||||
chkOld.selectedProperty().addListener(listener);
|
||||
|
||||
btnRefresh.setGraphic(wrap(SVG.refresh(Theme.blackFillBinding(), -1, -1)));
|
||||
|
||||
list.setCellFactory(listView -> new ListCell<RemoteVersion>() {
|
||||
IconedTwoLineListItem content = new IconedTwoLineListItem();
|
||||
RipplerContainer ripplerContainer = new RipplerContainer(content);
|
||||
|
||||
@@ -167,9 +167,9 @@ public class AboutPage extends StackPane {
|
||||
jstun.setExternalLink("https://github.com/tking/JSTUN");
|
||||
|
||||
IconedTwoLineListItem minecraftJFXSkin = new IconedTwoLineListItem();
|
||||
jstun.setTitle("minecraft-jfx-skin");
|
||||
jstun.setSubtitle("Copyright (c) 2016 InfinityStudio.\nLicensed under the GPL 3.");
|
||||
jstun.setExternalLink("https://github.com/InfinityStudio/minecraft-jfx-skin");
|
||||
minecraftJFXSkin.setTitle("minecraft-jfx-skin");
|
||||
minecraftJFXSkin.setSubtitle("Copyright (c) 2016 InfinityStudio.\nLicensed under the GPL 3.");
|
||||
minecraftJFXSkin.setExternalLink("https://github.com/InfinityStudio/minecraft-jfx-skin");
|
||||
|
||||
dep.getContent().setAll(javafx, jfoenix, gson, xz, fxgson, constantPoolScanner, openNBT, jstun, minecraftJFXSkin);
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ public class FeedbackPage extends VBox implements PageAware {
|
||||
container.setPickOnBounds(false);
|
||||
container.setAlignment(Pos.CENTER_LEFT);
|
||||
HBox.setHgrow(content, Priority.ALWAYS);
|
||||
content.setMouseTransparent(true);
|
||||
content.setMouseTransparent(false);
|
||||
setSelectable();
|
||||
|
||||
likeButton.getStyleClass().add("toggle-icon4");
|
||||
@@ -152,7 +152,7 @@ public class FeedbackPage extends VBox implements PageAware {
|
||||
"#" + feedback.getId(),
|
||||
i18n("feedback.state." + feedback.getState().name().toLowerCase(Locale.US)),
|
||||
i18n("feedback.type." + feedback.getType().name().toLowerCase(Locale.US)));
|
||||
container.setOnMouseClicked(e -> {
|
||||
content.setOnMouseClicked(e -> {
|
||||
getFeedback(feedback.getId())
|
||||
.thenAcceptAsync(Schedulers.javafx(), f -> {
|
||||
Controllers.dialog(new ViewFeedbackDialog(f));
|
||||
|
||||
@@ -25,9 +25,11 @@ import org.jackhuang.hmcl.event.EventManager;
|
||||
import org.jackhuang.hmcl.launch.StreamPump;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
@@ -59,56 +61,52 @@ import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
* Cato Management.
|
||||
*/
|
||||
public final class MultiplayerManager {
|
||||
static final String CATO_VERSION = "1.1.1-3f36a56";
|
||||
// private static final String CATO_DOWNLOAD_URL = "https://files.huangyuhui.net/maven/cato/cato/" + MultiplayerManager.CATO_VERSION;
|
||||
private static final String CATO_DOWNLOAD_URL = "https://codechina.csdn.net/huanghongxun1/ioi_bin/-/raw/3f36a567d9381e5fa404b96bcecd6d6a555f235c/client/";
|
||||
static final String CATO_VERSION = "1.2.0-202112171527";
|
||||
private static final String CATO_DOWNLOAD_URL = "https://gitcode.net/huanghongxun1/ioi_bin/-/raw/3.5.3/client/";
|
||||
private static final String CATO_HASH_URL = CATO_DOWNLOAD_URL + "cato-all-files.sha1";
|
||||
private static final String CATO_PATH = getCatoPath();
|
||||
public static final int CATO_AGREEMENT_VERSION = 2;
|
||||
private static final String REMOTE_ADDRESS = "127.0.0.1";
|
||||
private static final String LOCAL_ADDRESS = "0.0.0.0";
|
||||
|
||||
private static final Map<String, String> HASH = mapOf(
|
||||
pair("cato-client-darwin-amd64", "e733fa4b72be24b99026249c31c4ae8b52ad3906"),
|
||||
pair("cato-client-darwin-arm64", "19ab41b7f78263cad81009ae1a391b0d0367d95f"),
|
||||
pair("cato-client-freebsd-amd64", "6de8439ab9267e444d45bb9f02394023ce0bfe5e"),
|
||||
pair("cato-client-freebsd-arm64", "5cd8930e7cd17d72386b085950e63467e4ce72ef"),
|
||||
pair("cato-client-freebsd-arm7", "d0e54a4a1b0f8c2f769d74a6a600eec4a9c4e0da"),
|
||||
pair("cato-client-freebsd-i386", "d9d22faf85e01e77b18f28f1609e87623ac9b1a4"),
|
||||
pair("cato-client-js.wasm", "acb366a4d6a3c6cd464ef5919ce7d2d58a52964e"),
|
||||
pair("cato-client-linux-amd64", "04e94a3adff1e4d865dead2c6c5da584b74bff0a"),
|
||||
pair("cato-client-linux-arm64", "ba662bc461995dae5e78d21b9fea35c2db81b652"),
|
||||
pair("cato-client-linux-arm7", "28021eff56e1e273c051853808e70e7ab3af80d9"),
|
||||
pair("cato-client-linux-i386", "4f524a9cfbcea560bdf07d3468b883f946c431ed"),
|
||||
pair("cato-client-linux-mips", "91c3108b046511c2aafbe88f82b38a06c03d88e1"),
|
||||
pair("cato-client-linux-mips64", "04d1b66494dbdb0443eaae85bda6b61408e300ed"),
|
||||
pair("cato-client-linux-mips64le", "d838ac71052f7873b28f4b4a2f23470b918ef85d"),
|
||||
pair("cato-client-linux-mipsle", "54918ed9cdbe48f7adf11680947281f5fc7c5a7d"),
|
||||
pair("cato-client-linux-ppc64", "f2679259516b0db0422f85afcce35bfa6f7b3192"),
|
||||
pair("cato-client-linux-ppc64le", "463d9b86316dfc83e668ddfae96e51181a9e3ffa"),
|
||||
pair("cato-client-openbsd-amd64", "b776155e955340485faf62c3b2aad3a788c57e2b"),
|
||||
pair("cato-client-openbsd-arm64", "fd6ec21248fd9c8be3d9d2876faf65b95c63b3a9"),
|
||||
pair("cato-client-openbsd-arm7", "19eabe71530bb4eac7a32f6b8aeef72f6f9382e9"),
|
||||
pair("cato-client-openbsd-i386", "3e755798628a379997dbc43dfd71765d62263ebe"),
|
||||
pair("cato-client-windows-amd64.exe", "21a862f455c909c9fb786cd308e969629eb3c8d9"),
|
||||
pair("cato-client-windows-arm64.exe", "7f2fe5d054ebc3265edb046badafdd6ac94bf7eb"),
|
||||
pair("cato-client-windows-i386.exe", "4e03e640c6cabfd7e1ddf60bba1726353bc3afee")
|
||||
);
|
||||
private static CompletableFuture<Map<String, String>> HASH;
|
||||
|
||||
private MultiplayerManager() {
|
||||
}
|
||||
|
||||
private static CompletableFuture<Map<String, String>> getCatoHash() {
|
||||
FXUtils.checkFxUserThread();
|
||||
if (HASH == null) {
|
||||
HASH = CompletableFuture.supplyAsync(wrap(() -> {
|
||||
String hashList = HttpRequest.GET(CATO_HASH_URL).getString();
|
||||
Map<String, String> hashes = new HashMap<>();
|
||||
for (String line : hashList.split("\n")) {
|
||||
String[] items = line.trim().split(" ");
|
||||
if (items.length == 2 && items[0].length() == 40) {
|
||||
hashes.put(items[1], items[0]);
|
||||
} else {
|
||||
LOG.warning("Failed to parse cato hash file, hash line " + line);
|
||||
}
|
||||
}
|
||||
return hashes;
|
||||
}));
|
||||
}
|
||||
return HASH;
|
||||
}
|
||||
|
||||
public static Task<Void> downloadCato() {
|
||||
return new FileDownloadTask(
|
||||
NetworkUtils.toURL(CATO_DOWNLOAD_URL + getCatoFileName()),
|
||||
getCatoExecutable().toFile(),
|
||||
new FileDownloadTask.IntegrityCheck("SHA-1", HASH.get(getCatoFileName()))
|
||||
).thenRunAsync(() -> {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
Set<PosixFilePermission> perm = Files.getPosixFilePermissions(getCatoExecutable());
|
||||
perm.add(PosixFilePermission.OWNER_EXECUTE);
|
||||
Files.setPosixFilePermissions(getCatoExecutable(), perm);
|
||||
}
|
||||
});
|
||||
return Task.fromCompletableFuture(getCatoHash()).thenComposeAsync(catoHashes ->
|
||||
new FileDownloadTask(
|
||||
NetworkUtils.toURL(CATO_DOWNLOAD_URL + getCatoFileName()),
|
||||
getCatoExecutable().toFile(),
|
||||
catoHashes.get(getCatoFileName()) == null ? null : new FileDownloadTask.IntegrityCheck("SHA-1", catoHashes.get(getCatoFileName()))
|
||||
).thenRunAsync(() -> {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
Set<PosixFilePermission> perm = Files.getPosixFilePermissions(getCatoExecutable());
|
||||
perm.add(PosixFilePermission.OWNER_EXECUTE);
|
||||
Files.setPosixFilePermissions(getCatoExecutable(), perm);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public static Path getCatoExecutable() {
|
||||
@@ -116,7 +114,7 @@ public final class MultiplayerManager {
|
||||
}
|
||||
|
||||
private static CompletableFuture<CatoSession> startCato(String token, State state) {
|
||||
return CompletableFuture.completedFuture(null).thenApplyAsync(wrap(unused -> {
|
||||
return getCatoHash().thenApplyAsync(wrap(catoHashes -> {
|
||||
Path exe = getCatoExecutable();
|
||||
if (!Files.isRegularFile(exe)) {
|
||||
throw new CatoNotExistsException(exe);
|
||||
@@ -127,13 +125,16 @@ public final class MultiplayerManager {
|
||||
}
|
||||
|
||||
try {
|
||||
ChecksumMismatchException.verifyChecksum(exe, "SHA-1", HASH.get(getCatoFileName()));
|
||||
String hash = catoHashes.get(getCatoFileName());
|
||||
if (hash != null) {
|
||||
ChecksumMismatchException.verifyChecksum(exe, "SHA-1", hash);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Files.deleteIfExists(exe);
|
||||
throw e;
|
||||
}
|
||||
|
||||
String[] commands = new String[]{exe.toString(), "--token", StringUtils.isBlank(token) ? "new" : token};
|
||||
String[] commands = new String[]{exe.toString(), "-client.token", StringUtils.isBlank(token) ? "new" : token};
|
||||
Process process = new ProcessBuilder()
|
||||
.command(commands)
|
||||
.start();
|
||||
@@ -400,7 +401,8 @@ public final class MultiplayerManager {
|
||||
onExit.fireEvent(new CatoExitEvent(this, CatoExitEvent.EXIT_CODE_INTERRUPTED));
|
||||
} finally {
|
||||
try {
|
||||
writer.close();
|
||||
if (writer != null)
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to close cato stdin writer", e);
|
||||
}
|
||||
|
||||
@@ -88,6 +88,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
private TaskExecutor executor;
|
||||
protected RemoteModRepository repository;
|
||||
|
||||
private Runnable retrySearch;
|
||||
|
||||
public DownloadListPage(RemoteModRepository repository) {
|
||||
this(repository, null);
|
||||
}
|
||||
@@ -151,6 +153,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
}
|
||||
|
||||
public void search(String userGameVersion, RemoteModRepository.Category category, int pageOffset, String searchFilter, int sort) {
|
||||
retrySearch = null;
|
||||
setLoading(true);
|
||||
setFailed(false);
|
||||
File versionJar = StringUtils.isNotBlank(version.get().getVersion())
|
||||
@@ -177,6 +180,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
failed.set(false);
|
||||
} else {
|
||||
failed.set(true);
|
||||
retrySearch = () -> search(userGameVersion, category, pageOffset, searchFilter, sort);
|
||||
}
|
||||
}).executor(true);
|
||||
}
|
||||
@@ -360,6 +364,11 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
return null;
|
||||
}
|
||||
}, getSkinnable().failedProperty()));
|
||||
spinnerPane.setOnFailedAction(e -> {
|
||||
if (getSkinnable().retrySearch != null) {
|
||||
getSkinnable().retrySearch.run();
|
||||
}
|
||||
});
|
||||
|
||||
JFXListView<RemoteMod> listView = new JFXListView<>();
|
||||
spinnerPane.setContent(listView);
|
||||
|
||||
@@ -84,7 +84,12 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
this.mod = ModTranslations.getModByCurseForgeId(addon.getSlug());
|
||||
this.version = version;
|
||||
this.callback = callback;
|
||||
loadModVersions();
|
||||
|
||||
this.state.set(State.fromTitle(addon.getTitle()));
|
||||
}
|
||||
|
||||
private void loadModVersions() {
|
||||
File versionJar = StringUtils.isNotBlank(version.getVersion())
|
||||
? version.getProfile().getRepository().getVersionJar(version.getVersion())
|
||||
: null;
|
||||
@@ -93,9 +98,9 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
setFailed(false);
|
||||
|
||||
Task.allOf(
|
||||
Task.supplyAsync(() -> addon.getData().loadDependencies()),
|
||||
Task.supplyAsync(() -> {
|
||||
Stream<RemoteMod.Version> versions = addon.getData().loadVersions();
|
||||
Task.supplyAsync(() -> addon.getData().loadDependencies()),
|
||||
Task.supplyAsync(() -> {
|
||||
Stream<RemoteMod.Version> versions = addon.getData().loadVersions();
|
||||
// if (StringUtils.isNotBlank(version.getVersion())) {
|
||||
// Optional<String> gameVersion = GameVersion.minecraftVersion(versionJar);
|
||||
// if (gameVersion.isPresent()) {
|
||||
@@ -103,8 +108,8 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
// .filter(file -> file.getGameVersions().contains(gameVersion.get())));
|
||||
// }
|
||||
// }
|
||||
return sortVersions(versions);
|
||||
}))
|
||||
return sortVersions(versions);
|
||||
}))
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -122,8 +127,6 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
}
|
||||
setLoading(false);
|
||||
}).start();
|
||||
|
||||
this.state.set(State.fromTitle(addon.getTitle()));
|
||||
}
|
||||
|
||||
private SimpleMultimap<String, RemoteMod.Version> sortVersions(Stream<RemoteMod.Version> versions) {
|
||||
@@ -195,7 +198,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
}
|
||||
|
||||
Controllers.taskDialog(
|
||||
new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest).executor(true),
|
||||
new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest, file.getFile().getIntegrityCheck()).executor(true),
|
||||
i18n("message.downloading")
|
||||
);
|
||||
}
|
||||
@@ -297,6 +300,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
return null;
|
||||
}
|
||||
}, getSkinnable().failedProperty()));
|
||||
spinnerPane.setOnFailedAction(e -> getSkinnable().loadModVersions());
|
||||
|
||||
ComponentList list = new ComponentList();
|
||||
StackPane.setAlignment(list, Pos.TOP_CENTER);
|
||||
|
||||
@@ -112,7 +112,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
private final StringProperty selectedVersion = new SimpleStringProperty();
|
||||
private final BooleanProperty navigateToSpecificSettings = new SimpleBooleanProperty(false);
|
||||
private final BooleanProperty enableSpecificSettings = new SimpleBooleanProperty(true);
|
||||
private final BooleanProperty enableSpecificSettings = new SimpleBooleanProperty(false);
|
||||
private final IntegerProperty maxMemory = new SimpleIntegerProperty();
|
||||
private final ObjectProperty<OperatingSystem.PhysicalMemoryStatus> memoryStatus = new SimpleObjectProperty<>(OperatingSystem.PhysicalMemoryStatus.INVALID);
|
||||
private final BooleanProperty modpack = new SimpleBooleanProperty();
|
||||
|
||||
@@ -123,7 +123,12 @@ public final class Versions {
|
||||
}
|
||||
if (profile.getRepository().renameVersion(version, newName)) {
|
||||
resolve.run();
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
profile.getRepository().refreshVersionsAsync()
|
||||
.thenRunAsync(Schedulers.javafx(), () -> {
|
||||
if (profile.getRepository().hasVersion(newName)) {
|
||||
profile.setSelectedVersion(newName);
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
reject.accept(i18n("version.manage.rename.fail"));
|
||||
}
|
||||
|
||||
@@ -23,10 +23,26 @@
|
||||
<JFXSpinner fx:id="spinner" styleClass="first-spinner" />
|
||||
<StackPane fx:id="center" style="-fx-padding: 10;">
|
||||
<ComponentList fx:id="centrePane" styleClass="no-padding">
|
||||
<HBox fx:id="checkPane" spacing="10" style="-fx-padding: 10;">
|
||||
<JFXCheckBox fx:id="chkRelease" text="%version.game.releases" selected="true" />
|
||||
<JFXCheckBox fx:id="chkSnapshot" text="%version.game.snapshots" />
|
||||
<JFXCheckBox fx:id="chkOld" text="%version.game.old" />
|
||||
<HBox fx:id="checkPane" spacing="10">
|
||||
<JFXCheckBox fx:id="chkRelease" text="%version.game.releases" selected="true">
|
||||
<HBox.margin>
|
||||
<Insets top="10" bottom="10" />
|
||||
</HBox.margin>
|
||||
</JFXCheckBox>
|
||||
<JFXCheckBox fx:id="chkSnapshot" text="%version.game.snapshots">
|
||||
<HBox.margin>
|
||||
<Insets top="10" bottom="10" />
|
||||
</HBox.margin>
|
||||
</JFXCheckBox>
|
||||
<JFXCheckBox fx:id="chkOld" text="%version.game.old">
|
||||
<HBox.margin>
|
||||
<Insets top="10" bottom="10" />
|
||||
</HBox.margin>
|
||||
</JFXCheckBox>
|
||||
|
||||
<HBox HBox.hgrow="ALWAYS" />
|
||||
|
||||
<JFXButton fx:id="btnRefresh" styleClass="jfx-tool-bar-button" onMouseClicked="#onRefresh" text="%button.refresh" />
|
||||
</HBox>
|
||||
<JFXListView fx:id="list" styleClass="jfx-list-view-float" VBox.vgrow="ALWAYS">
|
||||
</JFXListView>
|
||||
|
||||
@@ -182,6 +182,11 @@ curse.category.4472=Tech
|
||||
curse.category.4473=Magic
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.5129=Vanilla+
|
||||
curse.category.5189=Utility & QOL
|
||||
curse.category.5190=QoL
|
||||
curse.category.5191=Utility & QoL
|
||||
curse.category.5192=FancyMenu
|
||||
curse.category.423=Map and Information
|
||||
curse.category.426=Addons
|
||||
curse.category.434=Armor, Tools, and Weapons
|
||||
@@ -223,7 +228,8 @@ curse.category.4558=Redstone
|
||||
curse.category.4843=Automation
|
||||
curse.category.4906=MCreator
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/12
|
||||
curse.category.5193=Data Packs
|
||||
curse.category.399=Steampunk
|
||||
curse.category.396=128x
|
||||
curse.category.398=512x and Higher
|
||||
@@ -943,7 +949,7 @@ update.found=Update Available!
|
||||
update.newest_version=Latest version: %s
|
||||
update.bubble.title=Update Available: %s
|
||||
update.bubble.subtitle=Click here to update
|
||||
update.note=Warning: Beta versions may have more functionality and bug fixes but also more possible errors.
|
||||
update.note=Warning: Beta versions and nightly versions may have more functionality and bug fixes but also more possible errors.
|
||||
update.latest=This is the latest version.
|
||||
update.no_browser=Cannot open a browser. The link has been copied to the clipboard. Paste it into the address bar of browser to update.
|
||||
update.tooltip=Update
|
||||
|
||||
@@ -148,7 +148,7 @@ button.install=インストール
|
||||
button.export=エクスポート
|
||||
button.no=いいえ
|
||||
button.ok=OK
|
||||
button.refresh=リフレッシュ
|
||||
button.refresh=更新
|
||||
button.remove=削除
|
||||
button.remove.confirm=削除してもよろしいですか?この操作をロールバックすることはできません!
|
||||
button.save=保存
|
||||
@@ -182,6 +182,11 @@ curse.category.4472=Tech
|
||||
curse.category.4473=魔法
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.5129=Vanilla+
|
||||
curse.category.5189=Utility & QOL
|
||||
curse.category.5190=QoL
|
||||
curse.category.5191=Utility & QoL
|
||||
curse.category.5192=FancyMenu
|
||||
curse.category.423=地図と情報
|
||||
curse.category.426=Addons
|
||||
curse.category.434=武器、道具、武器
|
||||
@@ -289,6 +294,12 @@ download.javafx.notes=ネットワークを介したHMCLに必要なコンポー
|
||||
download.javafx.component=モジュール%sをダウンロードしています
|
||||
download.javafx.prepare=ダウンロードする準備ができました
|
||||
|
||||
exception.access_denied=It's denied by operating system to access file %s. Maybe we don't have permission to access this file, or this file has already been opened by other program.\n\
|
||||
Please check if current operating system user has permission to access that file.\n\
|
||||
For Windows users, you can also try the Resource Monitor, find if some programs is holding the file, try to close related program, or restart your computer.
|
||||
exception.artifact_malformed=The file cannot pass verification.
|
||||
exception.ssl_handshake=Cannot establish SSL connection because of missing SSL certifcate in current Java installation. Try to start HMCL on another Java VM.
|
||||
|
||||
extension.bat=WindowsBatファイル
|
||||
extension.mod=Modファイル
|
||||
extension.png=画像ファイル
|
||||
@@ -300,7 +311,8 @@ fatal.javafx.missing=JavaFXがありません。\nJava11以降を使用してい
|
||||
fatal.missing_dst_root_ca_x3=現在のJavaプラットフォームにDSTルートCAX3証明書がありません。\n引き続きHelloMinecraftを使用できます。ランチャーですが、一部のサイト(Let's Encryptによって発行された証明書を使用するサイトなど)に接続できず、ランチャーが正しく機能しない可能性があります。\n問題を解決するには、Javaランタイムを8u101以降に更新してください。
|
||||
fatal.config_loading_failure=構成にアクセスできません。\nHelloMinecraftを確認してください。 Launcherには、「%s」とその中のファイルへの読み取りおよび書き込みアクセス権があります。
|
||||
fatal.migration_requires_manual_reboot=更新が完了しました。 Hello Minecraftを再開してください!ランチャー。
|
||||
fatal.apply_update_failure=ごめんなさい、こんにちはMinecraft!何か問題が発生したため、ランチャーは更新を完了できませんでした。\nただし、Hello Minecraftをダウンロードすることで、手動で更新を終了できます。 %sからのランチャー。\nこの問題を報告することを検討してください。
|
||||
fatal.apply_update_failure=ごめんなさい、Hello Minecraft! Launcher 何か問題が発生したため、ランチャーは更新を完了できませんでした。\nただし、Hello Minecraftをダウンロードすることで、手動で更新を終了できます。 %sからのランチャー。\nこの問題を報告することを検討してください。
|
||||
fatal.samba=If you are trying to run HMCL in a shared folder by Samba, HMCL may not working, please try updating your Java or running HMCL in a local folder.
|
||||
|
||||
feedback=フィードバック
|
||||
Feedback.add=フィードバックを追加
|
||||
@@ -392,12 +404,11 @@ input.number=数値である必要があります。
|
||||
input.not_empty=必須フィールド
|
||||
input.url=有効なURLである必要があります。
|
||||
|
||||
install=新しい構成を作成
|
||||
install=新規作成
|
||||
install.change_version=バージョンの変更
|
||||
install.change_version.confirm=%sをバージョン%sから%sに更新してもよろしいですか?
|
||||
install.failed=バージョンのインストールに失敗しました
|
||||
install.failed.downloading=一部のファイルが正常にダウンロードされなかったため、インストールに失敗しました
|
||||
install.failed.downloading.access_denied=ファイルのダウンロードに失敗しました:%1$s。これは、オペレーティングシステムによってファイル%2 $ sへのアクセスが拒否されているためです。このファイルにアクセスする権限がないか、このファイルが他のプログラムによって既に開かれている可能性があります。関連するプログラムを閉じるか、コンピュータを再起動してみてください。
|
||||
install.failed.downloading.detail=ファイルのダウンロードに失敗しました:%s
|
||||
install.failed.downloading.timeout=ファイルのダウンロード中にタイムアウトしました:%s
|
||||
install.failed.install_online=提供されたインストーラーファイルを認識できません。 modをインストールする場合は、「Mods」ページに移動します。
|
||||
@@ -425,7 +436,7 @@ install.installer.not_installed=インストールされていません
|
||||
install.installer.optifine=OptiFine
|
||||
install.installer.version=%s
|
||||
install.modpack=modpackを導入
|
||||
install.new_game=新しいゲームをインストールする
|
||||
install.new_game=新規作成
|
||||
install.new_game.already_exists=このバージョンはすでに存在します。
|
||||
install.new_game.current_game_version=現在のゲームバージョン
|
||||
install.new_game.malformed=無効な名前
|
||||
@@ -482,6 +493,7 @@ launcher.background.classic=クラシック
|
||||
launcher.background.choose=背景画像ファイルを選択してください
|
||||
launcher.background.default=標準
|
||||
launcher.background.network=ネットワーク
|
||||
launcher.background.translucent=半透明
|
||||
launcher.cache_directory=キャッシュ用のディレクトリ
|
||||
launcher.cache_directory.clean=Clear
|
||||
launcher.cache_directory.choose=キャッシュするディレクトリを選択します
|
||||
@@ -641,8 +653,10 @@ multiplayer.download.unsupported=現在のオペレーティングシステム
|
||||
multiplayer.exit.after_ready=マルチプレイヤーセッションが壊れています。 cato exitcode%d
|
||||
multiplayer.exit.before_ready=マルチプレイヤーセッションの作成に失敗しました。 cato exitcode%d
|
||||
multiplayer.exit.timeout=マルチプレイヤーサーバーへの接続に失敗しました。
|
||||
multiplayer.exit.timeout.dynamic_token=マルチプレイヤーサーバーへの接続に失敗しました。
|
||||
multiplayer.hint=マルチプレイヤー機能は実験的なものです。フィードバックをお願いします。
|
||||
multiplayer.nat=ネットワークタイプの検出
|
||||
multiplayer.nat.failed=ネットワークタイプの検出に失敗しました。
|
||||
multiplayer.nat.hint=ネットワークタイプの検出により、ネットワークがマルチプレイヤーモードの要件を満たしているかどうかが明確になります。
|
||||
multiplayer.nat.latency=レイテンシー
|
||||
multiplayer.nat.not_yet_tested=まだテストされていません
|
||||
@@ -750,7 +764,7 @@ profile.instance_directory=ゲームディレクトリ
|
||||
profile.instance_directory.choose=ゲームディレクトリを選択してください
|
||||
profile.manage=ゲームディレクトリリスト
|
||||
profile.name=名前
|
||||
profile.new=新しい構成
|
||||
profile.new=新規作成
|
||||
profile.title=ゲームディレクトリ
|
||||
profile.selected=選択済み
|
||||
profile.use_relative_path=可能であれば、ゲームディレクトリの相対パスを使用します
|
||||
@@ -799,7 +813,7 @@ settings.advanced.launcher_visibility.close=ゲームの起動時にランチャ
|
||||
settings.advanced.launcher_visibility.hide=ゲームの起動時にランチャーを非表示にします。
|
||||
settings.advanced.launcher_visibility.hide_and_reopen=ランチャーを非表示にし、ゲームが終了したら再度開きます。
|
||||
settings.advanced.launcher_visibility.keep=ランチャーを表示したままにします。
|
||||
settings.advanced.launcher_visible=ランチャーの可視性
|
||||
settings.advanced.launcher_visible=ランチャーの設定
|
||||
settings.advanced.minecraft_arguments=Minecraftの引数
|
||||
settings.advanced.minecraft_arguments.prompt=デフォルト
|
||||
settings.advanced.natives_directory=ネイティブライブラリパス(例: LWJGL)
|
||||
@@ -883,7 +897,7 @@ settings.memory.allocate.auto=最小%1 $ .1f GB /実際の%2 $ .1f GB
|
||||
settings.memory.allocate.auto.exceeded=最小%1 $ .1f GB /実際の%2 $ .1f GB(%3 $ .1f GB使用可能)
|
||||
settings.memory.allocate.manual=割り当て%1 $ .1f GB
|
||||
settings.memory.allocate.manual.exceeded=割り当て%1 $ .1f GB(%3 $ .1f GB使用可能)
|
||||
settings.memory.auto_allocate=メモリサイズは自動的に決定されます
|
||||
settings.memory.auto_allocate=メモリサイズの自動割り当て
|
||||
settings.memory.lower_bound=最小メモリ
|
||||
settings.memory.used_per_total=%1 $ .1f GB使用済み/%2 $ .1fGB合計
|
||||
settings.physical_memory=物理メモリサイズ
|
||||
@@ -969,9 +983,9 @@ version.manage.rename=このバージョンの名前を変更します
|
||||
version.manage.rename.message=このバージョンの新しい名前を入力してください
|
||||
version.manage.rename.fail=バージョンの名前を変更できませんでした。
|
||||
version.settings=設定
|
||||
version.update=modpackを更新します
|
||||
version.update=modpackを更新する
|
||||
|
||||
wizard.prev=<前へ
|
||||
wizard.prev=< 前へ
|
||||
wizard.failed=失敗
|
||||
wizard.finish=終了
|
||||
wizard.next=次へ>
|
||||
wizard.next=次へ >
|
||||
@@ -182,6 +182,11 @@ curse.category.4472=科技
|
||||
curse.category.4473=魔法
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.5129=原版增強
|
||||
curse.category.5189=實用與QOL
|
||||
curse.category.5190=QoL
|
||||
curse.category.5191=實用與QoL
|
||||
curse.category.5192=夢幻菜單
|
||||
curse.category.423=訊息展示
|
||||
curse.category.426=模組擴展
|
||||
curse.category.434=裝備武器
|
||||
@@ -224,6 +229,7 @@ curse.category.4843=自動化
|
||||
curse.category.4906=MCreator
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.5193=封包
|
||||
curse.category.399=蒸汽朋克
|
||||
curse.category.396=128x
|
||||
curse.category.398=512x 及更高
|
||||
@@ -943,7 +949,7 @@ update.found=發現到更新
|
||||
update.newest_version=最新版本為: %s
|
||||
update.bubble.title=發現更新: %s
|
||||
update.bubble.subtitle=點選此處進行升級
|
||||
update.note=開發版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。
|
||||
update.note=測試版與開發版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。
|
||||
update.latest=目前版本為最新版本
|
||||
update.no_browser=無法開啟瀏覽器,網址已經複製到剪貼簿了,您可以手動複製網址開啟頁面
|
||||
update.tooltip=更新
|
||||
|
||||
@@ -182,6 +182,11 @@ curse.category.4472=科技
|
||||
curse.category.4473=魔法
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.5129=原版增强
|
||||
curse.category.5189=实用与QOL
|
||||
curse.category.5190=QoL
|
||||
curse.category.5191=实用与QoL
|
||||
curse.category.5192=梦幻菜单
|
||||
curse.category.423=信息展示
|
||||
curse.category.426=模组扩展
|
||||
curse.category.434=装备武器
|
||||
@@ -223,7 +228,8 @@ curse.category.4558=红石
|
||||
curse.category.4843=自动化
|
||||
curse.category.4906=MCreator
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/12
|
||||
curse.category.5193=数据包
|
||||
curse.category.399=蒸汽朋克
|
||||
curse.category.396=128x
|
||||
curse.category.398=512x 及更高
|
||||
@@ -943,7 +949,7 @@ update.found=发现更新
|
||||
update.newest_version=最新版本为:%s
|
||||
update.bubble.title=发现更新:%s
|
||||
update.bubble.subtitle=点击此处进行升级
|
||||
update.note=开发版包含更多的功能以及错误修复,但也可能会包含其他的问题。
|
||||
update.note=测试版与开发版包含更多的功能以及错误修复,但也可能会包含其他的问题。
|
||||
update.latest=当前版本为最新版本
|
||||
update.no_browser=无法打开浏览器,网址已经复制到剪贴板了,您可以手动粘贴网址打开页面
|
||||
update.tooltip=更新
|
||||
|
||||
@@ -17,3 +17,11 @@ dependencies {
|
||||
api group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0'
|
||||
compileOnlyApi group: 'org.jetbrains', name: 'annotations', version: '16.0.3'
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
dependsOn ':log4j-patch:jar'
|
||||
|
||||
into('assets/game') {
|
||||
from project(':log4j-patch').file("build/libs")
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,10 @@
|
||||
package org.jackhuang.hmcl.auth;
|
||||
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.game.LaunchOptions;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -27,26 +29,18 @@ import java.util.UUID;
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@Immutable
|
||||
public final class AuthInfo implements AutoCloseable {
|
||||
public class AuthInfo implements AutoCloseable {
|
||||
|
||||
private final String username;
|
||||
private final UUID uuid;
|
||||
private final String accessToken;
|
||||
private final String userProperties;
|
||||
private final Arguments arguments;
|
||||
private final AutoCloseable closeable;
|
||||
|
||||
public AuthInfo(String username, UUID uuid, String accessToken, String userProperties) {
|
||||
this(username, uuid, accessToken, userProperties, null, null);
|
||||
}
|
||||
|
||||
public AuthInfo(String username, UUID uuid, String accessToken, String userProperties, Arguments arguments, AutoCloseable closeable) {
|
||||
this.username = username;
|
||||
this.uuid = uuid;
|
||||
this.accessToken = accessToken;
|
||||
this.userProperties = userProperties;
|
||||
this.arguments = arguments;
|
||||
this.closeable = closeable;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
@@ -72,24 +66,14 @@ public final class AuthInfo implements AutoCloseable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when launching game.
|
||||
* @return null if no argument is specified
|
||||
*/
|
||||
public Arguments getArguments() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
public AuthInfo withArguments(Arguments arguments) {
|
||||
return new AuthInfo(username, uuid, accessToken, userProperties, arguments, closeable);
|
||||
}
|
||||
|
||||
public AuthInfo withCloseable(AutoCloseable closeable) {
|
||||
return new AuthInfo(username, uuid, accessToken, userProperties, arguments, closeable);
|
||||
public Arguments getLaunchArguments(LaunchOptions options) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (closeable != null) {
|
||||
closeable.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
-7
@@ -26,6 +26,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.game.LaunchOptions;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
|
||||
import java.io.IOException;
|
||||
@@ -71,7 +72,7 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||
Optional<String> prefetchedMeta = server.getMetadataResponse();
|
||||
|
||||
if (auth.isPresent() && artifact.isPresent() && prefetchedMeta.isPresent()) {
|
||||
return Optional.of(auth.get().withArguments(generateArguments(artifact.get(), server, prefetchedMeta.get())));
|
||||
return Optional.of(new AuthlibInjectorAuthInfo(auth.get(), artifact.get(), server, prefetchedMeta.get()));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
@@ -112,14 +113,30 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||
}
|
||||
}
|
||||
|
||||
return auth.withArguments(generateArguments(artifact, server, prefetchedMeta));
|
||||
return new AuthlibInjectorAuthInfo(auth, artifact, server, prefetchedMeta);
|
||||
}
|
||||
|
||||
private static Arguments generateArguments(AuthlibInjectorArtifactInfo artifact, AuthlibInjectorServer server, String prefetchedMeta) {
|
||||
return new Arguments().addJVMArguments(
|
||||
"-javaagent:" + artifact.getLocation().toString() + "=" + server.getUrl(),
|
||||
"-Dauthlibinjector.side=client",
|
||||
"-Dauthlibinjector.yggdrasil.prefetched=" + Base64.getEncoder().encodeToString(prefetchedMeta.getBytes(UTF_8)));
|
||||
private static class AuthlibInjectorAuthInfo extends AuthInfo {
|
||||
|
||||
private final AuthlibInjectorArtifactInfo artifact;
|
||||
private final AuthlibInjectorServer server;
|
||||
private final String prefetchedMeta;
|
||||
|
||||
public AuthlibInjectorAuthInfo(AuthInfo authInfo, AuthlibInjectorArtifactInfo artifact, AuthlibInjectorServer server, String prefetchedMeta) {
|
||||
super(authInfo.getUsername(), authInfo.getUUID(), authInfo.getAccessToken(), authInfo.getUserProperties());
|
||||
|
||||
this.artifact = artifact;
|
||||
this.server = server;
|
||||
this.prefetchedMeta = prefetchedMeta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Arguments getLaunchArguments(LaunchOptions options) {
|
||||
return new Arguments().addJVMArguments(
|
||||
"-javaagent:" + artifact.getLocation().toString() + "=" + server.getUrl(),
|
||||
"-Dauthlibinjector.side=client",
|
||||
"-Dauthlibinjector.yggdrasil.prefetched=" + Base64.getEncoder().encodeToString(prefetchedMeta.getBytes(UTF_8)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.Texture;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureModel;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.game.LaunchOptions;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
@@ -129,15 +130,7 @@ public class OfflineAccount extends Account {
|
||||
}
|
||||
|
||||
try {
|
||||
YggdrasilServer server = new YggdrasilServer(0);
|
||||
server.start();
|
||||
server.addCharacter(new YggdrasilServer.Character(uuid, username, skin.load(username).run()));
|
||||
|
||||
return authInfo.withArguments(new Arguments().addJVMArguments(
|
||||
"-javaagent:" + artifact.getLocation().toString() + "=" + "http://localhost:" + server.getListeningPort(),
|
||||
"-Dauthlibinjector.side=client"
|
||||
))
|
||||
.withCloseable(server::stop);
|
||||
return new OfflineAuthInfo(authInfo, artifact);
|
||||
} catch (Exception e) {
|
||||
throw new AuthenticationException(e);
|
||||
}
|
||||
@@ -146,6 +139,46 @@ public class OfflineAccount extends Account {
|
||||
}
|
||||
}
|
||||
|
||||
private class OfflineAuthInfo extends AuthInfo {
|
||||
private final AuthlibInjectorArtifactInfo artifact;
|
||||
private YggdrasilServer server;
|
||||
|
||||
public OfflineAuthInfo(AuthInfo authInfo, AuthlibInjectorArtifactInfo artifact) {
|
||||
super(authInfo.getUsername(), authInfo.getUUID(), authInfo.getAccessToken(), authInfo.getUserProperties());
|
||||
|
||||
this.artifact = artifact;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Arguments getLaunchArguments(LaunchOptions options) throws IOException {
|
||||
if (!options.isDaemon()) return null;
|
||||
|
||||
server = new YggdrasilServer(0);
|
||||
server.start();
|
||||
|
||||
try {
|
||||
server.addCharacter(new YggdrasilServer.Character(uuid, username, skin.load(username).run()));
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
return new Arguments().addJVMArguments(
|
||||
"-javaagent:" + artifact.getLocation().toString() + "=" + "http://localhost:" + server.getListeningPort(),
|
||||
"-Dauthlibinjector.side=client"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
super.close();
|
||||
|
||||
if (server != null)
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AuthInfo> playOffline() throws AuthenticationException {
|
||||
return Optional.of(logIn());
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
@@ -78,7 +79,8 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
Version version = original.resolve(repository);
|
||||
return Task.allOf(
|
||||
Task.composeAsync(() -> {
|
||||
if (!repository.getVersionJar(version).exists())
|
||||
File versionJar = repository.getVersionJar(version);
|
||||
if (!versionJar.exists() || versionJar.length() == 0)
|
||||
return new GameDownloadTask(this, null, version);
|
||||
else
|
||||
return null;
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -62,18 +63,31 @@ public class MaintainTask extends Task<Version> {
|
||||
String mainClass = version.resolve(null).getMainClass();
|
||||
|
||||
if (mainClass != null && mainClass.equals(LibraryAnalyzer.LAUNCH_WRAPPER_MAIN)) {
|
||||
return maintainOptiFineLibrary(repository, maintainGameWithLaunchWrapper(unique(version), true), false);
|
||||
version = maintainOptiFineLibrary(repository, maintainGameWithLaunchWrapper(unique(version), true), false);
|
||||
} else if (mainClass != null && mainClass.equals(LibraryAnalyzer.MOD_LAUNCHER_MAIN)) {
|
||||
// Forge 1.13 and OptiFine
|
||||
return maintainOptiFineLibrary(repository, maintainGameWithCpwModLauncher(repository, unique(version)), true);
|
||||
version = maintainOptiFineLibrary(repository, maintainGameWithCpwModLauncher(repository, unique(version)), true);
|
||||
} else if (mainClass != null && mainClass.equals(LibraryAnalyzer.BOOTSTRAP_LAUNCHER_MAIN)) {
|
||||
// Forge 1.17
|
||||
return maintainGameWithCpwBoostrapLauncher(repository, unique(version));
|
||||
version = maintainGameWithCpwBoostrapLauncher(repository, unique(version));
|
||||
} else {
|
||||
// Vanilla Minecraft does not need maintain
|
||||
// Fabric does not need maintain, nothing compatible with fabric now.
|
||||
return maintainOptiFineLibrary(repository, unique(version), false);
|
||||
version = maintainOptiFineLibrary(repository, unique(version), false);
|
||||
}
|
||||
|
||||
List<Library> libraries = version.getLibraries();
|
||||
if (libraries.size() > 0) {
|
||||
Library library = libraries.get(0);
|
||||
if ("org.glavo".equals(library.getGroupId())
|
||||
&& ("log4j-patch".equals(library.getArtifactId()) || "log4j-patch-beta9".equals(library.getArtifactId()))
|
||||
&& "1.0".equals(library.getVersion())
|
||||
&& library.getDownload() == null) {
|
||||
version = version.setLibraries(libraries.subList(1, libraries.size()));
|
||||
}
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
public static Version maintainPreservingPatches(GameRepository repository, Version version) {
|
||||
@@ -136,10 +150,9 @@ public class MaintainTask extends Task<Version> {
|
||||
builder.addJvmArgument("-Dhmcl.transformer.candidates=${library_directory}/" + library.getPath());
|
||||
if (!libraryExisting) builder.addLibrary(hmclTransformerDiscoveryService);
|
||||
Path libraryPath = repository.getLibraryFile(version, hmclTransformerDiscoveryService).toPath();
|
||||
try {
|
||||
try (InputStream input = MaintainTask.class.getResourceAsStream("/assets/game/HMCLTransformerDiscoveryService-1.0.jar")) {
|
||||
Files.createDirectories(libraryPath.getParent());
|
||||
Files.copy(MaintainTask.class.getResourceAsStream("/assets/game/HMCLTransformerDiscoveryService-1.0.jar"),
|
||||
libraryPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.copy(input, libraryPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to unpack HMCLTransformerDiscoveryService", e);
|
||||
}
|
||||
|
||||
+3
-22
@@ -20,13 +20,11 @@ package org.jackhuang.hmcl.download.fabric;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -37,7 +35,6 @@ import java.util.List;
|
||||
*/
|
||||
public final class FabricAPIInstallTask extends Task<Version> {
|
||||
|
||||
private GetTask dependent;
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final Version version;
|
||||
private final FabricAPIRemoteVersion remote;
|
||||
@@ -47,22 +44,6 @@ public final class FabricAPIInstallTask extends Task<Version> {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.version = version;
|
||||
this.remote = remoteVersion;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doPreExecute() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preExecute() throws Exception {
|
||||
this.dependent = new GetTask(new URL(remote.getUrls().get(0) + ".sha1"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task<?>> getDependents() {
|
||||
return Collections.singleton(dependent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,9 +59,9 @@ public final class FabricAPIInstallTask extends Task<Version> {
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
dependencies.add(new FileDownloadTask(
|
||||
new URL(remote.getUrls().get(0)),
|
||||
dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("fabric-api-" + remote.getFullVersion() + ".jar").toFile(),
|
||||
new FileDownloadTask.IntegrityCheck("SHA-1", dependent.getResult().trim()))
|
||||
new URL(remote.getVersion().getFile().getUrl()),
|
||||
dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("fabric-api-" + remote.getVersion().getVersion() + ".jar").toFile(),
|
||||
remote.getVersion().getFile().getIntegrityCheck())
|
||||
);
|
||||
}
|
||||
}
|
||||
+15
-2
@@ -19,12 +19,15 @@ package org.jackhuang.hmcl.download.fabric;
|
||||
|
||||
import org.jackhuang.hmcl.download.*;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
public class FabricAPIRemoteVersion extends RemoteVersion {
|
||||
private final String fullVersion;
|
||||
private final RemoteMod.Version version;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -33,10 +36,11 @@ public class FabricAPIRemoteVersion extends RemoteVersion {
|
||||
* @param selfVersion the version string of the remote version.
|
||||
* @param urls the installer or universal jar original URL.
|
||||
*/
|
||||
FabricAPIRemoteVersion(String gameVersion, String selfVersion, String fullVersion, List<String> urls) {
|
||||
super(LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId(), gameVersion, selfVersion, null, urls);
|
||||
FabricAPIRemoteVersion(String gameVersion, String selfVersion, String fullVersion, Instant datePublished, RemoteMod.Version version, List<String> urls) {
|
||||
super(LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId(), gameVersion, selfVersion, datePublished, urls);
|
||||
|
||||
this.fullVersion = fullVersion;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,9 +48,18 @@ public class FabricAPIRemoteVersion extends RemoteVersion {
|
||||
return fullVersion;
|
||||
}
|
||||
|
||||
public RemoteMod.Version getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {
|
||||
return new FabricAPIInstallTask(dependencyManager, baseVersion, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(RemoteVersion o) {
|
||||
if (!(o instanceof FabricAPIRemoteVersion)) return 0;
|
||||
return -this.getReleaseDate().compareTo(o.getReleaseDate());
|
||||
}
|
||||
}
|
||||
+7
-23
@@ -19,12 +19,10 @@ package org.jackhuang.hmcl.download.fabric;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -49,24 +47,10 @@ public class FabricAPIVersionList extends VersionList<FabricAPIRemoteVersion> {
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync() {
|
||||
return CompletableFuture.runAsync(wrap(() -> {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
Document doc = builder.parse("https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api/maven-metadata.xml");
|
||||
Element r = doc.getDocumentElement();
|
||||
NodeList versionElements = r.getElementsByTagName("version");
|
||||
for (int i = 0; i < versionElements.getLength(); i++) {
|
||||
String versionName = versionElements.item(i).getTextContent();
|
||||
|
||||
Matcher matcher = FABRIC_VERSION_PATTERN.matcher(versionName);
|
||||
if (matcher.find()) {
|
||||
String fabricVersion = matcher.group("version");
|
||||
if (matcher.group("build") != null) {
|
||||
fabricVersion += "." + matcher.group("build");
|
||||
}
|
||||
String gameVersion = matcher.group("mcversion");
|
||||
versions.put(gameVersion, new FabricAPIRemoteVersion(gameVersion, fabricVersion, versionName,
|
||||
Collections.singletonList(String.format(
|
||||
"https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api/%1$s/fabric-api-%1$s.jar", versionName))));
|
||||
for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.INSTANCE.getRemoteVersionsById("P7dR8mSH"))) {
|
||||
for (String gameVersion : modVersion.getGameVersions()) {
|
||||
versions.put(gameVersion, new FabricAPIRemoteVersion(gameVersion, modVersion.getVersion(), modVersion.getName(), modVersion.getDatePublished(), modVersion,
|
||||
Collections.singletonList(modVersion.getFile().getUrl())));
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -229,6 +229,7 @@ public final class OptiFineInstallTask extends Task<Version> {
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {
|
||||
Path configClass = fs.getPath("Config.class");
|
||||
if (!Files.exists(configClass)) configClass = fs.getPath("net/optifine/Config.class");
|
||||
if (!Files.exists(configClass)) configClass = fs.getPath("notch/net/optifine/Config.class");
|
||||
if (!Files.exists(configClass)) throw new IOException("Unrecognized installer");
|
||||
ConstantPool pool = ConstantPoolScanner.parse(Files.readAllBytes(configClass), ConstantType.UTF8);
|
||||
List<String> constants = new ArrayList<>();
|
||||
|
||||
@@ -59,6 +59,7 @@ public class LaunchOptions implements Serializable {
|
||||
private ProcessPriority processPriority = ProcessPriority.NORMAL;
|
||||
private boolean useNativeGLFW;
|
||||
private boolean useNativeOpenAL;
|
||||
private boolean daemon;
|
||||
|
||||
/**
|
||||
* The game directory
|
||||
@@ -243,6 +244,13 @@ public class LaunchOptions implements Serializable {
|
||||
return useNativeOpenAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will launcher keeps alive after game launched or not.
|
||||
*/
|
||||
public boolean isDaemon() {
|
||||
return daemon;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final LaunchOptions options = new LaunchOptions();
|
||||
@@ -411,6 +419,10 @@ public class LaunchOptions implements Serializable {
|
||||
return options.useNativeOpenAL;
|
||||
}
|
||||
|
||||
public boolean isDaemon() {
|
||||
return options.daemon;
|
||||
}
|
||||
|
||||
public Builder setGameDir(File gameDir) {
|
||||
options.gameDir = gameDir;
|
||||
return this;
|
||||
@@ -543,5 +555,10 @@ public class LaunchOptions implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDaemon(boolean daemon) {
|
||||
options.daemon = daemon;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -19,24 +19,20 @@ package org.jackhuang.hmcl.launch;
|
||||
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.Argument;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.game.LaunchOptions;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.NativesDirectoryType;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.io.Unzipper;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.ManagedProcess;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.platform.Bits;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetSocketAddress;
|
||||
@@ -46,10 +42,13 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
/**
|
||||
@@ -75,14 +74,14 @@ public class DefaultLauncher extends Launcher {
|
||||
switch (options.getProcessPriority()) {
|
||||
case HIGH:
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
res.add("cmd", "/C", "start", "unused title", "/B", "/high");
|
||||
// res.add("cmd", "/C", "start", "unused title", "/B", "/high");
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
res.add("nice", "-n", "-5");
|
||||
}
|
||||
break;
|
||||
case ABOVE_NORMAL:
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
res.add("cmd", "/C", "start", "unused title", "/B", "/abovenormal");
|
||||
// res.add("cmd", "/C", "start", "unused title", "/B", "/abovenormal");
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
res.add("nice", "-n", "-1");
|
||||
}
|
||||
@@ -92,14 +91,14 @@ public class DefaultLauncher extends Launcher {
|
||||
break;
|
||||
case BELOW_NORMAL:
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
res.add("cmd", "/C", "start", "unused title", "/B", "/belownormal");
|
||||
// res.add("cmd", "/C", "start", "unused title", "/B", "/belownormal");
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
res.add("nice", "-n", "1");
|
||||
}
|
||||
break;
|
||||
case LOW:
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
res.add("cmd", "/C", "start", "unused title", "/B", "/low");
|
||||
// res.add("cmd", "/C", "start", "unused title", "/B", "/low");
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
res.add("nice", "-n", "5");
|
||||
}
|
||||
@@ -114,6 +113,21 @@ public class DefaultLauncher extends Launcher {
|
||||
|
||||
res.addAllWithoutParsing(options.getJavaArguments());
|
||||
|
||||
Charset encoding = OperatingSystem.NATIVE_CHARSET;
|
||||
|
||||
// After Java 17, file.encoding does not affect console encoding
|
||||
if (options.getJava().getParsedVersion() <= JavaVersion.JAVA_17) {
|
||||
try {
|
||||
String fileEncoding = res.addDefault("-Dfile.encoding=", encoding.name());
|
||||
if (fileEncoding != null)
|
||||
encoding = Charset.forName(fileEncoding.substring("-Dfile.encoding=".length()));
|
||||
} catch (Throwable ex) {
|
||||
LOG.log(Level.WARNING, "Bad file encoding", ex);
|
||||
}
|
||||
}
|
||||
|
||||
Optional<Library> log4j = findLog4j();
|
||||
|
||||
// JVM Args
|
||||
if (!options.isNoGeneratedJVMArgs()) {
|
||||
appendJvmArgs(res);
|
||||
@@ -179,8 +193,22 @@ public class DefaultLauncher extends Launcher {
|
||||
res.addDefault("-Dfml.ignoreInvalidMinecraftCertificates=", "true");
|
||||
res.addDefault("-Dfml.ignorePatchDiscrepancies=", "true");
|
||||
|
||||
// Fix RCE vulnerability of log4j2
|
||||
res.addDefault("-Dlog4j2.formatMsgNoLookups=", "true");
|
||||
if (log4j.isPresent()) {
|
||||
String enableJndi = res.addDefault("-Dlog4j2.enableJndi=", "false");
|
||||
if (!"-Dlog4j2.enableJndi=true".equals(enableJndi)) {
|
||||
res.add("-javaagent:" + repository.getLibraryFile(version, LOG4J_PATCH_AGENT).getAbsolutePath() + "=" + log4j.get().getVersion().startsWith("2.0-beta"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fix RCE vulnerability of log4j2
|
||||
res.addDefault("-Djava.rmi.server.useCodebaseOnly=", "true");
|
||||
res.addDefault("-Dcom.sun.jndi.rmi.object.trustURLCodebase=", "false");
|
||||
res.addDefault("-Dcom.sun.jndi.cosnaming.object.trustURLCodebase=", "false");
|
||||
|
||||
String formatMsgNoLookups = res.addDefault("-Dlog4j2.formatMsgNoLookups=", "true");
|
||||
if (!"-Dlog4j2.formatMsgNoLookups=false".equals(formatMsgNoLookups) && log4j.isPresent()) {
|
||||
res.addDefault("-Dlog4j.configurationFile=", getLog4jConfigurationFile().getAbsolutePath());
|
||||
}
|
||||
|
||||
Proxy proxy = options.getProxy();
|
||||
@@ -227,8 +255,9 @@ public class DefaultLauncher extends Launcher {
|
||||
configuration.put("${natives_directory}", nativeFolderPath);
|
||||
|
||||
res.addAll(Arguments.parseArguments(version.getArguments().map(Arguments::getJvm).orElseGet(this::getDefaultJVMArguments), configuration));
|
||||
if (authInfo.getArguments() != null && authInfo.getArguments().getJvm() != null && !authInfo.getArguments().getJvm().isEmpty())
|
||||
res.addAll(Arguments.parseArguments(authInfo.getArguments().getJvm(), configuration));
|
||||
Arguments argumentsFromAuthInfo = authInfo.getLaunchArguments(options);
|
||||
if (argumentsFromAuthInfo != null && argumentsFromAuthInfo.getJvm() != null && !argumentsFromAuthInfo.getJvm().isEmpty())
|
||||
res.addAll(Arguments.parseArguments(argumentsFromAuthInfo.getJvm(), configuration));
|
||||
|
||||
res.add(version.getMainClass());
|
||||
|
||||
@@ -239,8 +268,8 @@ public class DefaultLauncher extends Launcher {
|
||||
if (version.getMinecraftArguments().isPresent()) {
|
||||
res.addAll(Arguments.parseArguments(this.getDefaultGameArguments(), configuration, features));
|
||||
}
|
||||
if (authInfo.getArguments() != null && authInfo.getArguments().getGame() != null && !authInfo.getArguments().getGame().isEmpty())
|
||||
res.addAll(Arguments.parseArguments(authInfo.getArguments().getGame(), configuration, features));
|
||||
if (argumentsFromAuthInfo != null && argumentsFromAuthInfo.getGame() != null && !argumentsFromAuthInfo.getGame().isEmpty())
|
||||
res.addAll(Arguments.parseArguments(argumentsFromAuthInfo.getGame(), configuration, features));
|
||||
|
||||
if (StringUtils.isNotBlank(options.getServerIp())) {
|
||||
String[] args = options.getServerIp().split(":");
|
||||
@@ -272,7 +301,7 @@ public class DefaultLauncher extends Launcher {
|
||||
res.addAllWithoutParsing(Arguments.parseStringArguments(options.getGameArguments(), configuration));
|
||||
|
||||
res.removeIf(it -> getForbiddens().containsKey(it) && getForbiddens().get(it).get());
|
||||
return new Command(res, tempNativeFolder);
|
||||
return new Command(res, tempNativeFolder, encoding);
|
||||
}
|
||||
|
||||
public Map<String, Boolean> getFeatures() {
|
||||
@@ -336,6 +365,48 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
}
|
||||
|
||||
private static final Library LOG4J_PATCH_AGENT = new Library(new Artifact("org.glavo", "log4j-patch-agent", "1.0"));
|
||||
|
||||
private Optional<Library> findLog4j() {
|
||||
return version.getLibraries().stream()
|
||||
.filter(it -> it.is("org.apache.logging.log4j", "log4j-core")
|
||||
&& (VersionNumber.VERSION_COMPARATOR.compare(it.getVersion(), "2.17") < 0 || "2.0-beta9".equals(it.getVersion())))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private boolean isLog4jUnsafe(Library log4j) {
|
||||
return VersionNumber.VERSION_COMPARATOR.compare(log4j.getVersion(), "2.17") < 0;
|
||||
}
|
||||
|
||||
public File getLog4jConfigurationFile() {
|
||||
return new File(repository.getVersionRoot(version.getId()), "log4j2.xml");
|
||||
}
|
||||
|
||||
public void extractLog4jConfigurationFile() throws IOException {
|
||||
File targetFile = getLog4jConfigurationFile();
|
||||
InputStream source;
|
||||
if (VersionNumber.VERSION_COMPARATOR.compare(repository.getGameVersion(version).orElse("0.0"), "1.12") < 0) {
|
||||
source = DefaultLauncher.class.getResourceAsStream("/assets/game/log4j2-1.7.xml");
|
||||
} else {
|
||||
source = DefaultLauncher.class.getResourceAsStream("/assets/game/log4j2-1.12.xml");
|
||||
}
|
||||
|
||||
try (InputStream input = source; OutputStream output = new FileOutputStream(targetFile)) {
|
||||
IOUtils.copyTo(input, output);
|
||||
}
|
||||
}
|
||||
|
||||
public void extractLog4jAgent() throws IOException {
|
||||
Path log4jPatchPath = repository.getLibraryFile(version, LOG4J_PATCH_AGENT).toPath();
|
||||
String patchName = LOG4J_PATCH_AGENT.getArtifactId() + "-" + LOG4J_PATCH_AGENT.getVersion();
|
||||
if (Files.notExists(log4jPatchPath)) {
|
||||
try (InputStream input = DefaultLauncher.class.getResourceAsStream("/assets/game/" + patchName + ".jar")) {
|
||||
Files.createDirectories(log4jPatchPath.getParent());
|
||||
Files.copy(input, log4jPatchPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Map<String, String> getConfigurations() {
|
||||
return mapOf(
|
||||
// defined by Minecraft official launcher
|
||||
@@ -380,15 +451,7 @@ public class DefaultLauncher extends Launcher {
|
||||
final Command command = generateCommandLine(nativeFolder);
|
||||
|
||||
// To guarantee that when failed to generate launch command line, we will not call pre-launch command
|
||||
List<String> rawCommandLine = command.commandLine.asMutableList();
|
||||
|
||||
// Pass classpath using the environment variable, to reduce the command length
|
||||
String classpath = null;
|
||||
final int cpIndex = rawCommandLine.indexOf("-cp");
|
||||
if (cpIndex >= 0 && cpIndex < rawCommandLine.size() - 1) {
|
||||
rawCommandLine.remove(cpIndex); // remove "-cp"
|
||||
classpath = rawCommandLine.remove(cpIndex);
|
||||
}
|
||||
List<String> rawCommandLine = command.commandLine.asList();
|
||||
|
||||
if (command.tempNativeFolder != null) {
|
||||
Files.deleteIfExists(command.tempNativeFolder);
|
||||
@@ -403,6 +466,13 @@ public class DefaultLauncher extends Launcher {
|
||||
decompressNatives(nativeFolder);
|
||||
}
|
||||
|
||||
Optional<Library> log4j = findLog4j();
|
||||
if (log4j.isPresent()) {
|
||||
extractLog4jConfigurationFile();
|
||||
if (isLog4jUnsafe(log4j.get()))
|
||||
extractLog4jAgent();
|
||||
}
|
||||
|
||||
File runDirectory = repository.getRunDirectory(version.getId());
|
||||
|
||||
if (StringUtils.isNotBlank(options.getPreLaunchCommand())) {
|
||||
@@ -419,21 +489,16 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
String appdata = options.getGameDir().getAbsoluteFile().getParent();
|
||||
if (appdata != null) builder.environment().put("APPDATA", appdata);
|
||||
if (classpath != null) {
|
||||
builder.environment().put("CLASSPATH", classpath);
|
||||
// Fix #1153: On Windows, the 'classpath' environment variable in the context overrides the 'CLASSPATH'
|
||||
// Environment variables on Windows are not case-sensitive; The lowercase 'classpath' overwrites any other case.
|
||||
builder.environment().put("classpath", classpath);
|
||||
}
|
||||
|
||||
builder.environment().putAll(getEnvVars());
|
||||
process = builder.start();
|
||||
} catch (IOException e) {
|
||||
throw new ProcessCreationException(e);
|
||||
}
|
||||
|
||||
ManagedProcess p = new ManagedProcess(process, rawCommandLine, classpath);
|
||||
ManagedProcess p = new ManagedProcess(process, rawCommandLine);
|
||||
if (listener != null)
|
||||
startMonitors(p, listener, daemon);
|
||||
startMonitors(p, listener, command.encoding, daemon);
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -477,6 +542,13 @@ public class DefaultLauncher extends Launcher {
|
||||
decompressNatives(nativeFolder);
|
||||
}
|
||||
|
||||
Optional<Library> log4j = findLog4j();
|
||||
if (log4j.isPresent()) {
|
||||
extractLog4jConfigurationFile();
|
||||
if (isLog4jUnsafe(log4j.get()))
|
||||
extractLog4jAgent();
|
||||
}
|
||||
|
||||
String scriptExtension = FileUtils.getExtension(scriptFile);
|
||||
boolean usePowerShell = "ps1".equals(scriptExtension);
|
||||
|
||||
@@ -594,17 +666,17 @@ public class DefaultLauncher extends Launcher {
|
||||
throw new ExecutionPolicyLimitException();
|
||||
}
|
||||
|
||||
private void startMonitors(ManagedProcess managedProcess, ProcessListener processListener, boolean isDaemon) {
|
||||
private void startMonitors(ManagedProcess managedProcess, ProcessListener processListener, Charset encoding, boolean isDaemon) {
|
||||
processListener.setProcess(managedProcess);
|
||||
Thread stdout = Lang.thread(new StreamPump(managedProcess.getProcess().getInputStream(), it -> {
|
||||
processListener.onLog(it, Optional.ofNullable(Log4jLevel.guessLevel(it)).orElse(Log4jLevel.INFO));
|
||||
managedProcess.addLine(it);
|
||||
}, OperatingSystem.NATIVE_CHARSET), "stdout-pump", isDaemon);
|
||||
}, encoding), "stdout-pump", isDaemon);
|
||||
managedProcess.addRelatedThread(stdout);
|
||||
Thread stderr = Lang.thread(new StreamPump(managedProcess.getProcess().getErrorStream(), it -> {
|
||||
processListener.onLog(it, Log4jLevel.ERROR);
|
||||
managedProcess.addLine(it);
|
||||
}, OperatingSystem.NATIVE_CHARSET), "stderr-pump", isDaemon);
|
||||
}, encoding), "stderr-pump", isDaemon);
|
||||
managedProcess.addRelatedThread(stderr);
|
||||
managedProcess.addRelatedThread(Lang.thread(new ExitWaiter(managedProcess, Arrays.asList(stdout, stderr), processListener::onExit), "exit-waiter", isDaemon));
|
||||
}
|
||||
@@ -612,10 +684,12 @@ public class DefaultLauncher extends Launcher {
|
||||
private static final class Command {
|
||||
final CommandBuilder commandLine;
|
||||
final Path tempNativeFolder;
|
||||
final Charset encoding;
|
||||
|
||||
Command(CommandBuilder commandBuilder, Path tempNativeFolder) {
|
||||
Command(CommandBuilder commandBuilder, Path tempNativeFolder, Charset encoding) {
|
||||
this.commandLine = commandBuilder;
|
||||
this.tempNativeFolder = tempNativeFolder;
|
||||
this.encoding = encoding;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.mod;
|
||||
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
@@ -184,6 +186,18 @@ public class RemoteMod {
|
||||
return hashes;
|
||||
}
|
||||
|
||||
public FileDownloadTask.IntegrityCheck getIntegrityCheck() {
|
||||
if (hashes.containsKey("md5")) {
|
||||
return new FileDownloadTask.IntegrityCheck("MD5", hashes.get("sha1"));
|
||||
} else if (hashes.containsKey("sha1")) {
|
||||
return new FileDownloadTask.IntegrityCheck("SHA-1", hashes.get("sha1"));
|
||||
} else if (hashes.containsKey("sha512")) {
|
||||
return new FileDownloadTask.IntegrityCheck("SHA-256", hashes.get("sha1"));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ public interface RemoteModRepository {
|
||||
}
|
||||
|
||||
String[] DEFAULT_GAME_VERSIONS = new String[]{
|
||||
"1.18.1", "1.18",
|
||||
"1.17.1", "1.17",
|
||||
"1.16.5", "1.16.4", "1.16.3", "1.16.2", "1.16.1", "1.16",
|
||||
"1.15.2", "1.15.1", "1.15",
|
||||
|
||||
@@ -62,7 +62,6 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
private final String version;
|
||||
private final File configurationFile;
|
||||
private ModpackConfiguration<McbbsModpackManifest> configuration;
|
||||
private GetTask dependent;
|
||||
private McbbsModpackManifest manifest;
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
|
||||
@@ -108,11 +107,11 @@ public class McbbsModpackCompletionTask extends CompletableFutureTask<Void> {
|
||||
}
|
||||
})).thenComposeAsync(wrap(unused1 -> {
|
||||
return executor.one(new GetTask(new URL(manifest.getFileApi() + "/manifest.json")));
|
||||
})).thenComposeAsync(wrap(unused1 -> {
|
||||
})).thenComposeAsync(wrap(remoteManifestJson -> {
|
||||
McbbsModpackManifest remoteManifest;
|
||||
// We needs to update modpack from online server.
|
||||
try {
|
||||
remoteManifest = JsonUtils.fromNonNullJson(dependent.getResult(), McbbsModpackManifest.class);
|
||||
remoteManifest = JsonUtils.fromNonNullJson(remoteManifestJson, McbbsModpackManifest.class);
|
||||
} catch (JsonParseException e) {
|
||||
throw new IOException("Unable to parse server manifest.json from " + manifest.getFileApi(), e);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ public final class Logging {
|
||||
FileHandler fileHandler = new FileHandler(logFolder.resolve("hmcl.log").toAbsolutePath().toString());
|
||||
fileHandler.setLevel(Level.FINEST);
|
||||
fileHandler.setFormatter(DefaultFormatter.INSTANCE);
|
||||
fileHandler.setEncoding("UTF-8");
|
||||
LOG.addHandler(fileHandler);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Unable to create hmcl.log, " + e.getMessage());
|
||||
@@ -88,7 +89,11 @@ public final class Logging {
|
||||
}
|
||||
|
||||
public static String getLogs() {
|
||||
return storedLogs.toString();
|
||||
try {
|
||||
return storedLogs.toString("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new InternalError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DefaultFormatter extends Formatter {
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.util.io;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
@@ -36,7 +37,6 @@ import java.util.function.Predicate;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huang
|
||||
*/
|
||||
public final class FileUtils {
|
||||
@@ -425,4 +425,24 @@ public final class FileUtils {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public static Path tmpSaveFile(Path file) {
|
||||
return file.toAbsolutePath().resolveSibling("." + file.getFileName().toString() + ".tmp");
|
||||
}
|
||||
|
||||
public static void saveSafely(Path file, String content) throws IOException {
|
||||
Path tmpFile = tmpSaveFile(file);
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(tmpFile, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)) {
|
||||
writer.write(content);
|
||||
}
|
||||
|
||||
try {
|
||||
if (Files.exists(file) && Files.getAttribute(file, "dos:hidden") == Boolean.TRUE) {
|
||||
Files.setAttribute(tmpFile, "dos:hidden", true);
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
Files.move(tmpFile, file, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
@@ -83,33 +83,33 @@ public final class CommandBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandBuilder addDefault(String opt) {
|
||||
public String addDefault(String opt) {
|
||||
for (Item item : raw) {
|
||||
if (item.arg.equals(opt)) {
|
||||
return this;
|
||||
return item.arg;
|
||||
}
|
||||
}
|
||||
raw.add(new Item(opt, true));
|
||||
return this;
|
||||
return null;
|
||||
}
|
||||
|
||||
public CommandBuilder addDefault(String opt, String value) {
|
||||
public String addDefault(String opt, String value) {
|
||||
for (Item item : raw) {
|
||||
if (item.arg.startsWith(opt)) {
|
||||
LOG.info("Default option '" + opt + value + "' is suppressed by '" + item.arg + "'");
|
||||
return this;
|
||||
return item.arg;
|
||||
}
|
||||
}
|
||||
raw.add(new Item(opt + value, true));
|
||||
return this;
|
||||
return null;
|
||||
}
|
||||
|
||||
public CommandBuilder addUnstableDefault(String opt, boolean value) {
|
||||
public String addUnstableDefault(String opt, boolean value) {
|
||||
for (Item item : raw) {
|
||||
final Matcher matcher = UNSTABLE_BOOLEAN_OPTION_PATTERN.matcher(item.arg);
|
||||
if (matcher.matches()) {
|
||||
if (matcher.group("key").equals(opt)) {
|
||||
return this;
|
||||
return item.arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,21 +119,21 @@ public final class CommandBuilder {
|
||||
} else {
|
||||
raw.add(new Item("-XX:-" + opt, true));
|
||||
}
|
||||
return this;
|
||||
return null;
|
||||
}
|
||||
|
||||
public CommandBuilder addUnstableDefault(String opt, String value) {
|
||||
public String addUnstableDefault(String opt, String value) {
|
||||
for (Item item : raw) {
|
||||
final Matcher matcher = UNSTABLE_OPTION_PATTERN.matcher(item.arg);
|
||||
if (matcher.matches()) {
|
||||
if (matcher.group("key").equals(opt)) {
|
||||
return this;
|
||||
return item.arg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
raw.add(new Item("-XX:" + opt + "=" + value, true));
|
||||
return this;
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean removeIf(Predicate<String> pred) {
|
||||
|
||||
@@ -112,6 +112,7 @@ public final class JavaVersion {
|
||||
public static final int JAVA_8 = 8;
|
||||
public static final int JAVA_9 = 9;
|
||||
public static final int JAVA_16 = 16;
|
||||
public static final int JAVA_17 = 17;
|
||||
|
||||
private static int parseVersion(String version) {
|
||||
Matcher matcher = VERSION.matcher(version);
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.charset.UnsupportedCharsetException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -112,15 +113,26 @@ public enum OperatingSystem {
|
||||
private static final Pattern MEMINFO_PATTERN = Pattern.compile("^(?<key>.*?):\\s+(?<value>\\d+) kB?$");
|
||||
|
||||
static {
|
||||
String nativeEncoding = System.getProperty("native.encoding", System.getProperty("sun.jnu.encoding"));
|
||||
String nativeEncoding = System.getProperty("native.encoding");
|
||||
String hmclNativeEncoding = System.getProperty("hmcl.native.encoding");
|
||||
Charset nativeCharset = Charset.defaultCharset();
|
||||
|
||||
if (nativeEncoding != null) {
|
||||
try {
|
||||
nativeCharset = Charset.forName(nativeEncoding);
|
||||
} catch (UnsupportedCharsetException e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
if (hmclNativeEncoding != null) {
|
||||
nativeCharset = Charset.forName(hmclNativeEncoding);
|
||||
} else {
|
||||
if (nativeEncoding != null && !nativeEncoding.equalsIgnoreCase(nativeCharset.name())) {
|
||||
nativeCharset = Charset.forName(nativeEncoding);
|
||||
}
|
||||
|
||||
if (nativeCharset == StandardCharsets.UTF_8 || nativeCharset == StandardCharsets.US_ASCII) {
|
||||
nativeCharset = StandardCharsets.UTF_8;
|
||||
} else if ("GBK".equalsIgnoreCase(nativeCharset.name()) || "GB2312".equalsIgnoreCase(nativeCharset.name())) {
|
||||
nativeCharset = Charset.forName("GB18030");
|
||||
}
|
||||
}
|
||||
} catch (UnsupportedCharsetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
NATIVE_CHARSET = nativeCharset;
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="WARN">
|
||||
<Appenders>
|
||||
<Console name="SysOut" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg{nolookups}%n" />
|
||||
</Console>
|
||||
<Queue name="ServerGuiConsole">
|
||||
<PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg{nolookups}%n" />
|
||||
</Queue>
|
||||
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
|
||||
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg{nolookups}%n" />
|
||||
<Policies>
|
||||
<TimeBasedTriggeringPolicy />
|
||||
<OnStartupTriggeringPolicy />
|
||||
</Policies>
|
||||
</RollingRandomAccessFile>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="info">
|
||||
<filters>
|
||||
<MarkerFilter marker="NETWORK_PACKETS" onMatch="DENY" onMismatch="NEUTRAL" />
|
||||
</filters>
|
||||
<AppenderRef ref="SysOut"/>
|
||||
<AppenderRef ref="File"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="WARN">
|
||||
<Appenders>
|
||||
<Console name="SysOut" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg%n" />
|
||||
</Console>
|
||||
<Queue name="ServerGuiConsole">
|
||||
<PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg%n" />
|
||||
</Queue>
|
||||
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
|
||||
<PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg%n" />
|
||||
<Policies>
|
||||
<TimeBasedTriggeringPolicy />
|
||||
<OnStartupTriggeringPolicy />
|
||||
</Policies>
|
||||
</RollingRandomAccessFile>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="info">
|
||||
<filters>
|
||||
<MarkerFilter marker="NETWORK_PACKETS" onMatch="DENY" onMismatch="NEUTRAL" />
|
||||
<RegexFilter regex=".*\$\{[^}]*\}.*" onMatch="DENY" onMismatch="NEUTRAL"/>
|
||||
</filters>
|
||||
<AppenderRef ref="SysOut"/>
|
||||
<AppenderRef ref="File"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
-1
@@ -1 +0,0 @@
|
||||
org.jackhuang.hmcl.HMCLTransformerDiscoveryService
|
||||
@@ -58,5 +58,6 @@ Make sure you have Java installed with JavaFX 8 at least. Liberica full JDK 8 or
|
||||
|`-Dhmcl.update_source.override=<url>`|Override the update source.|
|
||||
|`-Dhmcl.authlibinjector.location=<path>`|Use specified authlib-injector (instead of downloading one).|
|
||||
|`-Dhmcl.openjfx.repo=<maven repository url>`|Add custom maven repository for download OpenJFX.|
|
||||
|`-Dhmcl.native.encoding=<encoding>`|Override the native encoding.|
|
||||
|`-Dhmcl.microsoft.auth.id=<App ID>`|Override Microsoft OAuth App ID.|
|
||||
|`-Dhmcl.microsoft.auth.secret=<App Secret>`|Override Microsoft OAuth App secret.|
|
||||
+5
@@ -4,6 +4,11 @@ dependencies {
|
||||
compileOnly project.files("lib/modlauncher-4.1.0.jar")
|
||||
}
|
||||
|
||||
compileJava {
|
||||
sourceCompatibility = 8
|
||||
targetCompatibility = 8
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes 'Created-By': 'Copyright(c) 2013-2020 huangyuhui.',
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,13 @@
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
@@ -0,0 +1,6 @@
|
||||
# Log4j Patch
|
||||
|
||||
Copy from [Glavo/log4j-patch](https://github.com/Glavo/log4j-patch/).
|
||||
|
||||
`org.apache.logging.log4j.core.lookup.JndiLookup` is licensed under the WTFPL 2.0 license,
|
||||
and `org.apache.logging.log4j.core.lookup.Interpolator` is licensed under the Apache 2.0 license.
|
||||
@@ -0,0 +1,52 @@
|
||||
version '1.0'
|
||||
|
||||
sourceSets.create("agent") {
|
||||
java {
|
||||
srcDir 'src/main/agent'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.0-beta9'
|
||||
}
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
sourceCompatibility = 8
|
||||
targetCompatibility = 8
|
||||
|
||||
doLast {
|
||||
FileTree tree = fileTree('build/classes/java')
|
||||
tree.include '**/*.class'
|
||||
tree.each {
|
||||
RandomAccessFile rf = new RandomAccessFile(it, "rw")
|
||||
rf.seek(7) // major version
|
||||
rf.write(50) // java 6
|
||||
rf.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task agentJar(type: Jar) {
|
||||
dependsOn(tasks.compileJava)
|
||||
|
||||
baseName = 'log4j-patch-agent'
|
||||
|
||||
manifest {
|
||||
attributes 'Premain-Class': 'org.glavo.log4j.patch.agent.Log4jAgent'
|
||||
}
|
||||
|
||||
from(sourceSets.agent.output)
|
||||
from(sourceSets.main.output) {
|
||||
includeEmptyDirs = false
|
||||
|
||||
eachFile {
|
||||
it.path = "org/glavo/log4j/patch/agent/${it.name}.bin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
enabled = false
|
||||
dependsOn agentJar
|
||||
}
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package org.glavo.log4j.patch.agent;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.IllegalClassFormatException;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class Log4jAgent {
|
||||
static final String JNDI_LOOKUP_CLASS_NAME = "org/apache/logging/log4j/core/lookup/JndiLookup";
|
||||
static final String INTERPOLATOR_CLASS_NAME = "org/apache/logging/log4j/core/lookup/Interpolator";
|
||||
|
||||
static final byte[] INTERPOLATOR_CLASS_SHA = {53, 103, 16, 123, 51, 29, 65, -70, -32, 71, -11, 7, 114, -15, 72, 127, 40, -38, 35, 18};
|
||||
|
||||
static boolean isBeta = false;
|
||||
|
||||
private static byte[] loadResource(String name) {
|
||||
try {
|
||||
try (InputStream input = Log4jAgent.class.getResourceAsStream(name)) {
|
||||
if (input == null) {
|
||||
throw new AssertionError(name + " not found");
|
||||
}
|
||||
int available = input.available();
|
||||
if (available <= 0) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
byte[] res = new byte[available];
|
||||
if (input.read(res) != available) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new InternalError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void premain(String agentArgs, Instrumentation inst) throws Exception {
|
||||
if ("true".equals(agentArgs)) {
|
||||
isBeta = true;
|
||||
}
|
||||
inst.addTransformer(new Transformer());
|
||||
}
|
||||
|
||||
private static final class Transformer implements ClassFileTransformer {
|
||||
|
||||
@Override
|
||||
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
|
||||
if (!isBeta && JNDI_LOOKUP_CLASS_NAME.equals(className)) {
|
||||
return loadResource("JndiLookup.class.bin");
|
||||
}
|
||||
if (isBeta && INTERPOLATOR_CLASS_NAME.equals(className)) {
|
||||
try {
|
||||
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
|
||||
sha1.update(classfileBuffer);
|
||||
if (Arrays.equals(INTERPOLATOR_CLASS_SHA, sha1.digest())) {
|
||||
return loadResource("Interpolator.class.bin");
|
||||
}
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new InternalError(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache license, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the license for the specific language governing permissions and
|
||||
* limitations under the license.
|
||||
*/
|
||||
package org.apache.logging.log4j.core.lookup;
|
||||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
import org.apache.logging.log4j.core.config.plugins.PluginManager;
|
||||
import org.apache.logging.log4j.core.config.plugins.PluginType;
|
||||
import org.apache.logging.log4j.status.StatusLogger;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The Interpolator is a StrLookup that acts as a proxy for all the other StrLookups.
|
||||
*/
|
||||
public class Interpolator implements StrLookup {
|
||||
|
||||
private static final Logger LOGGER = StatusLogger.getLogger();
|
||||
|
||||
/** Constant for the prefix separator. */
|
||||
private static final char PREFIX_SEPARATOR = ':';
|
||||
|
||||
private final Map<String, StrLookup> lookups = new HashMap<String, StrLookup>();
|
||||
|
||||
private final StrLookup defaultLookup;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public Interpolator(final StrLookup defaultLookup) {
|
||||
this.defaultLookup = defaultLookup == null ? new MapLookup(new HashMap<String, String>()) : defaultLookup;
|
||||
final PluginManager manager = new PluginManager("Lookup");
|
||||
manager.collectPlugins();
|
||||
final Map<String, PluginType<?>> plugins = manager.getPlugins();
|
||||
|
||||
for (final Map.Entry<String, PluginType<?>> entry : plugins.entrySet()) {
|
||||
@SuppressWarnings("unchecked")
|
||||
final Class<? extends StrLookup> clazz = (Class<? extends StrLookup>) entry.getValue().getPluginClass();
|
||||
try {
|
||||
lookups.put(entry.getKey(), clazz.newInstance());
|
||||
} catch (final Exception ex) {
|
||||
LOGGER.error("Unable to create Lookup for " + entry.getKey(), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the default Interpolator using only Lookups that work without an event.
|
||||
*/
|
||||
public Interpolator() {
|
||||
this.defaultLookup = new MapLookup(new HashMap<String, String>());
|
||||
lookups.put("sys", new SystemPropertiesLookup());
|
||||
lookups.put("env", new EnvironmentLookup());
|
||||
// lookups.put("jndi", new JndiLookup());
|
||||
LOGGER.warn("Log4j patch works, JNDI lookup is disabled. This message is not a warning, it represents the successful elimination of some security risks. You should be able to play safely.");
|
||||
|
||||
try {
|
||||
if (Class.forName("javax.servlet.ServletContext") != null) {
|
||||
lookups.put("web", new WebLookup());
|
||||
}
|
||||
} catch (ClassNotFoundException ex) {
|
||||
LOGGER.debug("ServletContext not present - WebLookup not added");
|
||||
} catch (Exception ex) {
|
||||
LOGGER.error("Unable to locate ServletContext", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the specified variable. This implementation will try to extract
|
||||
* a variable prefix from the given variable name (the first colon (':') is
|
||||
* used as prefix separator). It then passes the name of the variable with
|
||||
* the prefix stripped to the lookup object registered for this prefix. If
|
||||
* no prefix can be found or if the associated lookup object cannot resolve
|
||||
* this variable, the default lookup object will be used.
|
||||
*
|
||||
* @param var the name of the variable whose value is to be looked up
|
||||
* @return the value of this variable or <b>null</b> if it cannot be
|
||||
* resolved
|
||||
*/
|
||||
@Override
|
||||
public String lookup(final String var) {
|
||||
return lookup(null, var);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the specified variable. This implementation will try to extract
|
||||
* a variable prefix from the given variable name (the first colon (':') is
|
||||
* used as prefix separator). It then passes the name of the variable with
|
||||
* the prefix stripped to the lookup object registered for this prefix. If
|
||||
* no prefix can be found or if the associated lookup object cannot resolve
|
||||
* this variable, the default lookup object will be used.
|
||||
*
|
||||
* @param event The current LogEvent or null.
|
||||
* @param var the name of the variable whose value is to be looked up
|
||||
* @return the value of this variable or <b>null</b> if it cannot be
|
||||
* resolved
|
||||
*/
|
||||
@Override
|
||||
public String lookup(final LogEvent event, String var) {
|
||||
if (var == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int prefixPos = var.indexOf(PREFIX_SEPARATOR);
|
||||
if (prefixPos >= 0) {
|
||||
final String prefix = var.substring(0, prefixPos);
|
||||
final String name = var.substring(prefixPos + 1);
|
||||
final StrLookup lookup = lookups.get(prefix);
|
||||
String value = null;
|
||||
if (lookup != null) {
|
||||
value = event == null ? lookup.lookup(name) : lookup.lookup(event, name);
|
||||
}
|
||||
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
var = var.substring(prefixPos + 1);
|
||||
}
|
||||
if (defaultLookup != null) {
|
||||
return event == null ? defaultLookup.lookup(var) : defaultLookup.lookup(event, var);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (final String name : lookups.keySet()) {
|
||||
if (sb.length() == 0) {
|
||||
sb.append("{");
|
||||
} else {
|
||||
sb.append(", ");
|
||||
}
|
||||
|
||||
sb.append(name);
|
||||
}
|
||||
if (sb.length() > 0) {
|
||||
sb.append("}");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright © 2021 Glavo <zjx001202@gmail.com>
|
||||
* This work is free. You can redistribute it and/or modify it under the
|
||||
* terms of the Do What The Fuck You Want To Public License, Version 2,
|
||||
* as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
|
||||
*/
|
||||
package org.apache.logging.log4j.core.lookup;
|
||||
|
||||
public class JndiLookup {
|
||||
public JndiLookup() {
|
||||
throw new NoClassDefFoundError("JNDI lookup is disabled. This is not an error. We are blocking some vulnerabilities through it. You should be able to play safely.");
|
||||
}
|
||||
}
|
||||
@@ -2,3 +2,7 @@ rootProject.name = 'HMCL3'
|
||||
include ':HMCL'
|
||||
include ':HMCLCore'
|
||||
include ':HMCLTransformerDiscoveryService'
|
||||
include ':log4j-patch'
|
||||
|
||||
project(':HMCLTransformerDiscoveryService').projectDir = file('minecraft/libraries/HMCLTransformerDiscoveryService')
|
||||
project(':log4j-patch').projectDir = file('minecraft/libraries/log4j-patch')
|
||||
Reference in new issue
Block a user