Compare commits

...
Author SHA1 Message Date
huanghongxun 295499c17b fix: missing translations. 2021-12-18 16:55:33 +08:00
Glavo 043d1c88c8 Fix #1096: Feedback item cannot be clicked 2021-12-18 16:49:42 +08:00
bling-yshs 55e9b93b1c fix curse category 2021-12-18 16:49:23 +08:00
Glavo aa5e3e9697 Fix #1148: Specify console encoding explicitly 2021-12-18 15:59:24 +08:00
Glavo fbf7ff6ebd Lifting GB2312 to GB18030 2021-12-18 15:59:24 +08:00
Glavo 3a5e8a827c update README 2021-12-18 15:59:24 +08:00
Glavo 6176dfaaf7 Lifting encoding to be compatible with more Unicode characters 2021-12-18 15:59:24 +08:00
Glavo 76c361e9bc Fix #1201: Download version jar if the jar is empty 2021-12-18 15:58:33 +08:00
Glavo ccb8981634 Remove invalid Appender 2021-12-18 15:47:41 +08:00
Glavo 99b55d03c1 Allows user to enable message lookup 2021-12-18 15:47:41 +08:00
Glavo 651fa6e2d1 fix #663: Incorrect judgment of WebView loading status 2021-12-18 15:46:24 +08:00
Glavo 4b258da0a0 Fix code format 2021-12-18 15:45:35 +08:00
Glavo 9a66605616 Disable JndiLookup using log4j-patch 2021-12-18 15:45:35 +08:00
Glavo da13895b72 fix: InputStream not closed 2021-12-18 15:45:35 +08:00
Glavo c657a1d2cc Introducing log4j-patch 2021-12-18 15:45:35 +08:00
Glavo eac8896f3e fix NPE in MultiplayerManager 2021-12-18 15:39:57 +08:00
Gamom 113f4f948b Update I18N_zh_CN.properties
翻译问题
解决 https://github.com/huanghongxun/HMCL/issues/1203#issuecomment-997155654
2021-12-18 15:39:26 +08:00
huanghongxun 1b8dc1f75e fix(mcbbs): NPE when fileApi is not empty. Closes #1197. 2021-12-18 01:40:25 +08:00
huanghongxun 001236e8e6 feat(download): refresh button 2021-12-18 01:40:25 +08:00
Glavo 8025b8e24b Support offline installation of OptiFine 1.17.1 H1 and later 2021-12-18 01:26:03 +08:00
Glavo e48bf012a6 Safely save config 2021-12-18 01:21:45 +08:00
Glavo 94e32694b2 fix: Game should use global settings by default 2021-12-12 16:51:01 +08:00
37 changed files with 456 additions and 73 deletions

No files matched your search

+6 -3
View File
@@ -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
-1
View File
@@ -129,7 +129,6 @@ shadowJar {
exclude(dependency('com.google.code.gson:.*:.*'))
exclude(dependency('com.github.steveice10:.*:.*'))
exclude(dependency('libs:JFoenix:.*'))
exclude(project(":HMCLTransformerDiscoveryService"))
}
manifest {
@@ -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 及更高
+8
View File
@@ -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<>();
@@ -50,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;
/**
@@ -116,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,15 +195,15 @@ 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");
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");
}
if (isUsingLog4j()) {
// 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());
}
@@ -281,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() {
@@ -469,14 +484,13 @@ 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;
}
private Map<String, String> getEnvVars() {
String versionName = Optional.ofNullable(options.getVersionName()).orElse(version.getId());
Map<String, String> env = new HashMap<>();
env.put("LOG4J_FORMAT_MSG_NO_LOOKUPS", "true");
env.put("INST_NAME", versionName);
env.put("INST_ID", versionName);
env.put("INST_DIR", repository.getVersionRoot(version.getId()).getAbsolutePath());
@@ -635,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));
}
@@ -653,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);
}
@@ -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;
@@ -22,7 +22,6 @@
</filters>
<AppenderRef ref="SysOut"/>
<AppenderRef ref="File"/>
<AppenderRef ref="ServerGuiConsole"/>
</Root>
</Loggers>
</Configuration>
@@ -23,7 +23,6 @@
</filters>
<AppenderRef ref="SysOut"/>
<AppenderRef ref="File"/>
<AppenderRef ref="ServerGuiConsole"/>
</Root>
</Loggers>
</Configuration>
@@ -1 +0,0 @@
org.jackhuang.hmcl.HMCLTransformerDiscoveryService
+1
View File
@@ -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.|
@@ -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.',
+13
View File
@@ -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,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
}
@@ -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();
}
}
@@ -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");
}
}
+4
View File
@@ -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')