Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31568a5806 | ||
|
|
9aa73f5a5c | ||
|
|
35943feb08 | ||
|
|
73de21f936 | ||
|
|
4f1af87ba0 | ||
|
|
d129bd3e6b | ||
|
|
c2c52e8747 | ||
|
|
7e2ab49f89 | ||
|
|
3192d41fe3 | ||
|
|
ad5b260460 | ||
|
|
e57cb0ce51 | ||
|
|
9514357391 | ||
|
|
6fd0a40cc6 | ||
|
|
350d63231f | ||
|
|
cc28f1d5b5 | ||
|
|
4b73af9250 | ||
|
|
6e0efe521d | ||
|
|
041ab30778 | ||
|
|
678be4b92f | ||
|
|
74d759a45b |
No files matched your search
@@ -20,18 +20,13 @@ jobs:
|
||||
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
|
||||
if [ -z `echo $HMCL_EXE_FILE_NAME | grep -E "^HMCL-[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?\.exe\$"` ]; then exit 1; fi
|
||||
|
||||
export HMCL_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_VERSION=`echo "${HMCL_EXE_FILE_NAME%.exe}" | tail -c +6`
|
||||
export HMCL_COMMIT_SHA=`cat ci.json | jq -M -r '.actions[] | select(._class == "hudson.plugins.git.util.BuildData") | .lastBuiltRevision.SHA1'`
|
||||
|
||||
if [ "${#HMCL_BUILD_NUMBER}" != 3 ]; then exit 1; fi
|
||||
if [ "${#HMCL_COMMIT_SHA}" != 40 ]; then exit 1; fi
|
||||
|
||||
echo "HMCL_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
|
||||
echo "HMCL_TAG_NAME=v$HMCL_VERSION" >> $GITHUB_ENV
|
||||
@@ -45,7 +40,7 @@ jobs:
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar"
|
||||
wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar.sha1"
|
||||
env:
|
||||
DOWNLOAD_BASE_URL: https://ci.huangyuhui.net/job/HMCL/${{ env.HMCL_BUILD_NUMBER }}/artifact/HMCL/build/libs
|
||||
DOWNLOAD_BASE_URL: https://ci.huangyuhui.net/job/HMCL/lastSuccessfulBuild/artifact/HMCL/build/libs
|
||||
- name: Generate release note
|
||||
if: ${{ env.continue == 'true' }}
|
||||
run: |
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
name: Sync to Gitee
|
||||
|
||||
on:
|
||||
push
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Mirror Github to Gitee
|
||||
uses: Yikun/hub-mirror-action@v1.1
|
||||
with:
|
||||
src: github/huanghongxun
|
||||
dst: gitee/huanghongxun
|
||||
static_list: 'HMCL'
|
||||
force_update: true
|
||||
debug: true
|
||||
dst_key: ${{ secrets.GITEE_SYNC_BOT_PRIVATE_KEY }}
|
||||
dst_token: ${{ secrets.GITEE_SYNC_BOT_TOKEN }}
|
||||
cache_path: /github/workspace/hub-mirror-cache
|
||||
@@ -328,7 +328,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
vs.setUsesGlobal(true);
|
||||
}
|
||||
|
||||
public LaunchOptions getLaunchOptions(String version, JavaVersion javaVersion, File gameDir) {
|
||||
public LaunchOptions getLaunchOptions(String version, JavaVersion javaVersion, File gameDir, boolean makeLaunchScript) {
|
||||
VersionSetting vs = getVersionSetting(version);
|
||||
|
||||
LaunchOptions.Builder builder = new LaunchOptions.Builder()
|
||||
@@ -358,7 +358,8 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
.setNativesDir(vs.getNativesDir())
|
||||
.setProcessPriority(vs.getProcessPriority())
|
||||
.setUseNativeGLFW(vs.isUseNativeGLFW())
|
||||
.setUseNativeOpenAL(vs.isUseNativeOpenAL());
|
||||
.setUseNativeOpenAL(vs.isUseNativeOpenAL())
|
||||
.setDaemon(!makeLaunchScript && vs.getLauncherVisibility().isDaemon());
|
||||
if (config().hasProxy()) {
|
||||
builder.setProxy(ProxyManager.getProxy());
|
||||
if (config().hasProxyAuth()) {
|
||||
|
||||
@@ -172,7 +172,7 @@ public final class LauncherHelper {
|
||||
}
|
||||
}).withStage("launch.state.logging_in"))
|
||||
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
|
||||
LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir());
|
||||
LaunchOptions launchOptions = repository.getLaunchOptions(selectedVersion, javaVersionRef.get(), profile.getGameDir(), scriptFile != null);
|
||||
return new HMCLGameLauncher(
|
||||
repository,
|
||||
version,
|
||||
|
||||
@@ -43,5 +43,9 @@ public enum LauncherVisibility {
|
||||
/**
|
||||
* Hide the launcher and reopen it when game closes.
|
||||
*/
|
||||
HIDE_AND_REOPEN
|
||||
HIDE_AND_REOPEN;
|
||||
|
||||
public boolean isDaemon() {
|
||||
return this != CLOSE;
|
||||
}
|
||||
}
|
||||
@@ -221,7 +221,7 @@ public class GameCrashWindow extends Stage {
|
||||
LogWindow logWindow = new LogWindow();
|
||||
|
||||
logWindow.logLine("Command: " + new CommandBuilder().addAll(managedProcess.getCommands()).toString(), Log4jLevel.INFO);
|
||||
logWindow.logLine("ClassPath: " + managedProcess.getClasspath(), Log4jLevel.INFO);
|
||||
if (managedProcess.getClasspath() != null) logWindow.logLine("ClassPath: " + managedProcess.getClasspath(), Log4jLevel.INFO);
|
||||
for (Map.Entry<String, Log4jLevel> entry : logs)
|
||||
logWindow.logLine(entry.getKey(), entry.getValue());
|
||||
|
||||
@@ -315,13 +315,15 @@ public class GameCrashWindow extends Stage {
|
||||
moddedPane.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
for (LibraryAnalyzer.LibraryType type : LibraryAnalyzer.LibraryType.values()) {
|
||||
analyzer.getVersion(type).ifPresent(ver -> {
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
item.getStyleClass().setAll("two-line-item-second-large");
|
||||
item.setTitle(i18n("install.installer." + type.getPatchId()));
|
||||
item.setSubtitle(ver);
|
||||
moddedPane.getChildren().add(item);
|
||||
});
|
||||
if (!type.getPatchId().isEmpty()) {
|
||||
analyzer.getVersion(type).ifPresent(ver -> {
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
item.getStyleClass().setAll("two-line-item-second-large");
|
||||
item.setTitle(i18n("install.installer." + type.getPatchId()));
|
||||
item.setSubtitle(ver);
|
||||
moddedPane.getChildren().add(item);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -167,9 +167,9 @@ public class AboutPage extends StackPane {
|
||||
jstun.setExternalLink("https://github.com/tking/JSTUN");
|
||||
|
||||
IconedTwoLineListItem minecraftJFXSkin = new IconedTwoLineListItem();
|
||||
jstun.setTitle("minecraft-jfx-skin");
|
||||
jstun.setSubtitle("Copyright (c) 2016 InfinityStudio.\nLicensed under the GPL 3.");
|
||||
jstun.setExternalLink("https://github.com/InfinityStudio/minecraft-jfx-skin");
|
||||
minecraftJFXSkin.setTitle("minecraft-jfx-skin");
|
||||
minecraftJFXSkin.setSubtitle("Copyright (c) 2016 InfinityStudio.\nLicensed under the GPL 3.");
|
||||
minecraftJFXSkin.setExternalLink("https://github.com/InfinityStudio/minecraft-jfx-skin");
|
||||
|
||||
dep.getContent().setAll(javafx, jfoenix, gson, xz, fxgson, constantPoolScanner, openNBT, jstun, minecraftJFXSkin);
|
||||
}
|
||||
|
||||
@@ -25,9 +25,11 @@ import org.jackhuang.hmcl.event.EventManager;
|
||||
import org.jackhuang.hmcl.launch.StreamPump;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
|
||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
@@ -59,56 +61,52 @@ import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
* Cato Management.
|
||||
*/
|
||||
public final class MultiplayerManager {
|
||||
static final String CATO_VERSION = "1.1.1-3f36a56";
|
||||
// private static final String CATO_DOWNLOAD_URL = "https://files.huangyuhui.net/maven/cato/cato/" + MultiplayerManager.CATO_VERSION;
|
||||
private static final String CATO_DOWNLOAD_URL = "https://codechina.csdn.net/huanghongxun1/ioi_bin/-/raw/3f36a567d9381e5fa404b96bcecd6d6a555f235c/client/";
|
||||
static final String CATO_VERSION = "1.2.0-202112171527";
|
||||
private static final String CATO_DOWNLOAD_URL = "https://gitcode.net/huanghongxun1/ioi_bin/-/raw/3.5.3/client/";
|
||||
private static final String CATO_HASH_URL = CATO_DOWNLOAD_URL + "cato-all-files.sha1";
|
||||
private static final String CATO_PATH = getCatoPath();
|
||||
public static final int CATO_AGREEMENT_VERSION = 2;
|
||||
private static final String REMOTE_ADDRESS = "127.0.0.1";
|
||||
private static final String LOCAL_ADDRESS = "0.0.0.0";
|
||||
|
||||
private static final Map<String, String> HASH = mapOf(
|
||||
pair("cato-client-darwin-amd64", "e733fa4b72be24b99026249c31c4ae8b52ad3906"),
|
||||
pair("cato-client-darwin-arm64", "19ab41b7f78263cad81009ae1a391b0d0367d95f"),
|
||||
pair("cato-client-freebsd-amd64", "6de8439ab9267e444d45bb9f02394023ce0bfe5e"),
|
||||
pair("cato-client-freebsd-arm64", "5cd8930e7cd17d72386b085950e63467e4ce72ef"),
|
||||
pair("cato-client-freebsd-arm7", "d0e54a4a1b0f8c2f769d74a6a600eec4a9c4e0da"),
|
||||
pair("cato-client-freebsd-i386", "d9d22faf85e01e77b18f28f1609e87623ac9b1a4"),
|
||||
pair("cato-client-js.wasm", "acb366a4d6a3c6cd464ef5919ce7d2d58a52964e"),
|
||||
pair("cato-client-linux-amd64", "04e94a3adff1e4d865dead2c6c5da584b74bff0a"),
|
||||
pair("cato-client-linux-arm64", "ba662bc461995dae5e78d21b9fea35c2db81b652"),
|
||||
pair("cato-client-linux-arm7", "28021eff56e1e273c051853808e70e7ab3af80d9"),
|
||||
pair("cato-client-linux-i386", "4f524a9cfbcea560bdf07d3468b883f946c431ed"),
|
||||
pair("cato-client-linux-mips", "91c3108b046511c2aafbe88f82b38a06c03d88e1"),
|
||||
pair("cato-client-linux-mips64", "04d1b66494dbdb0443eaae85bda6b61408e300ed"),
|
||||
pair("cato-client-linux-mips64le", "d838ac71052f7873b28f4b4a2f23470b918ef85d"),
|
||||
pair("cato-client-linux-mipsle", "54918ed9cdbe48f7adf11680947281f5fc7c5a7d"),
|
||||
pair("cato-client-linux-ppc64", "f2679259516b0db0422f85afcce35bfa6f7b3192"),
|
||||
pair("cato-client-linux-ppc64le", "463d9b86316dfc83e668ddfae96e51181a9e3ffa"),
|
||||
pair("cato-client-openbsd-amd64", "b776155e955340485faf62c3b2aad3a788c57e2b"),
|
||||
pair("cato-client-openbsd-arm64", "fd6ec21248fd9c8be3d9d2876faf65b95c63b3a9"),
|
||||
pair("cato-client-openbsd-arm7", "19eabe71530bb4eac7a32f6b8aeef72f6f9382e9"),
|
||||
pair("cato-client-openbsd-i386", "3e755798628a379997dbc43dfd71765d62263ebe"),
|
||||
pair("cato-client-windows-amd64.exe", "21a862f455c909c9fb786cd308e969629eb3c8d9"),
|
||||
pair("cato-client-windows-arm64.exe", "7f2fe5d054ebc3265edb046badafdd6ac94bf7eb"),
|
||||
pair("cato-client-windows-i386.exe", "4e03e640c6cabfd7e1ddf60bba1726353bc3afee")
|
||||
);
|
||||
private static CompletableFuture<Map<String, String>> HASH;
|
||||
|
||||
private MultiplayerManager() {
|
||||
}
|
||||
|
||||
private static CompletableFuture<Map<String, String>> getCatoHash() {
|
||||
FXUtils.checkFxUserThread();
|
||||
if (HASH == null) {
|
||||
HASH = CompletableFuture.supplyAsync(wrap(() -> {
|
||||
String hashList = HttpRequest.GET(CATO_HASH_URL).getString();
|
||||
Map<String, String> hashes = new HashMap<>();
|
||||
for (String line : hashList.split("\n")) {
|
||||
String[] items = line.trim().split(" ");
|
||||
if (items.length == 2 && items[0].length() == 40) {
|
||||
hashes.put(items[1], items[0]);
|
||||
} else {
|
||||
LOG.warning("Failed to parse cato hash file, hash line " + line);
|
||||
}
|
||||
}
|
||||
return hashes;
|
||||
}));
|
||||
}
|
||||
return HASH;
|
||||
}
|
||||
|
||||
public static Task<Void> downloadCato() {
|
||||
return new FileDownloadTask(
|
||||
NetworkUtils.toURL(CATO_DOWNLOAD_URL + getCatoFileName()),
|
||||
getCatoExecutable().toFile(),
|
||||
new FileDownloadTask.IntegrityCheck("SHA-1", HASH.get(getCatoFileName()))
|
||||
).thenRunAsync(() -> {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
Set<PosixFilePermission> perm = Files.getPosixFilePermissions(getCatoExecutable());
|
||||
perm.add(PosixFilePermission.OWNER_EXECUTE);
|
||||
Files.setPosixFilePermissions(getCatoExecutable(), perm);
|
||||
}
|
||||
});
|
||||
return Task.fromCompletableFuture(getCatoHash()).thenComposeAsync(catoHashes ->
|
||||
new FileDownloadTask(
|
||||
NetworkUtils.toURL(CATO_DOWNLOAD_URL + getCatoFileName()),
|
||||
getCatoExecutable().toFile(),
|
||||
catoHashes.get(getCatoFileName()) == null ? null : new FileDownloadTask.IntegrityCheck("SHA-1", catoHashes.get(getCatoFileName()))
|
||||
).thenRunAsync(() -> {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
Set<PosixFilePermission> perm = Files.getPosixFilePermissions(getCatoExecutable());
|
||||
perm.add(PosixFilePermission.OWNER_EXECUTE);
|
||||
Files.setPosixFilePermissions(getCatoExecutable(), perm);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public static Path getCatoExecutable() {
|
||||
@@ -116,7 +114,7 @@ public final class MultiplayerManager {
|
||||
}
|
||||
|
||||
private static CompletableFuture<CatoSession> startCato(String token, State state) {
|
||||
return CompletableFuture.completedFuture(null).thenApplyAsync(wrap(unused -> {
|
||||
return getCatoHash().thenApplyAsync(wrap(catoHashes -> {
|
||||
Path exe = getCatoExecutable();
|
||||
if (!Files.isRegularFile(exe)) {
|
||||
throw new CatoNotExistsException(exe);
|
||||
@@ -127,13 +125,16 @@ public final class MultiplayerManager {
|
||||
}
|
||||
|
||||
try {
|
||||
ChecksumMismatchException.verifyChecksum(exe, "SHA-1", HASH.get(getCatoFileName()));
|
||||
String hash = catoHashes.get(getCatoFileName());
|
||||
if (hash != null) {
|
||||
ChecksumMismatchException.verifyChecksum(exe, "SHA-1", hash);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Files.deleteIfExists(exe);
|
||||
throw e;
|
||||
}
|
||||
|
||||
String[] commands = new String[]{exe.toString(), "--token", StringUtils.isBlank(token) ? "new" : token};
|
||||
String[] commands = new String[]{exe.toString(), "-client.token", StringUtils.isBlank(token) ? "new" : token};
|
||||
Process process = new ProcessBuilder()
|
||||
.command(commands)
|
||||
.start();
|
||||
|
||||
@@ -88,6 +88,8 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
private TaskExecutor executor;
|
||||
protected RemoteModRepository repository;
|
||||
|
||||
private Runnable retrySearch;
|
||||
|
||||
public DownloadListPage(RemoteModRepository repository) {
|
||||
this(repository, null);
|
||||
}
|
||||
@@ -151,6 +153,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
}
|
||||
|
||||
public void search(String userGameVersion, RemoteModRepository.Category category, int pageOffset, String searchFilter, int sort) {
|
||||
retrySearch = null;
|
||||
setLoading(true);
|
||||
setFailed(false);
|
||||
File versionJar = StringUtils.isNotBlank(version.get().getVersion())
|
||||
@@ -177,6 +180,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
failed.set(false);
|
||||
} else {
|
||||
failed.set(true);
|
||||
retrySearch = () -> search(userGameVersion, category, pageOffset, searchFilter, sort);
|
||||
}
|
||||
}).executor(true);
|
||||
}
|
||||
@@ -360,6 +364,11 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
return null;
|
||||
}
|
||||
}, getSkinnable().failedProperty()));
|
||||
spinnerPane.setOnFailedAction(e -> {
|
||||
if (getSkinnable().retrySearch != null) {
|
||||
getSkinnable().retrySearch.run();
|
||||
}
|
||||
});
|
||||
|
||||
JFXListView<RemoteMod> listView = new JFXListView<>();
|
||||
spinnerPane.setContent(listView);
|
||||
|
||||
@@ -84,7 +84,12 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
this.mod = ModTranslations.getModByCurseForgeId(addon.getSlug());
|
||||
this.version = version;
|
||||
this.callback = callback;
|
||||
loadModVersions();
|
||||
|
||||
this.state.set(State.fromTitle(addon.getTitle()));
|
||||
}
|
||||
|
||||
private void loadModVersions() {
|
||||
File versionJar = StringUtils.isNotBlank(version.getVersion())
|
||||
? version.getProfile().getRepository().getVersionJar(version.getVersion())
|
||||
: null;
|
||||
@@ -93,9 +98,9 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
setFailed(false);
|
||||
|
||||
Task.allOf(
|
||||
Task.supplyAsync(() -> addon.getData().loadDependencies()),
|
||||
Task.supplyAsync(() -> {
|
||||
Stream<RemoteMod.Version> versions = addon.getData().loadVersions();
|
||||
Task.supplyAsync(() -> addon.getData().loadDependencies()),
|
||||
Task.supplyAsync(() -> {
|
||||
Stream<RemoteMod.Version> versions = addon.getData().loadVersions();
|
||||
// if (StringUtils.isNotBlank(version.getVersion())) {
|
||||
// Optional<String> gameVersion = GameVersion.minecraftVersion(versionJar);
|
||||
// if (gameVersion.isPresent()) {
|
||||
@@ -103,8 +108,8 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
// .filter(file -> file.getGameVersions().contains(gameVersion.get())));
|
||||
// }
|
||||
// }
|
||||
return sortVersions(versions);
|
||||
}))
|
||||
return sortVersions(versions);
|
||||
}))
|
||||
.whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||
if (exception == null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -122,8 +127,6 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
}
|
||||
setLoading(false);
|
||||
}).start();
|
||||
|
||||
this.state.set(State.fromTitle(addon.getTitle()));
|
||||
}
|
||||
|
||||
private SimpleMultimap<String, RemoteMod.Version> sortVersions(Stream<RemoteMod.Version> versions) {
|
||||
@@ -195,7 +198,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
}
|
||||
|
||||
Controllers.taskDialog(
|
||||
new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest).executor(true),
|
||||
new FileDownloadTask(NetworkUtils.toURL(file.getFile().getUrl()), dest, file.getFile().getIntegrityCheck()).executor(true),
|
||||
i18n("message.downloading")
|
||||
);
|
||||
}
|
||||
@@ -297,6 +300,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
return null;
|
||||
}
|
||||
}, getSkinnable().failedProperty()));
|
||||
spinnerPane.setOnFailedAction(e -> getSkinnable().loadModVersions());
|
||||
|
||||
ComponentList list = new ComponentList();
|
||||
StackPane.setAlignment(list, Pos.TOP_CENTER);
|
||||
|
||||
@@ -123,7 +123,12 @@ public final class Versions {
|
||||
}
|
||||
if (profile.getRepository().renameVersion(version, newName)) {
|
||||
resolve.run();
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
profile.getRepository().refreshVersionsAsync()
|
||||
.thenRunAsync(Schedulers.javafx(), () -> {
|
||||
if (profile.getRepository().hasVersion(newName)) {
|
||||
profile.setSelectedVersion(newName);
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
reject.accept(i18n("version.manage.rename.fail"));
|
||||
}
|
||||
|
||||
@@ -949,7 +949,7 @@ update.found=Update Available!
|
||||
update.newest_version=Latest version: %s
|
||||
update.bubble.title=Update Available: %s
|
||||
update.bubble.subtitle=Click here to update
|
||||
update.note=Warning: Beta versions may have more functionality and bug fixes but also more possible errors.
|
||||
update.note=Warning: Beta versions and nightly versions may have more functionality and bug fixes but also more possible errors.
|
||||
update.latest=This is the latest version.
|
||||
update.no_browser=Cannot open a browser. The link has been copied to the clipboard. Paste it into the address bar of browser to update.
|
||||
update.tooltip=Update
|
||||
|
||||
@@ -148,7 +148,7 @@ button.install=インストール
|
||||
button.export=エクスポート
|
||||
button.no=いいえ
|
||||
button.ok=OK
|
||||
button.refresh=リフレッシュ
|
||||
button.refresh=更新
|
||||
button.remove=削除
|
||||
button.remove.confirm=削除してもよろしいですか?この操作をロールバックすることはできません!
|
||||
button.save=保存
|
||||
@@ -182,6 +182,11 @@ curse.category.4472=Tech
|
||||
curse.category.4473=魔法
|
||||
|
||||
# https://addons-ecs.forgesvc.net/api/v2/category/section/6
|
||||
curse.category.5129=Vanilla+
|
||||
curse.category.5189=Utility & QOL
|
||||
curse.category.5190=QoL
|
||||
curse.category.5191=Utility & QoL
|
||||
curse.category.5192=FancyMenu
|
||||
curse.category.423=地図と情報
|
||||
curse.category.426=Addons
|
||||
curse.category.434=武器、道具、武器
|
||||
@@ -289,6 +294,12 @@ download.javafx.notes=ネットワークを介したHMCLに必要なコンポー
|
||||
download.javafx.component=モジュール%sをダウンロードしています
|
||||
download.javafx.prepare=ダウンロードする準備ができました
|
||||
|
||||
exception.access_denied=It's denied by operating system to access file %s. Maybe we don't have permission to access this file, or this file has already been opened by other program.\n\
|
||||
Please check if current operating system user has permission to access that file.\n\
|
||||
For Windows users, you can also try the Resource Monitor, find if some programs is holding the file, try to close related program, or restart your computer.
|
||||
exception.artifact_malformed=The file cannot pass verification.
|
||||
exception.ssl_handshake=Cannot establish SSL connection because of missing SSL certifcate in current Java installation. Try to start HMCL on another Java VM.
|
||||
|
||||
extension.bat=WindowsBatファイル
|
||||
extension.mod=Modファイル
|
||||
extension.png=画像ファイル
|
||||
@@ -300,7 +311,8 @@ fatal.javafx.missing=JavaFXがありません。\nJava11以降を使用してい
|
||||
fatal.missing_dst_root_ca_x3=現在のJavaプラットフォームにDSTルートCAX3証明書がありません。\n引き続きHelloMinecraftを使用できます。ランチャーですが、一部のサイト(Let's Encryptによって発行された証明書を使用するサイトなど)に接続できず、ランチャーが正しく機能しない可能性があります。\n問題を解決するには、Javaランタイムを8u101以降に更新してください。
|
||||
fatal.config_loading_failure=構成にアクセスできません。\nHelloMinecraftを確認してください。 Launcherには、「%s」とその中のファイルへの読み取りおよび書き込みアクセス権があります。
|
||||
fatal.migration_requires_manual_reboot=更新が完了しました。 Hello Minecraftを再開してください!ランチャー。
|
||||
fatal.apply_update_failure=ごめんなさい、こんにちはMinecraft!何か問題が発生したため、ランチャーは更新を完了できませんでした。\nただし、Hello Minecraftをダウンロードすることで、手動で更新を終了できます。 %sからのランチャー。\nこの問題を報告することを検討してください。
|
||||
fatal.apply_update_failure=ごめんなさい、Hello Minecraft! Launcher 何か問題が発生したため、ランチャーは更新を完了できませんでした。\nただし、Hello Minecraftをダウンロードすることで、手動で更新を終了できます。 %sからのランチャー。\nこの問題を報告することを検討してください。
|
||||
fatal.samba=If you are trying to run HMCL in a shared folder by Samba, HMCL may not working, please try updating your Java or running HMCL in a local folder.
|
||||
|
||||
feedback=フィードバック
|
||||
Feedback.add=フィードバックを追加
|
||||
@@ -392,12 +404,11 @@ input.number=数値である必要があります。
|
||||
input.not_empty=必須フィールド
|
||||
input.url=有効なURLである必要があります。
|
||||
|
||||
install=新しい構成を作成
|
||||
install=新規作成
|
||||
install.change_version=バージョンの変更
|
||||
install.change_version.confirm=%sをバージョン%sから%sに更新してもよろしいですか?
|
||||
install.failed=バージョンのインストールに失敗しました
|
||||
install.failed.downloading=一部のファイルが正常にダウンロードされなかったため、インストールに失敗しました
|
||||
install.failed.downloading.access_denied=ファイルのダウンロードに失敗しました:%1$s。これは、オペレーティングシステムによってファイル%2 $ sへのアクセスが拒否されているためです。このファイルにアクセスする権限がないか、このファイルが他のプログラムによって既に開かれている可能性があります。関連するプログラムを閉じるか、コンピュータを再起動してみてください。
|
||||
install.failed.downloading.detail=ファイルのダウンロードに失敗しました:%s
|
||||
install.failed.downloading.timeout=ファイルのダウンロード中にタイムアウトしました:%s
|
||||
install.failed.install_online=提供されたインストーラーファイルを認識できません。 modをインストールする場合は、「Mods」ページに移動します。
|
||||
@@ -425,7 +436,7 @@ install.installer.not_installed=インストールされていません
|
||||
install.installer.optifine=OptiFine
|
||||
install.installer.version=%s
|
||||
install.modpack=modpackを導入
|
||||
install.new_game=新しいゲームをインストールする
|
||||
install.new_game=新規作成
|
||||
install.new_game.already_exists=このバージョンはすでに存在します。
|
||||
install.new_game.current_game_version=現在のゲームバージョン
|
||||
install.new_game.malformed=無効な名前
|
||||
@@ -482,6 +493,7 @@ launcher.background.classic=クラシック
|
||||
launcher.background.choose=背景画像ファイルを選択してください
|
||||
launcher.background.default=標準
|
||||
launcher.background.network=ネットワーク
|
||||
launcher.background.translucent=半透明
|
||||
launcher.cache_directory=キャッシュ用のディレクトリ
|
||||
launcher.cache_directory.clean=Clear
|
||||
launcher.cache_directory.choose=キャッシュするディレクトリを選択します
|
||||
@@ -641,8 +653,10 @@ multiplayer.download.unsupported=現在のオペレーティングシステム
|
||||
multiplayer.exit.after_ready=マルチプレイヤーセッションが壊れています。 cato exitcode%d
|
||||
multiplayer.exit.before_ready=マルチプレイヤーセッションの作成に失敗しました。 cato exitcode%d
|
||||
multiplayer.exit.timeout=マルチプレイヤーサーバーへの接続に失敗しました。
|
||||
multiplayer.exit.timeout.dynamic_token=マルチプレイヤーサーバーへの接続に失敗しました。
|
||||
multiplayer.hint=マルチプレイヤー機能は実験的なものです。フィードバックをお願いします。
|
||||
multiplayer.nat=ネットワークタイプの検出
|
||||
multiplayer.nat.failed=ネットワークタイプの検出に失敗しました。
|
||||
multiplayer.nat.hint=ネットワークタイプの検出により、ネットワークがマルチプレイヤーモードの要件を満たしているかどうかが明確になります。
|
||||
multiplayer.nat.latency=レイテンシー
|
||||
multiplayer.nat.not_yet_tested=まだテストされていません
|
||||
@@ -750,7 +764,7 @@ profile.instance_directory=ゲームディレクトリ
|
||||
profile.instance_directory.choose=ゲームディレクトリを選択してください
|
||||
profile.manage=ゲームディレクトリリスト
|
||||
profile.name=名前
|
||||
profile.new=新しい構成
|
||||
profile.new=新規作成
|
||||
profile.title=ゲームディレクトリ
|
||||
profile.selected=選択済み
|
||||
profile.use_relative_path=可能であれば、ゲームディレクトリの相対パスを使用します
|
||||
@@ -799,7 +813,7 @@ settings.advanced.launcher_visibility.close=ゲームの起動時にランチャ
|
||||
settings.advanced.launcher_visibility.hide=ゲームの起動時にランチャーを非表示にします。
|
||||
settings.advanced.launcher_visibility.hide_and_reopen=ランチャーを非表示にし、ゲームが終了したら再度開きます。
|
||||
settings.advanced.launcher_visibility.keep=ランチャーを表示したままにします。
|
||||
settings.advanced.launcher_visible=ランチャーの可視性
|
||||
settings.advanced.launcher_visible=ランチャーの設定
|
||||
settings.advanced.minecraft_arguments=Minecraftの引数
|
||||
settings.advanced.minecraft_arguments.prompt=デフォルト
|
||||
settings.advanced.natives_directory=ネイティブライブラリパス(例: LWJGL)
|
||||
@@ -883,7 +897,7 @@ settings.memory.allocate.auto=最小%1 $ .1f GB /実際の%2 $ .1f GB
|
||||
settings.memory.allocate.auto.exceeded=最小%1 $ .1f GB /実際の%2 $ .1f GB(%3 $ .1f GB使用可能)
|
||||
settings.memory.allocate.manual=割り当て%1 $ .1f GB
|
||||
settings.memory.allocate.manual.exceeded=割り当て%1 $ .1f GB(%3 $ .1f GB使用可能)
|
||||
settings.memory.auto_allocate=メモリサイズは自動的に決定されます
|
||||
settings.memory.auto_allocate=メモリサイズの自動割り当て
|
||||
settings.memory.lower_bound=最小メモリ
|
||||
settings.memory.used_per_total=%1 $ .1f GB使用済み/%2 $ .1fGB合計
|
||||
settings.physical_memory=物理メモリサイズ
|
||||
@@ -969,9 +983,9 @@ version.manage.rename=このバージョンの名前を変更します
|
||||
version.manage.rename.message=このバージョンの新しい名前を入力してください
|
||||
version.manage.rename.fail=バージョンの名前を変更できませんでした。
|
||||
version.settings=設定
|
||||
version.update=modpackを更新します
|
||||
version.update=modpackを更新する
|
||||
|
||||
wizard.prev=<前へ
|
||||
wizard.prev=< 前へ
|
||||
wizard.failed=失敗
|
||||
wizard.finish=終了
|
||||
wizard.next=次へ>
|
||||
wizard.next=次へ >
|
||||
@@ -949,7 +949,7 @@ update.found=發現到更新
|
||||
update.newest_version=最新版本為: %s
|
||||
update.bubble.title=發現更新: %s
|
||||
update.bubble.subtitle=點選此處進行升級
|
||||
update.note=開發版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。
|
||||
update.note=測試版與開發版包含更多的功能以及錯誤修復,但也可能會包含其他的問題。
|
||||
update.latest=目前版本為最新版本
|
||||
update.no_browser=無法開啟瀏覽器,網址已經複製到剪貼簿了,您可以手動複製網址開啟頁面
|
||||
update.tooltip=更新
|
||||
|
||||
@@ -949,7 +949,7 @@ update.found=发现更新
|
||||
update.newest_version=最新版本为:%s
|
||||
update.bubble.title=发现更新:%s
|
||||
update.bubble.subtitle=点击此处进行升级
|
||||
update.note=开发版包含更多的功能以及错误修复,但也可能会包含其他的问题。
|
||||
update.note=测试版与开发版包含更多的功能以及错误修复,但也可能会包含其他的问题。
|
||||
update.latest=当前版本为最新版本
|
||||
update.no_browser=无法打开浏览器,网址已经复制到剪贴板了,您可以手动粘贴网址打开页面
|
||||
update.tooltip=更新
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
package org.jackhuang.hmcl.auth;
|
||||
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.game.LaunchOptions;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -27,26 +29,18 @@ import java.util.UUID;
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@Immutable
|
||||
public final class AuthInfo implements AutoCloseable {
|
||||
public class AuthInfo implements AutoCloseable {
|
||||
|
||||
private final String username;
|
||||
private final UUID uuid;
|
||||
private final String accessToken;
|
||||
private final String userProperties;
|
||||
private final Arguments arguments;
|
||||
private final AutoCloseable closeable;
|
||||
|
||||
public AuthInfo(String username, UUID uuid, String accessToken, String userProperties) {
|
||||
this(username, uuid, accessToken, userProperties, null, null);
|
||||
}
|
||||
|
||||
public AuthInfo(String username, UUID uuid, String accessToken, String userProperties, Arguments arguments, AutoCloseable closeable) {
|
||||
this.username = username;
|
||||
this.uuid = uuid;
|
||||
this.accessToken = accessToken;
|
||||
this.userProperties = userProperties;
|
||||
this.arguments = arguments;
|
||||
this.closeable = closeable;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
@@ -72,24 +66,14 @@ public final class AuthInfo implements AutoCloseable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when launching game.
|
||||
* @return null if no argument is specified
|
||||
*/
|
||||
public Arguments getArguments() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
public AuthInfo withArguments(Arguments arguments) {
|
||||
return new AuthInfo(username, uuid, accessToken, userProperties, arguments, closeable);
|
||||
}
|
||||
|
||||
public AuthInfo withCloseable(AutoCloseable closeable) {
|
||||
return new AuthInfo(username, uuid, accessToken, userProperties, arguments, closeable);
|
||||
public Arguments getLaunchArguments(LaunchOptions options) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (closeable != null) {
|
||||
closeable.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
-7
@@ -26,6 +26,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.game.LaunchOptions;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
|
||||
import java.io.IOException;
|
||||
@@ -71,7 +72,7 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||
Optional<String> prefetchedMeta = server.getMetadataResponse();
|
||||
|
||||
if (auth.isPresent() && artifact.isPresent() && prefetchedMeta.isPresent()) {
|
||||
return Optional.of(auth.get().withArguments(generateArguments(artifact.get(), server, prefetchedMeta.get())));
|
||||
return Optional.of(new AuthlibInjectorAuthInfo(auth.get(), artifact.get(), server, prefetchedMeta.get()));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
@@ -112,14 +113,30 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||
}
|
||||
}
|
||||
|
||||
return auth.withArguments(generateArguments(artifact, server, prefetchedMeta));
|
||||
return new AuthlibInjectorAuthInfo(auth, artifact, server, prefetchedMeta);
|
||||
}
|
||||
|
||||
private static Arguments generateArguments(AuthlibInjectorArtifactInfo artifact, AuthlibInjectorServer server, String prefetchedMeta) {
|
||||
return new Arguments().addJVMArguments(
|
||||
"-javaagent:" + artifact.getLocation().toString() + "=" + server.getUrl(),
|
||||
"-Dauthlibinjector.side=client",
|
||||
"-Dauthlibinjector.yggdrasil.prefetched=" + Base64.getEncoder().encodeToString(prefetchedMeta.getBytes(UTF_8)));
|
||||
private static class AuthlibInjectorAuthInfo extends AuthInfo {
|
||||
|
||||
private final AuthlibInjectorArtifactInfo artifact;
|
||||
private final AuthlibInjectorServer server;
|
||||
private final String prefetchedMeta;
|
||||
|
||||
public AuthlibInjectorAuthInfo(AuthInfo authInfo, AuthlibInjectorArtifactInfo artifact, AuthlibInjectorServer server, String prefetchedMeta) {
|
||||
super(authInfo.getUsername(), authInfo.getUUID(), authInfo.getAccessToken(), authInfo.getUserProperties());
|
||||
|
||||
this.artifact = artifact;
|
||||
this.server = server;
|
||||
this.prefetchedMeta = prefetchedMeta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Arguments getLaunchArguments(LaunchOptions options) {
|
||||
return new Arguments().addJVMArguments(
|
||||
"-javaagent:" + artifact.getLocation().toString() + "=" + server.getUrl(),
|
||||
"-Dauthlibinjector.side=client",
|
||||
"-Dauthlibinjector.yggdrasil.prefetched=" + Base64.getEncoder().encodeToString(prefetchedMeta.getBytes(UTF_8)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.Texture;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureModel;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.game.LaunchOptions;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
@@ -129,15 +130,7 @@ public class OfflineAccount extends Account {
|
||||
}
|
||||
|
||||
try {
|
||||
YggdrasilServer server = new YggdrasilServer(0);
|
||||
server.start();
|
||||
server.addCharacter(new YggdrasilServer.Character(uuid, username, skin.load(username).run()));
|
||||
|
||||
return authInfo.withArguments(new Arguments().addJVMArguments(
|
||||
"-javaagent:" + artifact.getLocation().toString() + "=" + "http://localhost:" + server.getListeningPort(),
|
||||
"-Dauthlibinjector.side=client"
|
||||
))
|
||||
.withCloseable(server::stop);
|
||||
return new OfflineAuthInfo(authInfo, artifact);
|
||||
} catch (Exception e) {
|
||||
throw new AuthenticationException(e);
|
||||
}
|
||||
@@ -146,6 +139,46 @@ public class OfflineAccount extends Account {
|
||||
}
|
||||
}
|
||||
|
||||
private class OfflineAuthInfo extends AuthInfo {
|
||||
private final AuthlibInjectorArtifactInfo artifact;
|
||||
private YggdrasilServer server;
|
||||
|
||||
public OfflineAuthInfo(AuthInfo authInfo, AuthlibInjectorArtifactInfo artifact) {
|
||||
super(authInfo.getUsername(), authInfo.getUUID(), authInfo.getAccessToken(), authInfo.getUserProperties());
|
||||
|
||||
this.artifact = artifact;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Arguments getLaunchArguments(LaunchOptions options) throws IOException {
|
||||
if (!options.isDaemon()) return null;
|
||||
|
||||
server = new YggdrasilServer(0);
|
||||
server.start();
|
||||
|
||||
try {
|
||||
server.addCharacter(new YggdrasilServer.Character(uuid, username, skin.load(username).run()));
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
return new Arguments().addJVMArguments(
|
||||
"-javaagent:" + artifact.getLocation().toString() + "=" + "http://localhost:" + server.getListeningPort(),
|
||||
"-Dauthlibinjector.side=client"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
super.close();
|
||||
|
||||
if (server != null)
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AuthInfo> playOffline() throws AuthenticationException {
|
||||
return Optional.of(logIn());
|
||||
|
||||
@@ -76,43 +76,17 @@ public class MaintainTask extends Task<Version> {
|
||||
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 (libraries.size() > 0) {
|
||||
Library library = libraries.get(0);
|
||||
if ("org.glavo".equals(library.getGroupId())
|
||||
&& ("log4j-patch".equals(library.getArtifactId()) || "log4j-patch-beta9".equals(library.getArtifactId()))
|
||||
&& "1.0".equals(library.getVersion())
|
||||
&& library.getDownload() == null) {
|
||||
version = version.setLibraries(libraries.subList(1, libraries.size()));
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
+3
-22
@@ -20,13 +20,11 @@ package org.jackhuang.hmcl.download.fabric;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -37,7 +35,6 @@ import java.util.List;
|
||||
*/
|
||||
public final class FabricAPIInstallTask extends Task<Version> {
|
||||
|
||||
private GetTask dependent;
|
||||
private final DefaultDependencyManager dependencyManager;
|
||||
private final Version version;
|
||||
private final FabricAPIRemoteVersion remote;
|
||||
@@ -47,22 +44,6 @@ public final class FabricAPIInstallTask extends Task<Version> {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.version = version;
|
||||
this.remote = remoteVersion;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doPreExecute() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preExecute() throws Exception {
|
||||
this.dependent = new GetTask(new URL(remote.getUrls().get(0) + ".sha1"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task<?>> getDependents() {
|
||||
return Collections.singleton(dependent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,9 +59,9 @@ public final class FabricAPIInstallTask extends Task<Version> {
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
dependencies.add(new FileDownloadTask(
|
||||
new URL(remote.getUrls().get(0)),
|
||||
dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("fabric-api-" + remote.getFullVersion() + ".jar").toFile(),
|
||||
new FileDownloadTask.IntegrityCheck("SHA-1", dependent.getResult().trim()))
|
||||
new URL(remote.getVersion().getFile().getUrl()),
|
||||
dependencyManager.getGameRepository().getRunDirectory(version.getId()).toPath().resolve("mods").resolve("fabric-api-" + remote.getVersion().getVersion() + ".jar").toFile(),
|
||||
remote.getVersion().getFile().getIntegrityCheck())
|
||||
);
|
||||
}
|
||||
}
|
||||
+15
-2
@@ -19,12 +19,15 @@ package org.jackhuang.hmcl.download.fabric;
|
||||
|
||||
import org.jackhuang.hmcl.download.*;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
public class FabricAPIRemoteVersion extends RemoteVersion {
|
||||
private final String fullVersion;
|
||||
private final RemoteMod.Version version;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -33,10 +36,11 @@ public class FabricAPIRemoteVersion extends RemoteVersion {
|
||||
* @param selfVersion the version string of the remote version.
|
||||
* @param urls the installer or universal jar original URL.
|
||||
*/
|
||||
FabricAPIRemoteVersion(String gameVersion, String selfVersion, String fullVersion, List<String> urls) {
|
||||
super(LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId(), gameVersion, selfVersion, null, urls);
|
||||
FabricAPIRemoteVersion(String gameVersion, String selfVersion, String fullVersion, Instant datePublished, RemoteMod.Version version, List<String> urls) {
|
||||
super(LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId(), gameVersion, selfVersion, datePublished, urls);
|
||||
|
||||
this.fullVersion = fullVersion;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,9 +48,18 @@ public class FabricAPIRemoteVersion extends RemoteVersion {
|
||||
return fullVersion;
|
||||
}
|
||||
|
||||
public RemoteMod.Version getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task<Version> getInstallTask(DefaultDependencyManager dependencyManager, Version baseVersion) {
|
||||
return new FabricAPIInstallTask(dependencyManager, baseVersion, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(RemoteVersion o) {
|
||||
if (!(o instanceof FabricAPIRemoteVersion)) return 0;
|
||||
return -this.getReleaseDate().compareTo(o.getReleaseDate());
|
||||
}
|
||||
}
|
||||
+7
-23
@@ -19,12 +19,10 @@ package org.jackhuang.hmcl.download.fabric;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -49,24 +47,10 @@ public class FabricAPIVersionList extends VersionList<FabricAPIRemoteVersion> {
|
||||
@Override
|
||||
public CompletableFuture<?> refreshAsync() {
|
||||
return CompletableFuture.runAsync(wrap(() -> {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
Document doc = builder.parse("https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api/maven-metadata.xml");
|
||||
Element r = doc.getDocumentElement();
|
||||
NodeList versionElements = r.getElementsByTagName("version");
|
||||
for (int i = 0; i < versionElements.getLength(); i++) {
|
||||
String versionName = versionElements.item(i).getTextContent();
|
||||
|
||||
Matcher matcher = FABRIC_VERSION_PATTERN.matcher(versionName);
|
||||
if (matcher.find()) {
|
||||
String fabricVersion = matcher.group("version");
|
||||
if (matcher.group("build") != null) {
|
||||
fabricVersion += "." + matcher.group("build");
|
||||
}
|
||||
String gameVersion = matcher.group("mcversion");
|
||||
versions.put(gameVersion, new FabricAPIRemoteVersion(gameVersion, fabricVersion, versionName,
|
||||
Collections.singletonList(String.format(
|
||||
"https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api/%1$s/fabric-api-%1$s.jar", versionName))));
|
||||
for (RemoteMod.Version modVersion : Lang.toIterable(ModrinthRemoteModRepository.INSTANCE.getRemoteVersionsById("P7dR8mSH"))) {
|
||||
for (String gameVersion : modVersion.getGameVersions()) {
|
||||
versions.put(gameVersion, new FabricAPIRemoteVersion(gameVersion, modVersion.getVersion(), modVersion.getName(), modVersion.getDatePublished(), modVersion,
|
||||
Collections.singletonList(modVersion.getFile().getUrl())));
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -59,6 +59,7 @@ public class LaunchOptions implements Serializable {
|
||||
private ProcessPriority processPriority = ProcessPriority.NORMAL;
|
||||
private boolean useNativeGLFW;
|
||||
private boolean useNativeOpenAL;
|
||||
private boolean daemon;
|
||||
|
||||
/**
|
||||
* The game directory
|
||||
@@ -243,6 +244,13 @@ public class LaunchOptions implements Serializable {
|
||||
return useNativeOpenAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will launcher keeps alive after game launched or not.
|
||||
*/
|
||||
public boolean isDaemon() {
|
||||
return daemon;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final LaunchOptions options = new LaunchOptions();
|
||||
@@ -411,6 +419,10 @@ public class LaunchOptions implements Serializable {
|
||||
return options.useNativeOpenAL;
|
||||
}
|
||||
|
||||
public boolean isDaemon() {
|
||||
return options.daemon;
|
||||
}
|
||||
|
||||
public Builder setGameDir(File gameDir) {
|
||||
options.gameDir = gameDir;
|
||||
return this;
|
||||
@@ -543,5 +555,10 @@ public class LaunchOptions implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDaemon(boolean daemon) {
|
||||
options.daemon = daemon;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,7 @@ package org.jackhuang.hmcl.launch;
|
||||
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.Argument;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.game.LaunchOptions;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.NativesDirectoryType;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
@@ -48,6 +42,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
@@ -79,14 +74,14 @@ public class DefaultLauncher extends Launcher {
|
||||
switch (options.getProcessPriority()) {
|
||||
case HIGH:
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
res.add("cmd", "/C", "start", "unused title", "/B", "/high");
|
||||
// res.add("cmd", "/C", "start", "unused title", "/B", "/high");
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
res.add("nice", "-n", "-5");
|
||||
}
|
||||
break;
|
||||
case ABOVE_NORMAL:
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
res.add("cmd", "/C", "start", "unused title", "/B", "/abovenormal");
|
||||
// res.add("cmd", "/C", "start", "unused title", "/B", "/abovenormal");
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
res.add("nice", "-n", "-1");
|
||||
}
|
||||
@@ -96,14 +91,14 @@ public class DefaultLauncher extends Launcher {
|
||||
break;
|
||||
case BELOW_NORMAL:
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
res.add("cmd", "/C", "start", "unused title", "/B", "/belownormal");
|
||||
// res.add("cmd", "/C", "start", "unused title", "/B", "/belownormal");
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
res.add("nice", "-n", "1");
|
||||
}
|
||||
break;
|
||||
case LOW:
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
res.add("cmd", "/C", "start", "unused title", "/B", "/low");
|
||||
// res.add("cmd", "/C", "start", "unused title", "/B", "/low");
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
res.add("nice", "-n", "5");
|
||||
}
|
||||
@@ -131,6 +126,8 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
}
|
||||
|
||||
Optional<Library> log4j = findLog4j();
|
||||
|
||||
// JVM Args
|
||||
if (!options.isNoGeneratedJVMArgs()) {
|
||||
appendJvmArgs(res);
|
||||
@@ -195,6 +192,13 @@ public class DefaultLauncher extends Launcher {
|
||||
|
||||
res.addDefault("-Dfml.ignoreInvalidMinecraftCertificates=", "true");
|
||||
res.addDefault("-Dfml.ignorePatchDiscrepancies=", "true");
|
||||
|
||||
if (log4j.isPresent()) {
|
||||
String enableJndi = res.addDefault("-Dlog4j2.enableJndi=", "false");
|
||||
if (!"-Dlog4j2.enableJndi=true".equals(enableJndi)) {
|
||||
res.add("-javaagent:" + repository.getLibraryFile(version, LOG4J_PATCH_AGENT).getAbsolutePath() + "=" + log4j.get().getVersion().startsWith("2.0-beta"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fix RCE vulnerability of log4j2
|
||||
@@ -203,7 +207,7 @@ public class DefaultLauncher extends Launcher {
|
||||
res.addDefault("-Dcom.sun.jndi.cosnaming.object.trustURLCodebase=", "false");
|
||||
|
||||
String formatMsgNoLookups = res.addDefault("-Dlog4j2.formatMsgNoLookups=", "true");
|
||||
if (!"-Dlog4j2.formatMsgNoLookups=false".equals(formatMsgNoLookups) && isUsingLog4j()) {
|
||||
if (!"-Dlog4j2.formatMsgNoLookups=false".equals(formatMsgNoLookups) && log4j.isPresent()) {
|
||||
res.addDefault("-Dlog4j.configurationFile=", getLog4jConfigurationFile().getAbsolutePath());
|
||||
}
|
||||
|
||||
@@ -251,8 +255,9 @@ public class DefaultLauncher extends Launcher {
|
||||
configuration.put("${natives_directory}", nativeFolderPath);
|
||||
|
||||
res.addAll(Arguments.parseArguments(version.getArguments().map(Arguments::getJvm).orElseGet(this::getDefaultJVMArguments), configuration));
|
||||
if (authInfo.getArguments() != null && authInfo.getArguments().getJvm() != null && !authInfo.getArguments().getJvm().isEmpty())
|
||||
res.addAll(Arguments.parseArguments(authInfo.getArguments().getJvm(), configuration));
|
||||
Arguments argumentsFromAuthInfo = authInfo.getLaunchArguments(options);
|
||||
if (argumentsFromAuthInfo != null && argumentsFromAuthInfo.getJvm() != null && !argumentsFromAuthInfo.getJvm().isEmpty())
|
||||
res.addAll(Arguments.parseArguments(argumentsFromAuthInfo.getJvm(), configuration));
|
||||
|
||||
res.add(version.getMainClass());
|
||||
|
||||
@@ -263,8 +268,8 @@ public class DefaultLauncher extends Launcher {
|
||||
if (version.getMinecraftArguments().isPresent()) {
|
||||
res.addAll(Arguments.parseArguments(this.getDefaultGameArguments(), configuration, features));
|
||||
}
|
||||
if (authInfo.getArguments() != null && authInfo.getArguments().getGame() != null && !authInfo.getArguments().getGame().isEmpty())
|
||||
res.addAll(Arguments.parseArguments(authInfo.getArguments().getGame(), configuration, features));
|
||||
if (argumentsFromAuthInfo != null && argumentsFromAuthInfo.getGame() != null && !argumentsFromAuthInfo.getGame().isEmpty())
|
||||
res.addAll(Arguments.parseArguments(argumentsFromAuthInfo.getGame(), configuration, features));
|
||||
|
||||
if (StringUtils.isNotBlank(options.getServerIp())) {
|
||||
String[] args = options.getServerIp().split(":");
|
||||
@@ -360,8 +365,17 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUsingLog4j() {
|
||||
return VersionNumber.VERSION_COMPARATOR.compare(repository.getGameVersion(version).orElse("Unknown"), "1.7") >= 0;
|
||||
private static final Library LOG4J_PATCH_AGENT = new Library(new Artifact("org.glavo", "log4j-patch-agent", "1.0"));
|
||||
|
||||
private Optional<Library> findLog4j() {
|
||||
return version.getLibraries().stream()
|
||||
.filter(it -> it.is("org.apache.logging.log4j", "log4j-core")
|
||||
&& (VersionNumber.VERSION_COMPARATOR.compare(it.getVersion(), "2.17") < 0 || "2.0-beta9".equals(it.getVersion())))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private boolean isLog4jUnsafe(Library log4j) {
|
||||
return VersionNumber.VERSION_COMPARATOR.compare(log4j.getVersion(), "2.17") < 0;
|
||||
}
|
||||
|
||||
public File getLog4jConfigurationFile() {
|
||||
@@ -371,18 +385,28 @@ public class DefaultLauncher extends Launcher {
|
||||
public void extractLog4jConfigurationFile() throws IOException {
|
||||
File targetFile = getLog4jConfigurationFile();
|
||||
InputStream source;
|
||||
if (VersionNumber.VERSION_COMPARATOR.compare(repository.getGameVersion(version).orElse("Unknown"), "1.12") < 0) {
|
||||
if (VersionNumber.VERSION_COMPARATOR.compare(repository.getGameVersion(version).orElse("0.0"), "1.12") < 0) {
|
||||
source = DefaultLauncher.class.getResourceAsStream("/assets/game/log4j2-1.7.xml");
|
||||
} else {
|
||||
source = DefaultLauncher.class.getResourceAsStream("/assets/game/log4j2-1.12.xml");
|
||||
}
|
||||
|
||||
try (InputStream input = source;
|
||||
OutputStream output = new FileOutputStream(targetFile)) {
|
||||
try (InputStream input = source; OutputStream output = new FileOutputStream(targetFile)) {
|
||||
IOUtils.copyTo(input, output);
|
||||
}
|
||||
}
|
||||
|
||||
public void extractLog4jAgent() throws IOException {
|
||||
Path log4jPatchPath = repository.getLibraryFile(version, LOG4J_PATCH_AGENT).toPath();
|
||||
String patchName = LOG4J_PATCH_AGENT.getArtifactId() + "-" + LOG4J_PATCH_AGENT.getVersion();
|
||||
if (Files.notExists(log4jPatchPath)) {
|
||||
try (InputStream input = DefaultLauncher.class.getResourceAsStream("/assets/game/" + patchName + ".jar")) {
|
||||
Files.createDirectories(log4jPatchPath.getParent());
|
||||
Files.copy(input, log4jPatchPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Map<String, String> getConfigurations() {
|
||||
return mapOf(
|
||||
// defined by Minecraft official launcher
|
||||
@@ -427,15 +451,7 @@ public class DefaultLauncher extends Launcher {
|
||||
final Command command = generateCommandLine(nativeFolder);
|
||||
|
||||
// To guarantee that when failed to generate launch command line, we will not call pre-launch command
|
||||
List<String> rawCommandLine = command.commandLine.asMutableList();
|
||||
|
||||
// Pass classpath using the environment variable, to reduce the command length
|
||||
String classpath = null;
|
||||
final int cpIndex = rawCommandLine.indexOf("-cp");
|
||||
if (cpIndex >= 0 && cpIndex < rawCommandLine.size() - 1) {
|
||||
rawCommandLine.remove(cpIndex); // remove "-cp"
|
||||
classpath = rawCommandLine.remove(cpIndex);
|
||||
}
|
||||
List<String> rawCommandLine = command.commandLine.asList();
|
||||
|
||||
if (command.tempNativeFolder != null) {
|
||||
Files.deleteIfExists(command.tempNativeFolder);
|
||||
@@ -450,8 +466,11 @@ public class DefaultLauncher extends Launcher {
|
||||
decompressNatives(nativeFolder);
|
||||
}
|
||||
|
||||
if (isUsingLog4j()) {
|
||||
Optional<Library> log4j = findLog4j();
|
||||
if (log4j.isPresent()) {
|
||||
extractLog4jConfigurationFile();
|
||||
if (isLog4jUnsafe(log4j.get()))
|
||||
extractLog4jAgent();
|
||||
}
|
||||
|
||||
File runDirectory = repository.getRunDirectory(version.getId());
|
||||
@@ -470,19 +489,14 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
String appdata = options.getGameDir().getAbsoluteFile().getParent();
|
||||
if (appdata != null) builder.environment().put("APPDATA", appdata);
|
||||
if (classpath != null) {
|
||||
builder.environment().put("CLASSPATH", classpath);
|
||||
// Fix #1153: On Windows, the 'classpath' environment variable in the context overrides the 'CLASSPATH'
|
||||
// Environment variables on Windows are not case-sensitive; The lowercase 'classpath' overwrites any other case.
|
||||
builder.environment().put("classpath", classpath);
|
||||
}
|
||||
|
||||
builder.environment().putAll(getEnvVars());
|
||||
process = builder.start();
|
||||
} catch (IOException e) {
|
||||
throw new ProcessCreationException(e);
|
||||
}
|
||||
|
||||
ManagedProcess p = new ManagedProcess(process, rawCommandLine, classpath);
|
||||
ManagedProcess p = new ManagedProcess(process, rawCommandLine);
|
||||
if (listener != null)
|
||||
startMonitors(p, listener, command.encoding, daemon);
|
||||
return p;
|
||||
@@ -528,8 +542,11 @@ public class DefaultLauncher extends Launcher {
|
||||
decompressNatives(nativeFolder);
|
||||
}
|
||||
|
||||
if (isUsingLog4j()) {
|
||||
Optional<Library> log4j = findLog4j();
|
||||
if (log4j.isPresent()) {
|
||||
extractLog4jConfigurationFile();
|
||||
if (isLog4jUnsafe(log4j.get()))
|
||||
extractLog4jAgent();
|
||||
}
|
||||
|
||||
String scriptExtension = FileUtils.getExtension(scriptFile);
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.mod;
|
||||
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
@@ -184,6 +186,18 @@ public class RemoteMod {
|
||||
return hashes;
|
||||
}
|
||||
|
||||
public FileDownloadTask.IntegrityCheck getIntegrityCheck() {
|
||||
if (hashes.containsKey("md5")) {
|
||||
return new FileDownloadTask.IntegrityCheck("MD5", hashes.get("sha1"));
|
||||
} else if (hashes.containsKey("sha1")) {
|
||||
return new FileDownloadTask.IntegrityCheck("SHA-1", hashes.get("sha1"));
|
||||
} else if (hashes.containsKey("sha512")) {
|
||||
return new FileDownloadTask.IntegrityCheck("SHA-256", hashes.get("sha1"));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ public interface RemoteModRepository {
|
||||
}
|
||||
|
||||
String[] DEFAULT_GAME_VERSIONS = new String[]{
|
||||
"1.18.1", "1.18",
|
||||
"1.17.1", "1.17",
|
||||
"1.16.5", "1.16.4", "1.16.3", "1.16.2", "1.16.1", "1.16",
|
||||
"1.15.2", "1.15.1", "1.15",
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
version '1.0'
|
||||
|
||||
sourceSets.create("agent") {
|
||||
java {
|
||||
srcDir 'src/main/agent'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.0-beta9'
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
tasks.withType(JavaCompile) {
|
||||
sourceCompatibility = 8
|
||||
targetCompatibility = 8
|
||||
|
||||
doLast {
|
||||
FileTree tree = fileTree('build/classes/java/main')
|
||||
FileTree tree = fileTree('build/classes/java')
|
||||
tree.include '**/*.class'
|
||||
tree.each {
|
||||
RandomAccessFile rf = new RandomAccessFile(it, "rw")
|
||||
@@ -20,27 +26,27 @@ tasks.compileJava {
|
||||
}
|
||||
}
|
||||
|
||||
task patchJar(type: Jar) {
|
||||
task agentJar(type: Jar) {
|
||||
dependsOn(tasks.compileJava)
|
||||
|
||||
baseName = 'log4j-patch'
|
||||
baseName = 'log4j-patch-agent'
|
||||
|
||||
from(sourceSets.main.output) {
|
||||
include('**/JndiLookup.class')
|
||||
manifest {
|
||||
attributes 'Premain-Class': 'org.glavo.log4j.patch.agent.Log4jAgent'
|
||||
}
|
||||
}
|
||||
|
||||
task patchBeta9Jar(type: Jar) {
|
||||
dependsOn(tasks.compileJava)
|
||||
|
||||
baseName = 'log4j-patch-beta9'
|
||||
|
||||
from(sourceSets.agent.output)
|
||||
from(sourceSets.main.output) {
|
||||
include '**/Interpolator.class'
|
||||
includeEmptyDirs = false
|
||||
|
||||
eachFile {
|
||||
it.path = "org/glavo/log4j/patch/agent/${it.name}.bin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
enabled = false
|
||||
dependsOn patchJar, patchBeta9Jar
|
||||
dependsOn agentJar
|
||||
}
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package org.glavo.log4j.patch.agent;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.IllegalClassFormatException;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class Log4jAgent {
|
||||
static final String JNDI_LOOKUP_CLASS_NAME = "org/apache/logging/log4j/core/lookup/JndiLookup";
|
||||
static final String INTERPOLATOR_CLASS_NAME = "org/apache/logging/log4j/core/lookup/Interpolator";
|
||||
|
||||
static final byte[] INTERPOLATOR_CLASS_SHA = {53, 103, 16, 123, 51, 29, 65, -70, -32, 71, -11, 7, 114, -15, 72, 127, 40, -38, 35, 18};
|
||||
|
||||
static boolean isBeta = false;
|
||||
|
||||
private static byte[] loadResource(String name) {
|
||||
try {
|
||||
try (InputStream input = Log4jAgent.class.getResourceAsStream(name)) {
|
||||
if (input == null) {
|
||||
throw new AssertionError(name + " not found");
|
||||
}
|
||||
int available = input.available();
|
||||
if (available <= 0) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
byte[] res = new byte[available];
|
||||
if (input.read(res) != available) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
throw new InternalError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static void premain(String agentArgs, Instrumentation inst) throws Exception {
|
||||
if ("true".equals(agentArgs)) {
|
||||
isBeta = true;
|
||||
}
|
||||
inst.addTransformer(new Transformer());
|
||||
}
|
||||
|
||||
private static final class Transformer implements ClassFileTransformer {
|
||||
|
||||
@Override
|
||||
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
|
||||
if (!isBeta && JNDI_LOOKUP_CLASS_NAME.equals(className)) {
|
||||
return loadResource("JndiLookup.class.bin");
|
||||
}
|
||||
if (isBeta && INTERPOLATOR_CLASS_NAME.equals(className)) {
|
||||
try {
|
||||
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
|
||||
sha1.update(classfileBuffer);
|
||||
if (Arrays.equals(INTERPOLATOR_CLASS_SHA, sha1.digest())) {
|
||||
return loadResource("Interpolator.class.bin");
|
||||
}
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new InternalError(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -65,7 +65,7 @@ public class Interpolator implements StrLookup {
|
||||
lookups.put("sys", new SystemPropertiesLookup());
|
||||
lookups.put("env", new EnvironmentLookup());
|
||||
// lookups.put("jndi", new JndiLookup());
|
||||
LOGGER.warn("log4j-patch works, JNDI lookup is disabled");
|
||||
LOGGER.warn("Log4j patch works, JNDI lookup is disabled. This message is not a warning, it represents the successful elimination of some security risks. You should be able to play safely.");
|
||||
|
||||
try {
|
||||
if (Class.forName("javax.servlet.ServletContext") != null) {
|
||||
|
||||
+1
-1
@@ -8,6 +8,6 @@ package org.apache.logging.log4j.core.lookup;
|
||||
|
||||
public class JndiLookup {
|
||||
public JndiLookup() {
|
||||
throw new NoClassDefFoundError("JNDI lookup is disabled");
|
||||
throw new NoClassDefFoundError("JNDI lookup is disabled. This is not an error. We are blocking some vulnerabilities through it. You should be able to play safely.");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user