Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
c31269a358 | ||
|
|
55092326a4 | ||
|
|
10dbcf63b7 | ||
|
|
32fad77104 | ||
|
|
05d2fbbbb9 | ||
|
|
916733a1e8 |
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,7 +18,11 @@ jobs:
|
||||
- name: Fetch last version
|
||||
run: |
|
||||
wget -O ci.json https://ci.huangyuhui.net/job/HMCL/lastSuccessfulBuild/api/json
|
||||
|
||||
|
||||
export HMCL_EXE_FILE_NAME=`cat ci.json | jq -M -r '.artifacts[] | select(.fileName | endswith(".exe")) | .fileName'`
|
||||
if [ -z `echo $HMCL_EXE_FILE_NAME | grep "^HMCL-[0-9]\.[0-9]\.[0-9]\{3\}\.exe\$"` ]; then exit 1; fi
|
||||
|
||||
export HMCL_BASE_VERSION=${HMCL_EXE_FILE_NAME:5:3}
|
||||
export HMCL_BUILD_NUMBER=`cat ci.json | jq -M -r '.id'`
|
||||
export HMCL_VERSION="$HMCL_BASE_VERSION.$HMCL_BUILD_NUMBER"
|
||||
export HMCL_COMMIT_SHA=`cat ci.json | jq -M -r '.actions[] | select(._class == "hudson.plugins.git.util.BuildData") | .lastBuiltRevision.SHA1'`
|
||||
@@ -28,6 +30,7 @@ jobs:
|
||||
if [ "${#HMCL_BUILD_NUMBER}" != 3 ]; then exit 1; fi
|
||||
if [ "${#HMCL_COMMIT_SHA}" != 40 ]; then exit 1; fi
|
||||
|
||||
echo "HMCL_BASE_VERSION=$HMCL_BASE_VERSION" >> $GITHUB_ENV
|
||||
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
|
||||
@@ -63,5 +66,3 @@ jobs:
|
||||
target_commitish: ${{ env.HMCL_COMMIT_SHA }}
|
||||
name: ${{ env.HMCL_TAG_NAME }}
|
||||
tag_name: ${{ env.HMCL_TAG_NAME }}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -400,7 +400,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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 及更高
|
||||
|
||||
@@ -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 及更高
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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,57 @@ 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);
|
||||
}
|
||||
|
||||
Library log4jPatch = null;
|
||||
|
||||
List<Library> libraries = version.getLibraries();
|
||||
for (Library library : libraries) {
|
||||
if (library.is("org.apache.logging.log4j", "log4j-core")) {
|
||||
if (library.getVersion().startsWith("2.0-beta")) {
|
||||
if ("2.0-beta9".equals(library.getVersion())) {
|
||||
log4jPatch = new Library(new Artifact("org.glavo", "log4j-patch-beta9", "1.0"));
|
||||
} else {
|
||||
Logging.LOG.warning("Log4j " + library.getVersion() + " cannot be patched");
|
||||
}
|
||||
} else if (VersionNumber.VERSION_COMPARATOR.compare(library.getVersion(), "2.16") < 0) {
|
||||
log4jPatch = new Library(new Artifact("org.glavo", "log4j-patch", "1.0"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (log4jPatch != null) {
|
||||
ArrayList<Library> patchedLibraries = new ArrayList<>(libraries.size() + 1);
|
||||
patchedLibraries.add(log4jPatch);
|
||||
patchedLibraries.addAll(libraries);
|
||||
version = version.setLibraries(patchedLibraries);
|
||||
|
||||
Path log4jPatchPath = repository.getLibraryFile(version, log4jPatch).toPath();
|
||||
String patchName = log4jPatch.getArtifactId() + "-" + log4jPatch.getVersion();
|
||||
if (Files.notExists(log4jPatchPath)) {
|
||||
try (InputStream input = MaintainTask.class.getResourceAsStream("/assets/game/" + patchName + ".jar")) {
|
||||
Files.createDirectories(log4jPatchPath.getParent());
|
||||
Files.copy(input, log4jPatchPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to unpack " + patchName, e);
|
||||
}
|
||||
}
|
||||
Logging.LOG.info("Apply patch " + patchName + " to log4j");
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
public static Version maintainPreservingPatches(GameRepository repository, Version version) {
|
||||
@@ -136,10 +176,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);
|
||||
}
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
@@ -31,12 +31,14 @@ 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;
|
||||
@@ -48,8 +50,10 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -114,6 +118,19 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
// JVM Args
|
||||
if (!options.isNoGeneratedJVMArgs()) {
|
||||
appendJvmArgs(res);
|
||||
@@ -180,6 +197,16 @@ public class DefaultLauncher extends Launcher {
|
||||
res.addDefault("-Dfml.ignorePatchDiscrepancies=", "true");
|
||||
}
|
||||
|
||||
// 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) && isUsingLog4j()) {
|
||||
res.addDefault("-Dlog4j.configurationFile=", getLog4jConfigurationFile().getAbsolutePath());
|
||||
}
|
||||
|
||||
Proxy proxy = options.getProxy();
|
||||
if (proxy != null && StringUtils.isBlank(options.getProxyUser()) && StringUtils.isBlank(options.getProxyPass())) {
|
||||
InetSocketAddress address = (InetSocketAddress) options.getProxy().address();
|
||||
@@ -269,7 +296,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() {
|
||||
@@ -333,6 +360,29 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUsingLog4j() {
|
||||
return VersionNumber.VERSION_COMPARATOR.compare(repository.getGameVersion(version).orElse("Unknown"), "1.7") >= 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("Unknown"), "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);
|
||||
}
|
||||
}
|
||||
|
||||
protected Map<String, String> getConfigurations() {
|
||||
return mapOf(
|
||||
// defined by Minecraft official launcher
|
||||
@@ -400,6 +450,10 @@ public class DefaultLauncher extends Launcher {
|
||||
decompressNatives(nativeFolder);
|
||||
}
|
||||
|
||||
if (isUsingLog4j()) {
|
||||
extractLog4jConfigurationFile();
|
||||
}
|
||||
|
||||
File runDirectory = repository.getRunDirectory(version.getId());
|
||||
|
||||
if (StringUtils.isNotBlank(options.getPreLaunchCommand())) {
|
||||
@@ -430,7 +484,7 @@ public class DefaultLauncher extends Launcher {
|
||||
|
||||
ManagedProcess p = new ManagedProcess(process, rawCommandLine, classpath);
|
||||
if (listener != null)
|
||||
startMonitors(p, listener, daemon);
|
||||
startMonitors(p, listener, command.encoding, daemon);
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -474,6 +528,10 @@ public class DefaultLauncher extends Launcher {
|
||||
decompressNatives(nativeFolder);
|
||||
}
|
||||
|
||||
if (isUsingLog4j()) {
|
||||
extractLog4jConfigurationFile();
|
||||
}
|
||||
|
||||
String scriptExtension = FileUtils.getExtension(scriptFile);
|
||||
boolean usePowerShell = "ps1".equals(scriptExtension);
|
||||
|
||||
@@ -591,17 +649,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));
|
||||
}
|
||||
@@ -609,10 +667,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -17,13 +17,14 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Date;
|
||||
import java.util.logging.*;
|
||||
|
||||
@@ -35,7 +36,7 @@ public final class Logging {
|
||||
}
|
||||
|
||||
public static final Logger LOG = Logger.getLogger("HMCL");
|
||||
private static ByteArrayOutputStream storedLogs = new ByteArrayOutputStream();
|
||||
private static final ByteArrayOutputStream storedLogs = new ByteArrayOutputStream(IOUtils.DEFAULT_BUFFER_SIZE);
|
||||
|
||||
public static void start(Path logFolder) {
|
||||
LOG.setLevel(Level.ALL);
|
||||
@@ -46,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());
|
||||
@@ -87,27 +89,29 @@ 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 {
|
||||
|
||||
static final DefaultFormatter INSTANCE = new DefaultFormatter();
|
||||
private final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
|
||||
private static final MessageFormat format = new MessageFormat("[{0,date,HH:mm:ss}] [{1}.{2}/{3}] {4}\n");
|
||||
|
||||
@Override
|
||||
public String format(LogRecord record) {
|
||||
String date = format.format(new Date(record.getMillis()));
|
||||
String log = String.format("[%s] [%s.%s/%s] %s%n",
|
||||
date, record.getSourceClassName(), record.getSourceMethodName(),
|
||||
record.getLevel().getName(), record.getMessage()
|
||||
);
|
||||
ByteArrayOutputStream builder = new ByteArrayOutputStream();
|
||||
String log = format.format(new Object[]{
|
||||
new Date(record.getMillis()),
|
||||
record.getSourceClassName(), record.getSourceMethodName(), record.getLevel().getName(),
|
||||
record.getMessage()
|
||||
}, new StringBuffer(128), null).toString();
|
||||
if (record.getThrown() != null)
|
||||
try (PrintWriter writer = new PrintWriter(builder)) {
|
||||
record.getThrown().printStackTrace(writer);
|
||||
}
|
||||
return log + builder.toString();
|
||||
log += StringUtils.getStackTrace(record.getThrown());
|
||||
|
||||
return log;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,9 +19,7 @@ package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.io.*;
|
||||
import java.nio.charset.CharsetEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
@@ -37,13 +35,11 @@ public final class StringUtils {
|
||||
}
|
||||
|
||||
public static String getStackTrace(Throwable throwable) {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
try {
|
||||
throwable.printStackTrace(new PrintStream(stream, false, "UTF-8"));
|
||||
return stream.toString("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new InternalError(e);
|
||||
StringWriter stringWriter = new StringWriter(512);
|
||||
try (PrintWriter printWriter = new PrintWriter(stringWriter)) {
|
||||
throwable.printStackTrace(printWriter);
|
||||
}
|
||||
return stringWriter.toString();
|
||||
}
|
||||
|
||||
public static String getStackTrace(StackTraceElement[] elements) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ public final class Zipper implements Closeable {
|
||||
if (filter != null && !filter.test(relativePath.replace('\\', '/'))) {
|
||||
return FileVisitResult.SKIP_SUBTREE;
|
||||
}
|
||||
Files.copy(file, root.resolve(relativePath));
|
||||
Files.copy(file, root.resolve(relativePath), StandardCopyOption.COPY_ATTRIBUTES);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@@ -104,11 +104,11 @@ public final class Zipper implements Closeable {
|
||||
}
|
||||
|
||||
public void putFile(Path file, String path) throws IOException {
|
||||
Files.copy(file, fs.getPath(path));
|
||||
Files.copy(file, fs.getPath(path), StandardCopyOption.COPY_ATTRIBUTES);
|
||||
}
|
||||
|
||||
public void putStream(InputStream in, String path) throws IOException {
|
||||
Files.copy(in, fs.getPath(path));
|
||||
Files.copy(in, fs.getPath(path), StandardCopyOption.COPY_ATTRIBUTES);
|
||||
}
|
||||
|
||||
public void putTextFile(String text, String path) throws IOException {
|
||||
|
||||
@@ -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
|
||||
@@ -11,7 +11,9 @@ No plugin API is provided.
|
||||
## Download
|
||||
Download the latest version [from the official website](https://hmcl.huangyuhui.net/download)
|
||||
|
||||
Note: Github releases are outdated.
|
||||
Note: The recent versions in Github release is the beta version, which contains extra testing functions compared to the release versions on the official website. However, they may be unstable and you're more likely to encounter bugs or unexpected problems.
|
||||
|
||||
If not necessary, it is recommended to download the ones from the official website.
|
||||
|
||||
## License
|
||||
The software is distributed under [GPL v3](https://www.gnu.org/licenses/gpl-3.0.html) with additional terms.
|
||||
@@ -56,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 <[email protected]>
|
||||
|
||||
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,46 @@
|
||||
version '1.0'
|
||||
|
||||
dependencies {
|
||||
compileOnly group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.0-beta9'
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
sourceCompatibility = 8
|
||||
targetCompatibility = 8
|
||||
|
||||
doLast {
|
||||
FileTree tree = fileTree('build/classes/java/main')
|
||||
tree.include '**/*.class'
|
||||
tree.each {
|
||||
RandomAccessFile rf = new RandomAccessFile(it, "rw")
|
||||
rf.seek(7) // major version
|
||||
rf.write(50) // java 6
|
||||
rf.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task patchJar(type: Jar) {
|
||||
dependsOn(tasks.compileJava)
|
||||
|
||||
baseName = 'log4j-patch'
|
||||
|
||||
from(sourceSets.main.output) {
|
||||
include('**/JndiLookup.class')
|
||||
}
|
||||
}
|
||||
|
||||
task patchBeta9Jar(type: Jar) {
|
||||
dependsOn(tasks.compileJava)
|
||||
|
||||
baseName = 'log4j-patch-beta9'
|
||||
|
||||
from(sourceSets.main.output) {
|
||||
include '**/Interpolator.class'
|
||||
}
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
enabled = false
|
||||
dependsOn patchJar, patchBeta9Jar
|
||||
}
|
||||
+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");
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
@@ -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