Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
123639f
feat: add GameInstanceID and GameInstanceManifest records, and Immuta…
Glavo Jul 4, 2026
1ac9ec3
feat: enhance GameInstanceManifest with additional nullable fields fo…
Glavo Jul 4, 2026
d0da5a7
feat: mark libraries and compatibilityRules as unmodifiable for enhan…
Glavo Jul 4, 2026
9787165
feat: introduce GameInstancePatch record and update GameInstanceManif…
Glavo Jul 4, 2026
78667c2
feat: implement GameRepository2 interface and DefaultGameRepository2 …
Glavo Jul 4, 2026
a063e4a
feat: refactor GameInstanceManifest to use Builder pattern for improv…
Glavo Jul 4, 2026
8ee1734
feat: enhance Library JSON parsing with custom deserializer and impro…
Glavo Jul 4, 2026
4461f01
feat: add GameInstance interface and implement read-write lock in Def…
Glavo Jul 4, 2026
a9d1070
update
Glavo Jul 4, 2026
ec44baa
feat: update GameInstance resolution logic and refactor manifest hand…
Glavo Jul 4, 2026
4b0ecf8
feat: enhance GameRepository2 with instance management methods and up…
Glavo Jul 4, 2026
aa2a2c4
feat: add version field to GameInstancePatch for enhanced instance ma…
Glavo Jul 4, 2026
67b5a6a
delete class
Glavo Jul 4, 2026
1239944
feat: enhance GameRepository2 and related classes with resolved manif…
Glavo Jul 4, 2026
1fb5c00
Merge remote-tracking branch 'upstream/main' into instance
Glavo Jul 5, 2026
2864dd4
refactor: simplify manifest merging in DefaultGameRepository2 and Gam…
Glavo Jul 5, 2026
3227246
feat: add toJsonObject method for GameInstanceManifest and enhance Ga…
Glavo Jul 5, 2026
e0b9e9e
feat: implement toJsonObject method in GameInstancePatch and update G…
Glavo Jul 5, 2026
0189bed
refactor: change nullable Boolean fields to primitive types in GameIn…
Glavo Jul 5, 2026
c8df614
refactor: replace nullable Boolean with primitive type and update unk…
Glavo Jul 5, 2026
3729ff5
refactor: change nullable Boolean fields to primitive types in GameIn…
Glavo Jul 5, 2026
bc3a637
refactor: rename isRoot method to root in GameInstanceManifest and up…
Glavo Jul 5, 2026
116da19
feat: enhance game version handling with event-driven refresh and ins…
Glavo Jul 5, 2026
7efab76
refactor: update GameRepository references to GameRepository2 and adj…
Glavo Jul 6, 2026
99228b7
refactor: rename DefaultGameRepository2 to DefaultGameRepository and …
Glavo Jul 6, 2026
86387ca
refactor: rename version references to instance in repository and rel…
Glavo Jul 6, 2026
38f5861
refactor: add detailed Javadoc comments to GameRepository interface m…
Glavo Jul 6, 2026
b1bcd76
refactor: rename version references to manifest in GameAssetDownloadT…
Glavo Jul 6, 2026
64947ee
refactor: replace version references with manifest in DefaultLauncher…
Glavo Jul 6, 2026
4513d5c
refactor: rename Versions to Instances and update references across t…
Glavo Jul 6, 2026
c890fca
refactor: replace String instanceId references with GameInstanceID ac…
Glavo Jul 6, 2026
7e6da03
refactor: update version references to use GameInstanceID across mult…
Glavo Jul 6, 2026
480e2f8
refactor: update GameItem and GameListItem constructors to use GameIn…
Glavo Jul 6, 2026
08b3cea
refactor: update instance handling to use GameInstanceID across multi…
Glavo Jul 6, 2026
e05d149
refactor: update instance handling to use GameInstanceID in various c…
Glavo Jul 6, 2026
6d91af2
refactor: update instance handling to use GameInstanceID in DefaultDe…
Glavo Jul 6, 2026
e6a68f0
refactor: update ExportWizardProvider and related tasks to use GameIn…
Glavo Jul 6, 2026
8cb61e1
refactor: update LogExporter and related classes to use GameInstanceI…
Glavo Jul 6, 2026
83f0307
refactor: update CurseCompletionTask, CurseInstallTask, and related c…
Glavo Jul 6, 2026
ce79064
refactor: update MainPage to use GameInstanceID instead of version st…
Glavo Jul 6, 2026
9df1c07
refactor: remove string id overloads in GameRepository to use GameIns…
Glavo Jul 6, 2026
c81e499
refactor: add instanceIdConflicts method to HMCLGameRepository and up…
Glavo Jul 6, 2026
d8317c4
refactor: update repository to use GameInstanceID consistently across…
Glavo Jul 6, 2026
90ec6c3
refactor: remove string id overloads and update methods to use GameIn…
Glavo Jul 6, 2026
30b0fc0
refactor: update LibraryAnalyzer to use GameInstanceManifest instead …
Glavo Jul 6, 2026
072cdee
refactor: update methods to use standaloneManifest instead of resolve…
Glavo Jul 6, 2026
aad0772
refactor: simplify resolve methods in DefaultGameRepository to stream…
Glavo Jul 6, 2026
a9956c9
refactor: streamline jar handling in DefaultGameRepository's manifest…
Glavo Jul 6, 2026
83df313
refactor: remove deprecated methods and update references to inherits…
Glavo Jul 6, 2026
72c7764
refactor: add getInheritanceChain method to retrieve inheritance hier…
Glavo Jul 6, 2026
a8eff69
refactor: remove deprecated setId, setJar, and setInheritsFrom method…
Glavo Jul 6, 2026
e4bab37
refactor: rename setMinecraftArguments to withMinecraftArguments for …
Glavo Jul 6, 2026
2012897
refactor: replace set methods with with methods for consistency in Ga…
Glavo Jul 6, 2026
ccd8912
refactor: update GameInstanceManifest constructor to use GameInstance…
Glavo Jul 6, 2026
22bd1cb
refactor(manifest): Make game instance manifests immutable
Glavo Jul 6, 2026
6688aad
refactor(manifest): change logging map to unmodifiable for immutability
Glavo Jul 6, 2026
4d5432e
refactor: replace getMainClass and getJavaVersion methods with direct…
Glavo Jul 6, 2026
1de7128
refactor: rename VersionLibraryBuilder to GameInstanceLibraryBuilder …
Glavo Jul 6, 2026
2118195
refactor: replace legacy method calls with Optional for improved null…
Glavo Jul 6, 2026
9fcb10e
refactor: simplify getDownloadInfo method for improved readability an…
Glavo Jul 6, 2026
6be770e
refactor: rename Instances and related classes to GameInstances for c…
Glavo Jul 6, 2026
680993b
refactor: rename GameInstanceManifest references to manifest for cons…
Glavo Jul 6, 2026
dfa97a3
refactor: rename versionJson to instanceJson for consistency
Glavo Jul 6, 2026
0c4d569
refactor: rename version references to instance for consistency
Glavo Jul 6, 2026
4a5f950
refactor: rename versionSelection to instanceSelection for consistency
Glavo Jul 6, 2026
3bf3120
refactor: update GameInstanceManifest.Resolved parameters for clarity
Glavo Jul 6, 2026
44a519d
refactor: remove obsolete version.cannot_read entries from localizati…
Glavo Jul 6, 2026
d638a53
refactor: rename VersionJsonDownloadTask to GameInstanceJsonDownloadT…
Glavo Jul 6, 2026
8d526e0
refactor: add Gson TypeAdapter for GameInstanceID serialization
Glavo Jul 6, 2026
4ba7f47
refactor: update getInstallTask method signatures to accept GameInsta…
Glavo Jul 6, 2026
f6daa19
refactor: update LibraryAnalyzer calls to use GameInstanceManifest.Re…
Glavo Jul 6, 2026
6b6b332
refactor: update GameInstancePatch methods to use direct property access
Glavo Jul 6, 2026
0e7f5d9
refactor: update saveAsync method to maintain patches in GameInstance…
Glavo Jul 7, 2026
3d9db53
refactor: update game version retrieval to use resolved instance mani…
Glavo Jul 7, 2026
612ee2c
refactor: update getResolvedInstanceManifest to use current status fo…
Glavo Jul 7, 2026
ac929e4
refactor: remove unused clearPatches and removePatchById methods from…
Glavo Jul 7, 2026
7d73f1c
refactor: update GameInstanceManifest setters to preserve raw JSON fi…
Glavo Jul 7, 2026
0ce8350
refactor: replace Map with ImmutableSequencedMap for downloads and lo…
Glavo Jul 7, 2026
93d9310
refactor: update GameInstancePatch to use ImmutableSequencedMap for d…
Glavo Jul 7, 2026
08e471b
refactor: enhance ImmutableSequencedMap with unmodifiable views and i…
Glavo Jul 7, 2026
82e4e30
refactor: update instance ID handling to ensure consistent string rep…
Glavo Jul 7, 2026
f9beb98
refactor: update inheritsFrom field to use GameInstanceID for improve…
Glavo Jul 7, 2026
b99e34e
refactor: add tests for patch parsing and download handling in GameIn…
Glavo Jul 7, 2026
458dea1
refactor: update UI labels to use 'instance' terminology for consistency
Glavo Jul 7, 2026
f219566
refactor: implement LowerCaseEnumTypeAdapter for case-insensitive enu…
Glavo Jul 7, 2026
dd5f627
fix(gson): Use Locale.ROOT for lowercase enum mapping
Glavo Jul 7, 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
20 changes: 10 additions & 10 deletions HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@
*/
public final class HMCLGameLauncher extends DefaultLauncher {

public HMCLGameLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options) {
this(repository, version, authInfo, options, null);
public HMCLGameLauncher(GameRepository repository, GameInstanceManifest manifest, AuthInfo authInfo, LaunchOptions options) {
this(repository, manifest, authInfo, options, null);
}

public HMCLGameLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) {
this(repository, version, authInfo, options, listener, true);
public HMCLGameLauncher(GameRepository repository, GameInstanceManifest manifest, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) {
this(repository, manifest, authInfo, options, listener, true);
}

public HMCLGameLauncher(GameRepository repository, Version version, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) {
super(repository, version, authInfo, options, listener, daemon);
public HMCLGameLauncher(GameRepository repository, GameInstanceManifest manifest, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) {
super(repository, manifest, authInfo, options, listener, daemon);
}

@Override
Expand All @@ -66,7 +66,7 @@ private void generateOptionsTxt() {
if (options.isDisableAutoGameOptions())
return;

Path runDir = repository.getRunDirectory(version.getId());
Path runDir = repository.getRunDirectory(manifest.id());
Path optionsFile = runDir.resolve("options.txt");
Path configFolder = runDir.resolve("config");

Expand All @@ -91,7 +91,7 @@ private void generateOptionsTxt() {
* 1.11 ~ 1.12 : zh_cn works fine, zh_CN will display Chinese but the language setting will incorrectly show English as selected
* 1.13+ : zh_cn works fine, zh_CN will automatically switch to English
*/
GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(repository.getGameVersion(version));
GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(repository.getGameVersion(manifest));
if (gameVersion.compareTo("1.1") < 0)
return;

Expand Down Expand Up @@ -161,7 +161,7 @@ protected void appendJvmArgs(CommandBuilder result) {
if (options.isAllowAutoAgent()
&& !options.isNoGeneratedJVMArgs()
&& !options.isNoGeneratedOptimizingJVMArgs()
&& NativePatcher.needPatchMemoryUtil(version, options.getJava().getParsedVersion())) {
&& NativePatcher.needPatchMemoryUtil(manifest, options.getJava().getParsedVersion())) {
LOG.info("Attempting to patch game with lwjgl-unsafe-agent");
try {
result.add("-javaagent:" + extractLwjglUnsafeAgent());
Expand All @@ -180,7 +180,7 @@ private Path extractLwjglUnsafeAgent() throws IOException {
Library library = new Library(new Artifact("org.glavo", "lwjgl-unsafe-agent", agentVersion));
String fileName = library.getArtifact().getFileName();

Path agentPath = repository.getLibraryFile(version, library).toAbsolutePath().normalize();
Path agentPath = repository.getLibraryFile(manifest, library).toAbsolutePath().normalize();
if (agentPath.toString().contains("=")) {
throw new IOException("Invalid library path: " + agentPath);
}
Expand Down
370 changes: 189 additions & 181 deletions HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -37,29 +37,29 @@

public final class HMCLModpackInstallTask extends Task<Void> {
private final Path zipFile;
private final String name;
private final GameInstanceID instanceId;
private final HMCLGameRepository repository;
private final DefaultDependencyManager dependency;
private final Modpack modpack;
private final List<Task<?>> dependencies = new ArrayList<>(1);
private final List<Task<?>> dependents = new ArrayList<>(4);

public HMCLModpackInstallTask(HMCLGameRepository repository, Path zipFile, Modpack modpack, String name) {
public HMCLModpackInstallTask(HMCLGameRepository repository, Path zipFile, Modpack modpack, GameInstanceID instanceId) {
this.repository = repository;
this.dependency = repository.getDependency();
this.zipFile = zipFile;
this.name = name;
this.instanceId = instanceId;
this.modpack = modpack;

Path run = repository.getRunDirectory(name);
Path json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && Files.notExists(json))
throw new IllegalArgumentException("Version " + name + " already exists");
Path run = repository.getRunDirectory(this.instanceId);
Path json = repository.getModpackConfiguration(this.instanceId);
if (repository.hasInstance(this.instanceId) && Files.notExists(json))
throw new IllegalArgumentException("Instance " + instanceId + " already exists");

dependents.add(dependency.gameBuilder().name(name).gameVersion(modpack.getGameVersion()).buildAsync());
dependents.add(dependency.newGameBuilder().name(this.instanceId).gameVersion(modpack.getGameVersion()).buildAsync());

onDone().register(event -> {
if (event.isFailed()) repository.removeVersionFromDisk(name);
if (event.isFailed()) repository.removeInstanceFromDisk(this.instanceId);
});

ModpackConfiguration<Modpack> config = null;
Expand All @@ -68,12 +68,12 @@ public HMCLModpackInstallTask(HMCLGameRepository repository, Path zipFile, Modpa
config = JsonUtils.fromJsonFile(json, ModpackConfiguration.typeOf(Modpack.class));

if (!HMCLModpackProvider.INSTANCE.getName().equals(config.getType()))
throw new IllegalArgumentException("Version " + name + " is not a HMCL modpack. Cannot update this version.");
throw new IllegalArgumentException("Version " + instanceId + " is not a HMCL modpack. Cannot update this version.");
}
} catch (JsonParseException | IOException ignore) {
}
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), Collections.singletonList("/minecraft"), it -> !"pack.json".equals(it), config));
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList("/minecraft"), modpack, HMCLModpackProvider.INSTANCE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(name)).withStage("hmcl.modpack"));
dependents.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), Collections.singletonList("/minecraft"), modpack, HMCLModpackProvider.INSTANCE, modpack.getName(), modpack.getVersion(), repository.getModpackConfiguration(this.instanceId)).withStage("hmcl.modpack"));
}

@Override
Expand All @@ -89,9 +89,9 @@ public List<Task<?>> getDependents() {
@Override
public void execute() throws Exception {
String json = CompressingUtils.readTextZipEntry(zipFile, "minecraft/pack.json");
Version originalVersion = JsonUtils.GSON.fromJson(json, Version.class).setId(name).setJar(null);
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(originalVersion, null);
Task<Version> libraryTask = Task.supplyAsync(() -> originalVersion);
GameInstanceManifest originalManifest = JsonUtils.GSON.fromJson(json, GameInstanceManifest.class).withId(instanceId).withJar(null);
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(originalManifest, null);
Task<GameInstanceManifest> libraryTask = Task.supplyAsync(() -> originalManifest);
// reinstall libraries
// libraries of Forge and OptiFine should be obtained by installation.
for (LibraryAnalyzer.LibraryMark mark : analyzer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,42 +42,42 @@ public String getName() {
}

@Override
public Task<?> createCompletionTask(DefaultDependencyManager dependencyManager, String version) {
public Task<?> createCompletionTask(DefaultDependencyManager dependencyManager, GameInstanceID instanceId) {
return null;
}

@Override
public Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, String name, Path zipFile, Modpack modpack) throws MismatchedModpackTypeException {
public Task<?> createUpdateTask(DefaultDependencyManager dependencyManager, GameInstanceID instanceId, Path zipFile, Modpack modpack) throws MismatchedModpackTypeException {
if (!(modpack.getManifest() instanceof HMCLModpackManifest))
throw new MismatchedModpackTypeException(getName(), modpack.getManifest().getProvider().getName());

if (!(dependencyManager.getGameRepository() instanceof HMCLGameRepository repository)) {
throw new IllegalArgumentException("HMCLModpackProvider requires HMCLGameRepository");
}

return new ModpackUpdateTask(dependencyManager.getGameRepository(), name, new HMCLModpackInstallTask(repository, zipFile, modpack, name));
return new ModpackUpdateTask(dependencyManager.getGameRepository(), instanceId, new HMCLModpackInstallTask(repository, zipFile, modpack, instanceId));
}

@Override
public Modpack readManifest(ZipArchiveReader file, Path path, Charset encoding) throws IOException, JsonParseException {
String manifestJson = CompressingUtils.readTextZipEntry(file, "modpack.json");
Modpack manifest = JsonUtils.fromNonNullJson(manifestJson, HMCLModpack.class).setEncoding(encoding);
String gameJson = CompressingUtils.readTextZipEntry(file, "minecraft/pack.json");
Version game = JsonUtils.fromNonNullJson(gameJson, Version.class);
if (game.getJar() == null)
GameInstanceManifest game = JsonUtils.fromNonNullJson(gameJson, GameInstanceManifest.class);
if (game.jar() == null)
if (StringUtils.isBlank(manifest.getVersion()))
throw new JsonParseException("Cannot recognize the game version of modpack " + file + ".");
else
manifest.setManifest(HMCLModpackManifest.INSTANCE);
else
manifest.setManifest(HMCLModpackManifest.INSTANCE).setGameVersion(game.getJar());
manifest.setManifest(HMCLModpackManifest.INSTANCE).setGameVersion(game.jar().id());
return manifest;
}

private final static class HMCLModpack extends Modpack {
@Override
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, String name, String iconUrl) {
return new HMCLModpackInstallTask((HMCLGameRepository) dependencyManager.getGameRepository(), zipFile, this, name);
public Task<?> getInstallTask(DefaultDependencyManager dependencyManager, Path zipFile, GameInstanceID instanceId, String iconUrl) {
return new HMCLModpackInstallTask((HMCLGameRepository) dependencyManager.getGameRepository(), zipFile, this, instanceId);
}
}

Expand Down
Loading