Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
451baf0
今夜若欠清光满堂 蘸笔天河绘万古生香
CiiLu Aug 27, 2026
f13112f
史笔滔滔烟霞绣像 就中拈来玲珑十二章
CiiLu Aug 27, 2026
66fd48c
吹开汗青淋漓苍茫 明辨残稿谁秋水一双
CiiLu Aug 27, 2026
1471ab1
书阁深处篆隶流芳 断简遗篇挥毫续兴亡
CiiLu Aug 27, 2026
d2e904e
谁白草胡笳烽燧长 将半生哀毁以诗情抵挡
CiiLu Aug 27, 2026
e40696e
断弦含泪更张 最高贵流浪
CiiLu Aug 27, 2026
295b9ec
隔几世玉楼试才歌咏琳琅 冠绝了乌衣巷
CiiLu Aug 27, 2026
130d755
谁一语风絮荡荡 摹尽落雪茫茫
CiiLu Aug 27, 2026
3e1bbdd
谁万里关山暂忘女儿模样 家与国都担当
CiiLu Aug 27, 2026
b11cd87
再亲手摘下黄河落日黑山飞霜 铁甲揩亮 舍旧时云裳
CiiLu Aug 27, 2026
a51cf51
乱世丹心碧血满腔 灵思千转一意辅隋杨
CiiLu Aug 27, 2026
45b23cc
河山纷裂终须入掌 百万明珠抬手赐名将
CiiLu Aug 27, 2026
79c46a7
逸兴遄飞催花一场 犹记看朱成碧添惆怅
CiiLu Aug 27, 2026
9d0cf5e
凰音一绽惊肃朝纲 无字空碑自不负倔强
CiiLu Aug 28, 2026
3841846
谁一身风雅奉君王 持素心清明秤天下才郎
CiiLu Aug 28, 2026
a44e28d
遇合锦绣黄粱 纷纭中静赏
CiiLu Aug 28, 2026
e30b09d
后来有一生明旷漱玉噙香 乌江旁歌霸王
CiiLu Aug 28, 2026
278a1ac
而燕燕于飞怎甘随波低昂 睥睨燕云月上
CiiLu Aug 28, 2026
8c0baba
谁马踏桃花画角长吟金鼓铿锵 寸土不让 万军一剑挡
CiiLu Aug 28, 2026
971bc4e
煌煌千载辉光 古今盛筵长 何幸有你登场 共举霞觞
CiiLu Aug 28, 2026
d5920ad
谁血作胭脂枪起夭矫龙翔 翠袖虎符封疆
CiiLu Aug 28, 2026
bae1641
谁又定两朝风波 掌三世的荣光
CiiLu Aug 28, 2026
ec22538
今夜我为你轻叙巾帼芬芳 浩歌一曲传香
CiiLu Aug 28, 2026
dd99e8b
唱不完跌宕明媚刚强 百代秾华千秋榜样
CiiLu Aug 28, 2026
d307940
行经故梦今又来访 天高海阔盛世飞扬
CiiLu Aug 28, 2026
9c630cc
再谱十万章
CiiLu Aug 30, 2026
35d3fda
update
CiiLu Aug 30, 2026
981ad6c
update
CiiLu Aug 30, 2026
bdac67b
update
CiiLu Aug 30, 2026
58e73c4
update
CiiLu Aug 30, 2026
895fe68
update
CiiLu Aug 30, 2026
aa98852
update
CiiLu Aug 30, 2026
1636c62
update
CiiLu Sep 3, 2026
404dff8
Merge branch 'main' into tare/minetare
CiiLu Sep 5, 2026
2285016
update
CiiLu Sep 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ language-subtag-registry
/HMCLBoot/build/
/minecraft/libraries/HMCLTransformerDiscoveryService/build/
/minecraft/libraries/HMCLMultiMCBootstrap/build/
/minecraft/libraries/HMCLLegacyForgeHelper/build/
/minecraft/libraries/HMCLModLoaderHelper/build/
/buildSrc/build/

# idea
Expand All @@ -42,6 +44,8 @@ language-subtag-registry
/HMCLCore/out/
/minecraft/libraries/HMCLTransformerDiscoveryService/out/
/minecraft/libraries/HMCLMultiMCBootstrap/out/
/minecraft/libraries/HMCLLegacyForgeHelper/out/
/minecraft/libraries/HMCLModLoaderHelper/out/

# eclipse
/bin/
Expand All @@ -50,6 +54,8 @@ language-subtag-registry
/HMCLCore/bin/
/minecraft/libraries/HMCLTransformerDiscoveryService/bin/
/minecraft/libraries/HMCLMultiMCBootstrap/bin/
/minecraft/libraries/HMCLLegacyForgeHelper/bin/
/minecraft/libraries/HMCLModLoaderHelper/bin/
.classpath
.project
.settings
Expand Down
96 changes: 91 additions & 5 deletions HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.util.*;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Stream;

import static org.jackhuang.hmcl.util.logging.Logger.LOG;
Expand Down Expand Up @@ -169,9 +172,27 @@ public void makeLaunchScript(Path scriptFile) throws IOException {
protected void appendJvmArgs(CommandBuilder result) {
super.appendJvmArgs(result);

if (options.isAllowAutoAgent()
&& !options.isNoGeneratedJVMArgs()
&& !options.isNoGeneratedOptimizingJVMArgs()
if (this.instance.getVersion().compareTo("1.6") < 0 && this.instance.hasComponent(GameComponentType.FORGE)) {
LOG.info("Attempting to patch game with legacy-forge-helper");
try {
result.add("-javaagent:" + extractLegacyForgeHelper());
} catch (Exception e) {
LOG.warning("Failed to extract legacy-forge-helper", e);
}
}

if (this.instance.getVersion().compareTo("1.2") < 0 && this.instance.hasComponent(GameComponentType.FORGE)) {
LOG.info("Attempting to patch game with modloader-helper");
try {
result.add("-javaagent:" + extractModLoaderHelper());
} catch (Exception e) {
LOG.warning("Failed to extract modloader-helper", e);
}
}

if (!options.isAllowAutoAgent() && !options.isNoGeneratedJVMArgs()) return;

if (!options.isNoGeneratedOptimizingJVMArgs()
&& NativePatcher.needPatchMemoryUtil(manifest, options.getJava().getParsedVersion())) {
LOG.info("Attempting to patch game with lwjgl-unsafe-agent");
try {
Expand Down Expand Up @@ -220,4 +241,69 @@ private Path extractLwjglUnsafeAgent() throws IOException {
return agentPath;
}

private Path extractLegacyForgeHelper() throws IOException {
Library library = new Library(new Artifact("org.jackhuang.hmcl", "legacy-forge-helper", "1.0"));
String fileName = "HMCLLegacyForgeHelper-1.0.jar";

Path agentPath = instance.getLayout().getLibraryFile(instance.getId(), library).toAbsolutePath().normalize();
if (agentPath.toString().contains("=")) {
throw new IOException("Invalid library path: " + agentPath);
}

byte[] bytes;
try (InputStream input = DefaultLauncher.class.getResourceAsStream("/assets/game/" + fileName)) {
if (input == null) {
throw new IOException("/assets/game/" + fileName + " not found");
}

bytes = input.readAllBytes();
}

if (Files.isRegularFile(agentPath)) {
try {
if (Files.size(agentPath) == bytes.length) {
return agentPath;
}
} catch (IOException e) {
LOG.warning("Failed to check size of " + agentPath, e);
}
}

Files.createDirectories(agentPath.getParent());
FileUtils.saveSafely(agentPath, output -> output.write(bytes));
return agentPath;
}

private Path extractModLoaderHelper() throws IOException {
Library library = new Library(new Artifact("org.jackhuang.hmcl", "modloader-helper", "1.0"));
String fileName = "HMCLModLoaderHelper-1.0.jar";

Path agentPath = instance.getLayout().getLibraryFile(instance.getId(), library).toAbsolutePath().normalize();
if (agentPath.toString().contains("=")) {
throw new IOException("Invalid library path: " + agentPath);
}

byte[] bytes;
try (InputStream input = DefaultLauncher.class.getResourceAsStream("/assets/game/" + fileName)) {
if (input == null) {
throw new IOException("/assets/game/" + fileName + " not found");
}

bytes = input.readAllBytes();
}

if (Files.isRegularFile(agentPath)) {
try {
if (Files.size(agentPath) == bytes.length) {
return agentPath;
}
} catch (IOException e) {
LOG.warning("Failed to check size of " + agentPath, e);
}
}

Files.createDirectories(agentPath.getParent());
FileUtils.saveSafely(agentPath, output -> output.write(bytes));
return agentPath;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ private void launch0() {
})
);
}).withStage("launch.state.dependencies")
.thenComposeAsync(() -> new GameVerificationFixTask(gameInstance, gameInstance.getVersion(), launchManifest.get()))
.thenComposeAsync(() -> new GameVerificationFixTask(gameInstance, gameInstance.getVersion()))
.thenComposeAsync(() -> {
if (setting.getInheritable(GameSettings::allowAutoAgentProperty)
|| setting.getInheritable(GameSettings::noJVMOptionsProperty)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.jackhuang.hmcl.download.cleanroom.CleanroomInstallTask;
import org.jackhuang.hmcl.download.fabric.FabricAPIInstallTask;
import org.jackhuang.hmcl.download.fabric.FabricInstallTask;
import org.jackhuang.hmcl.download.forge.ForgeLegacyInstallTask;
import org.jackhuang.hmcl.download.forge.ForgeNewInstallTask;
import org.jackhuang.hmcl.download.forge.ForgeOldInstallTask;
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
Expand Down Expand Up @@ -172,7 +173,7 @@ public void onRunning(Task<?> task) {
task.setName(i18n("install.installer.install", i18n("install.installer.cleanroom")));
} else if (task instanceof LegacyFabricInstallTask) {
task.setName(i18n("install.installer.install", i18n("install.installer.legacyfabric")));
} else if (task instanceof ForgeNewInstallTask || task instanceof ForgeOldInstallTask) {
} else if (task instanceof ForgeNewInstallTask || task instanceof ForgeOldInstallTask || task instanceof ForgeLegacyInstallTask) {
task.setName(i18n("install.installer.install", i18n("install.installer.forge")));
} else if (task instanceof NeoForgeInstallTask || task instanceof NeoForgeOldInstallTask) {
task.setName(i18n("install.installer.install", i18n("install.installer.neoforge")));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ private void reloadCurrentInstance() {

public void installOffline() {
FileChooser chooser = new FileChooser();
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.modloader.installer"), "*.jar", "*.exe"));
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.modloader.installer"), "*.jar", "*.exe", "*.zip"));
Path file = Controllers.showOpenDialog(chooser);
if (file != null) doInstallOffline(file);
}
Expand Down
4 changes: 3 additions & 1 deletion HMCLCore/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ dependencies {
tasks.processResources {
listOf(
"HMCLTransformerDiscoveryService",
"HMCLMultiMCBootstrap"
"HMCLMultiMCBootstrap",
"HMCLModLoaderHelper",
"HMCLLegacyForgeHelper"
).map { project(":$it").tasks["jar"] as Jar }.forEach { task ->
dependsOn(task)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.tree.ZipFileTree;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.nio.file.Path;
Expand Down Expand Up @@ -124,8 +125,12 @@ public String[] getAuthors() {
return authors;
}

public static LocalModFile fromFile(ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException {
ZipArchiveEntry mcmod = tree.getEntry("mcmod.info");
public static LocalModFile fromFile(@Nullable ModManager modManager, Path modFile, ZipFileTree tree) throws IOException, JsonParseException {
return fromFile(modManager, modFile, tree, "mcmod.info");
}

public static LocalModFile fromFile(@Nullable ModManager modManager, Path modFile, ZipFileTree tree, String infoFileName) throws IOException, JsonParseException {
ZipArchiveEntry mcmod = tree.getEntry(infoFileName);
if (mcmod == null)
throw new IOException("File " + modFile + " is not a Forge mod.");

Expand Down Expand Up @@ -157,7 +162,7 @@ else if (firstToken == JsonToken.BEGIN_OBJECT) {
authors = String.join(", ", metadata.getAuthorList());
if (StringUtils.isBlank(authors))
authors = metadata.getCredits();
return new LocalModFile(modManager, modManager.getLocalMod(metadata.getModId(), ModLoaderType.FORGE), modFile, metadata.getName(), new LocalAddonFile.Description(metadata.getDescription()),
return new LocalModFile(modManager, modManager == null ? null : modManager.getLocalMod(metadata.getModId(), ModLoaderType.FORGE), modFile, metadata.getName(), new LocalAddonFile.Description(metadata.getDescription()),
authors, metadata.getVersion(), metadata.getGameVersion(),
StringUtils.isBlank(metadata.getUrl()) ? metadata.getUpdateUrl() : metadata.url,
metadata.getLogoFile());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@
import org.jackhuang.hmcl.addon.RemoteAddonRepository;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import static org.jackhuang.hmcl.util.logging.Logger.LOG;

Expand All @@ -40,7 +44,9 @@
public final class LocalModFile extends LocalAddonFile implements Comparable<LocalModFile> {

private Path file;
@Nullable
private final ModManager modManager;
@Nullable
private final LocalMod mod;
private final String name;
private final Description description;
Expand All @@ -52,11 +58,11 @@ public final class LocalModFile extends LocalAddonFile implements Comparable<Loc
private final String logoPath;
private final BooleanProperty activeProperty;

public LocalModFile(ModManager modManager, LocalMod mod, Path file, String name, Description description) {
public LocalModFile(@Nullable ModManager modManager, @Nullable LocalMod mod, Path file, String name, Description description) {
this(modManager, mod, file, name, description, "", "", "", "", "");
}

public LocalModFile(ModManager modManager, LocalMod mod, Path file, String name, Description description, String authors, String version, String gameVersion, String url, String logoPath) {
public LocalModFile(@Nullable ModManager modManager, @Nullable LocalMod mod, Path file, String name, Description description, String authors, String version, String gameVersion, String url, String logoPath) {
super();
this.modManager = modManager;
this.mod = mod;
Expand All @@ -69,10 +75,11 @@ public LocalModFile(ModManager modManager, LocalMod mod, Path file, String name,
this.url = url;
this.logoPath = logoPath;

activeProperty = new SimpleBooleanProperty(this, "active", !modManager.isDisabled(file)) {
activeProperty = new SimpleBooleanProperty(this, "active", modManager != null && !modManager.isDisabled(file)) {
@Override
protected void invalidated() {
if (isOld()) return;
if (modManager == null) throw new IllegalStateException();

Path path = LocalModFile.this.file.toAbsolutePath();

Expand All @@ -89,17 +96,22 @@ protected void invalidated() {

fileName = FileUtils.getNameWithoutExtension(LocalAddonManager.getLocalAddonName(file));

if (isOld()) {
mod.getOldFiles().add(this);
} else {
mod.getFiles().add(this);

if (mod != null) {
if (isOld()) {
mod.getOldFiles().add(this);
} else {
mod.getFiles().add(this);
}
}
}

@Nullable
public ModManager getModManager() {
return modManager;
}

@Nullable
public LocalMod getMod() {
return mod;
}
Expand All @@ -110,10 +122,14 @@ public Path getFile() {
}

public ModLoaderType getModLoaderType() {
if (mod == null) throw new IllegalStateException();

return mod.getModLoaderType();
}

public String getId() {
if (mod == null) throw new IllegalStateException();

return mod.getId();
}

Expand Down Expand Up @@ -163,11 +179,16 @@ public String getFileName() {
}

public boolean isOld() {
if (modManager == null) throw new IllegalStateException();

return modManager.isOld(file);
}

@Override
public void setOld(boolean old) throws IOException {
if (modManager == null) throw new IllegalStateException();
if (mod == null) throw new IllegalStateException();

file = modManager.setOld(this, old);

if (old) {
Expand All @@ -186,6 +207,8 @@ public boolean keepOldFiles() {

@Override
public void markDisabled() throws IOException {
if (modManager == null) throw new IllegalStateException();

file = modManager.disableMod(file);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
package org.jackhuang.hmcl.download;

import org.jackhuang.hmcl.download.cleanroom.CleanroomInstallTask;
import org.jackhuang.hmcl.download.forge.ForgeInstallTask;
import org.jackhuang.hmcl.download.forge.ForgeInstallation;
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
import org.jackhuang.hmcl.download.game.GameDownloadTask;
import org.jackhuang.hmcl.download.game.GameLibrariesTask;
Expand Down Expand Up @@ -385,7 +385,7 @@ public Task<GameInstanceManifest> installComponentLocalAsync(GameInstance instan
}

try {
return ForgeInstallTask.install(this, baseManifest, gameVersion, installer);
return ForgeInstallation.install(this, baseManifest, gameVersion, installer);
} catch (IOException ignore) {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,13 @@ public static Task<GameInstancePatch> install(
}

ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class);
if (!gameVersion.equals(profile.getMinecraft()))
throw new VersionMismatchException(profile.getMinecraft(), gameVersion);
if (!gameVersion.equals(profile.minecraft()))
throw new VersionMismatchException(profile.minecraft(), gameVersion);
return new CleanroomInstallTask(
dependencyManager,
manifest,
gameVersion,
modifyVersion(profile.getVersion()),
modifyVersion(profile.version()),
installer);
} else {
throw new IOException();
Expand Down
Loading