From 0d6a092c379ed3f60ac4ebb0d9f63e39ceeef518 Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Wed, 4 Feb 2026 11:21:59 +0800 Subject: [PATCH 01/28] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0seed=E5=92=8C?= =?UTF-8?q?=E7=94=9F=E6=88=90=E5=BB=BA=E7=AD=91=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/WorldInfoPage.java | 6 ++++-- .../java/org/jackhuang/hmcl/game/World.java | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index 52e3216b63..d3d938932a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -260,8 +260,10 @@ private void updateControls() { // generate_features was valid after 20w20a and MapFeatures was before that if (dataTag.get("WorldGenSettings") instanceof CompoundTag worldGenSettings) { bindTagAndToggleButton(worldGenSettings.get("generate_features"), generateFeaturesButton); - } else { - bindTagAndToggleButton(dataTag.get("MapFeatures"), generateFeaturesButton); + } else if (dataTag.get("MapFeatures") instanceof ByteTag mapFeatures) { + bindTagAndToggleButton(mapFeatures, generateFeaturesButton); + } else if (world.getWorldGenSettingsDat() != null && world.getWorldGenSettingsDat().get("data") instanceof CompoundTag worldGenSettingsDataTag) { + bindTagAndToggleButton(worldGenSettingsDataTag.get("generate_structures"), generateFeaturesButton); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index 8e3f6fc38c..ef82618229 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -45,6 +45,7 @@ public final class World { private final Path file; private String fileName; private CompoundTag levelData; + private CompoundTag worldGenSettingsDat; private Image icon; private Path levelDataPath; @@ -92,6 +93,10 @@ public CompoundTag getLevelData() { return levelData; } + public CompoundTag getWorldGenSettingsDat() { + return worldGenSettingsDat; + } + public long getLastPlayed() { CompoundTag data = levelData.get("Data"); LongTag lastPlayedTag = data.get("LastPlayed"); @@ -113,6 +118,8 @@ public long getLastPlayed() { return seedTag.getValue(); } else if (data.get("RandomSeed") instanceof LongTag seedTag) { //Valid before 1.16 return seedTag.getValue(); + } else if (worldGenSettingsDat != null && worldGenSettingsDat.get("data") instanceof CompoundTag dataTag && dataTag.get("seed") instanceof LongTag seedTag) { //Valid after 26.1-snapshot-6 + return seedTag.getValue(); } return null; } @@ -232,6 +239,19 @@ private void loadAndCheckLevelDat(Path levelDat) throws IOException { if (!(data.get("LastPlayed") instanceof LongTag)) throw new IOException("level.dat missing LastPlayed"); + + loadAndCheckOtherDat(); + } + + private void loadAndCheckOtherDat() throws IOException { +// if (getGameVersion().isAtLeast()) { +// +// } + + Path wgsd = file.resolve("data\\minecraft\\world_gen_settings.dat"); + if (Files.exists(wgsd)) { + this.worldGenSettingsDat = parseLevelDat(wgsd); + } } public void reloadLevelDat() throws IOException { From 3257a9278f71ebbf23d0666a120a27d1eac85999 Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Wed, 4 Feb 2026 14:32:43 +0800 Subject: [PATCH 02/28] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0micanbt?= =?UTF-8?q?=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/WorldInfoPage.java | 32 +++++----- HMCLCore/build.gradle.kts | 1 + .../java/org/jackhuang/hmcl/game/World.java | 62 +++++++++---------- gradle/libs.versions.toml | 2 + 4 files changed, 47 insertions(+), 50 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index d3d938932a..85dd239b01 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.ui.versions; -import com.github.steveice10.opennbt.tag.builtin.*; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXTextField; import javafx.beans.property.SimpleBooleanProperty; @@ -47,6 +46,7 @@ import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.io.FileUtils; +import tech.minediamond.micanbt.tag.*; import java.io.IOException; import java.nio.file.Files; @@ -86,7 +86,7 @@ private CompoundTag loadWorldInfo() throws IOException { } private void updateControls() { - CompoundTag dataTag = levelDat.get("Data"); + CompoundTag dataTag = (CompoundTag) levelDat.get("Data"); ScrollPane scrollPane = new ScrollPane(); scrollPane.setFitToHeight(true); @@ -109,7 +109,7 @@ private void updateControls() { setRightTextField(worldNamePane, worldNameField, 200); if (dataTag.get("LevelName") instanceof StringTag worldNameTag) { - var worldName = new SimpleStringProperty(worldNameTag.getValue()); + var worldName = new SimpleStringProperty(worldNameTag.getRawValue()); FXUtils.bindString(worldNameField, worldName); worldNameField.getProperties().put(WorldInfoPage.class.getName() + ".worldNameProperty", worldName); worldName.addListener((observable, oldValue, newValue) -> { @@ -221,7 +221,7 @@ private void updateControls() { } else if (dataTag.get("SpawnX") instanceof IntTag intX && dataTag.get("SpawnY") instanceof IntTag intY && dataTag.get("SpawnZ") instanceof IntTag intZ) { - value = Dimension.OVERWORLD.formatPosition(intX.getValue(), intY.getValue(), intZ.getValue()); + value = Dimension.OVERWORLD.formatPosition(intX.getClonedValue(), intY.getClonedValue(), intZ.getClonedValue()); } else { value = null; } @@ -239,7 +239,7 @@ private void updateControls() { { timePane.setTitle(i18n("world.info.time")); if (dataTag.get("Time") instanceof LongTag timeTag) { - Duration duration = Duration.ofSeconds(timeTag.getValue() / 20); + Duration duration = Duration.ofSeconds(timeTag.getClonedValue() / 20); timePane.setText(i18n("world.info.time.format", duration.toDays(), duration.toHoursPart(), duration.toMinutesPart())); } } @@ -274,7 +274,7 @@ private void updateControls() { difficultyButton.setItems(Difficulty.items); if (dataTag.get("Difficulty") instanceof ByteTag difficultyTag) { - Difficulty difficulty = Difficulty.of(difficultyTag.getValue()); + Difficulty difficulty = Difficulty.of(difficultyTag.getClonedValue()); if (difficulty != null) { difficultyButton.setValue(difficulty); difficultyButton.valueProperty().addListener((o, oldValue, newValue) -> { @@ -343,7 +343,7 @@ private void updateControls() { && playerTag.get("SpawnZ") instanceof IntTag intZ) { // Valid before 25w07a // SpawnDimension tag is valid after 20w12a. Prior to this version, the game did not record the respawn point dimension and respawned in the Overworld. spawnPane.setText((playerTag.get("SpawnDimension") instanceof StringTag dimensionTag ? Dimension.of(dimensionTag) : Dimension.OVERWORLD) - .formatPosition(intX.getValue(), intY.getValue(), intZ.getValue())); + .formatPosition(intX.getClonedValue(), intY.getClonedValue(), intZ.getClonedValue())); } } @@ -355,8 +355,8 @@ private void updateControls() { if (playerTag.get("playerGameType") instanceof IntTag playerGameTypeTag && dataTag.get("hardcore") instanceof ByteTag hardcoreTag) { - boolean isHardcore = hardcoreTag.getValue() == 1; - GameType gameType = GameType.of(playerGameTypeTag.getValue(), isHardcore); + boolean isHardcore = hardcoreTag.getClonedValue() == 1; + GameType gameType = GameType.of(playerGameTypeTag.getClonedValue(), isHardcore); if (gameType != null) { playerGameTypePane.setValue(gameType); playerGameTypePane.valueProperty().addListener((o, oldValue, newValue) -> { @@ -433,7 +433,7 @@ private void setRightTextField(BorderPane borderPane, JFXTextField textField, in private void bindTagAndToggleButton(Tag tag, LineToggleButton toggleButton) { if (tag instanceof ByteTag byteTag) { - byte value = byteTag.getValue(); + byte value = byteTag.getClonedValue(); if (value == 0 || value == 1) { toggleButton.setSelected(value == 1); toggleButton.selectedProperty().addListener((o, oldValue, newValue) -> { @@ -454,7 +454,7 @@ private void bindTagAndToggleButton(Tag tag, LineToggleButton toggleButton) { } private void bindTagAndTextField(IntTag intTag, JFXTextField jfxTextField) { - jfxTextField.setText(intTag.getValue().toString()); + jfxTextField.setText(intTag.getClonedValue().toString()); jfxTextField.textProperty().addListener((o, oldValue, newValue) -> { if (newValue != null) { @@ -475,7 +475,7 @@ private void bindTagAndTextField(IntTag intTag, JFXTextField jfxTextField) { } private void bindTagAndTextField(FloatTag floatTag, JFXTextField jfxTextField) { - jfxTextField.setText(new DecimalFormat("0.#").format(floatTag.getValue())); + jfxTextField.setText(new DecimalFormat("0.#").format(floatTag.getClonedValue())); jfxTextField.textProperty().addListener((o, oldValue, newValue) -> { if (newValue != null) { @@ -528,14 +528,14 @@ private record Dimension(String name) { static Dimension of(Tag tag) { if (tag instanceof IntTag intTag) { - return switch (intTag.getValue()) { + return switch (intTag.getClonedValue()) { case 0 -> OVERWORLD; case -1 -> THE_NETHER; case 1 -> THE_END; default -> null; }; } else if (tag instanceof StringTag stringTag) { - String id = stringTag.getValue(); + String id = stringTag.getClonedValue(); return switch (id) { case "overworld", "minecraft:overworld" -> OVERWORLD; case "the_nether", "minecraft:the_nether" -> THE_NETHER; @@ -556,8 +556,8 @@ String formatPosition(Tag tag) { if (x instanceof DoubleTag && y instanceof DoubleTag && z instanceof DoubleTag) { return this == OVERWORLD - ? String.format("(%.2f, %.2f, %.2f)", x.getValue(), y.getValue(), z.getValue()) - : String.format("%s (%.2f, %.2f, %.2f)", name, x.getValue(), y.getValue(), z.getValue()); + ? String.format("(%.2f, %.2f, %.2f)", x.getClonedValue(), y.getClonedValue(), z.getClonedValue()) + : String.format("%s (%.2f, %.2f, %.2f)", name, x.getClonedValue(), y.getClonedValue(), z.getClonedValue()); } return null; diff --git a/HMCLCore/build.gradle.kts b/HMCLCore/build.gradle.kts index 86ca2bde92..6961ba37fc 100644 --- a/HMCLCore/build.gradle.kts +++ b/HMCLCore/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { api(libs.chardet) api(libs.jna) api(libs.pci.ids) + api(libs.micanbt) compileOnlyApi(libs.jetbrains.annotations) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index ef82618229..4fa691a005 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -17,16 +17,18 @@ */ package org.jackhuang.hmcl.game; -import com.github.steveice10.opennbt.NBTIO; -import com.github.steveice10.opennbt.tag.builtin.*; import javafx.scene.image.Image; import org.jackhuang.hmcl.util.io.*; import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jetbrains.annotations.Nullable; +import tech.minediamond.micanbt.NBT.NBT; +import tech.minediamond.micanbt.tag.ByteTag; +import tech.minediamond.micanbt.tag.CompoundTag; +import tech.minediamond.micanbt.tag.LongTag; +import tech.minediamond.micanbt.tag.StringTag; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; @@ -35,8 +37,6 @@ import java.nio.file.*; import java.util.List; import java.util.stream.Stream; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -69,9 +69,9 @@ public String getFileName() { } public String getWorldName() { - CompoundTag data = levelData.get("Data"); - StringTag levelNameTag = data.get("LevelName"); - return levelNameTag.getValue(); + CompoundTag data = (CompoundTag) levelData.get("Data"); + StringTag levelNameTag = (StringTag) data.get("LevelName"); + return levelNameTag.getClonedValue(); } public void setWorldName(String worldName) throws IOException { @@ -98,36 +98,36 @@ public CompoundTag getWorldGenSettingsDat() { } public long getLastPlayed() { - CompoundTag data = levelData.get("Data"); - LongTag lastPlayedTag = data.get("LastPlayed"); - return lastPlayedTag.getValue(); + CompoundTag data = (CompoundTag) levelData.get("Data"); + LongTag lastPlayedTag = (LongTag) data.get("LastPlayed"); + return lastPlayedTag.getClonedValue(); } public @Nullable GameVersionNumber getGameVersion() { if (levelData.get("Data") instanceof CompoundTag data && data.get("Version") instanceof CompoundTag versionTag && versionTag.get("Name") instanceof StringTag nameTag) { - return GameVersionNumber.asGameVersion(nameTag.getValue()); + return GameVersionNumber.asGameVersion(nameTag.getClonedValue()); } return null; } public @Nullable Long getSeed() { - CompoundTag data = levelData.get("Data"); + CompoundTag data = (CompoundTag) levelData.get("Data"); if (data.get("WorldGenSettings") instanceof CompoundTag worldGenSettingsTag && worldGenSettingsTag.get("seed") instanceof LongTag seedTag) { //Valid after 1.16 - return seedTag.getValue(); + return seedTag.getClonedValue(); } else if (data.get("RandomSeed") instanceof LongTag seedTag) { //Valid before 1.16 - return seedTag.getValue(); + return seedTag.getClonedValue(); } else if (worldGenSettingsDat != null && worldGenSettingsDat.get("data") instanceof CompoundTag dataTag && dataTag.get("seed") instanceof LongTag seedTag) { //Valid after 26.1-snapshot-6 - return seedTag.getValue(); + return seedTag.getClonedValue(); } return null; } public boolean isLargeBiomes() { - CompoundTag data = levelData.get("Data"); + CompoundTag data = (CompoundTag) levelData.get("Data"); if (data.get("generatorName") instanceof StringTag generatorNameTag) { //Valid before 1.16 - return "largeBiomes".equals(generatorNameTag.getValue()); + return "largeBiomes".equals(generatorNameTag.getClonedValue()); } else { if (data.get("WorldGenSettings") instanceof CompoundTag worldGenSettingsTag && worldGenSettingsTag.get("dimensions") instanceof CompoundTag dimensionsTag @@ -135,9 +135,9 @@ public boolean isLargeBiomes() { && overworldTag.get("generator") instanceof CompoundTag generatorTag) { if (generatorTag.get("biome_source") instanceof CompoundTag biomeSourceTag && biomeSourceTag.get("large_biomes") instanceof ByteTag largeBiomesTag) { //Valid between 1.16 and 1.16.2 - return largeBiomesTag.getValue() == (byte) 1; + return largeBiomesTag.getClonedValue() == (byte) 1; } else if (generatorTag.get("settings") instanceof StringTag settingsTag) { //Valid after 1.16.2 - return "minecraft:large_biomes".equals(settingsTag.getValue()); + return "minecraft:large_biomes".equals(settingsTag.getClonedValue()); } } return false; @@ -230,7 +230,7 @@ private void loadFromZip() throws IOException { private void loadAndCheckLevelDat(Path levelDat) throws IOException { this.levelData = parseLevelDat(levelDat); - CompoundTag data = levelData.get("Data"); + CompoundTag data = (CompoundTag) levelData.get("Data"); if (data == null) throw new IOException("level.dat missing Data"); @@ -267,7 +267,7 @@ public void rename(String newName) throws IOException { throw new IOException("Not a valid world directory"); // Change the name recorded in level.dat - CompoundTag data = levelData.get("Data"); + CompoundTag data = (CompoundTag) levelData.get("Data"); data.put(new StringTag("LevelName", newName)); writeLevelDat(levelData); @@ -369,21 +369,15 @@ public void writeLevelDat(CompoundTag nbt) throws IOException { if (!Files.isDirectory(file)) throw new IOException("Not a valid world directory"); - FileUtils.saveSafely(getLevelDatFile(), os -> { - try (OutputStream gos = new GZIPOutputStream(os)) { - NBTIO.writeTag(gos, nbt); - } - }); +// FileUtils.saveSafely(getLevelDatFile(), os -> { +// try (OutputStream gos = new GZIPOutputStream(os)) { +// } +// }); + NBT.write(nbt, getLevelDatFile()); } private static CompoundTag parseLevelDat(Path path) throws IOException { - try (InputStream is = new GZIPInputStream(Files.newInputStream(path))) { - Tag nbt = NBTIO.readTag(is); - if (nbt instanceof CompoundTag compoundTag) - return compoundTag; - else - throw new IOException("level.dat malformed"); - } + return NBT.read(path); } private static boolean isLocked(Path sessionLockFile) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2897211f61..4726e831c1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,6 +19,7 @@ java-info = "1.0" authlib-injector = "1.2.7" monet-fx = "0.4.0" terracotta = "0.4.1" +micanbt = "0.2.0" # testing junit = "6.0.1" @@ -49,6 +50,7 @@ pci-ids = { module = "org.glavo:pci-ids", version.ref = "pci-ids" } java-info = { module = "org.glavo:java-info", version.ref = "java-info" } authlib-injector = { module = "org.glavo.hmcl:authlib-injector", version.ref = "authlib-injector" } monet-fx = { module = "org.glavo:MonetFX", version.ref = "monet-fx" } +micanbt = {module = "tech.minediamond:micanbt", version.ref = "micanbt"} # testing junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } From ecdb8bc6a20148991a9369cb69a29261fadbede8 Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Wed, 4 Feb 2026 15:10:38 +0800 Subject: [PATCH 03/28] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E9=83=A8?= =?UTF-8?q?=E5=88=86tag=E8=8E=B7=E5=8F=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/jackhuang/hmcl/game/World.java | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index 4fa691a005..d1aeaf9c45 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -69,13 +69,11 @@ public String getFileName() { } public String getWorldName() { - CompoundTag data = (CompoundTag) levelData.get("Data"); - StringTag levelNameTag = (StringTag) data.get("LevelName"); - return levelNameTag.getClonedValue(); + return ((StringTag) levelData.at("Data/LevelName")).getClonedValue(); } public void setWorldName(String worldName) throws IOException { - if (levelData.get("Data") instanceof CompoundTag data && data.get("LevelName") instanceof StringTag levelNameTag) { + if (levelData.at("Data/LevelName") instanceof StringTag levelNameTag) { levelNameTag.setValue(worldName); writeLevelDat(levelData); } @@ -98,27 +96,22 @@ public CompoundTag getWorldGenSettingsDat() { } public long getLastPlayed() { - CompoundTag data = (CompoundTag) levelData.get("Data"); - LongTag lastPlayedTag = (LongTag) data.get("LastPlayed"); - return lastPlayedTag.getClonedValue(); + return ((LongTag) levelData.at("Data/LastPlayed")).getClonedValue(); } public @Nullable GameVersionNumber getGameVersion() { - if (levelData.get("Data") instanceof CompoundTag data && - data.get("Version") instanceof CompoundTag versionTag && - versionTag.get("Name") instanceof StringTag nameTag) { + if (levelData.at("Data/Version/Name") instanceof StringTag nameTag) { return GameVersionNumber.asGameVersion(nameTag.getClonedValue()); } return null; } public @Nullable Long getSeed() { - CompoundTag data = (CompoundTag) levelData.get("Data"); - if (data.get("WorldGenSettings") instanceof CompoundTag worldGenSettingsTag && worldGenSettingsTag.get("seed") instanceof LongTag seedTag) { //Valid after 1.16 + if (levelData.at("Data/WorldGenSettings/seed") instanceof LongTag seedTag) { //Valid after 1.16 return seedTag.getClonedValue(); - } else if (data.get("RandomSeed") instanceof LongTag seedTag) { //Valid before 1.16 + } else if (levelData.get("Data/RandomSeed") instanceof LongTag seedTag) { //Valid before 1.16 return seedTag.getClonedValue(); - } else if (worldGenSettingsDat != null && worldGenSettingsDat.get("data") instanceof CompoundTag dataTag && dataTag.get("seed") instanceof LongTag seedTag) { //Valid after 26.1-snapshot-6 + } else if (worldGenSettingsDat != null && worldGenSettingsDat.at("data/seed") instanceof LongTag seedTag) { //Valid after 26.1-snapshot-6 return seedTag.getClonedValue(); } return null; @@ -129,12 +122,8 @@ public boolean isLargeBiomes() { if (data.get("generatorName") instanceof StringTag generatorNameTag) { //Valid before 1.16 return "largeBiomes".equals(generatorNameTag.getClonedValue()); } else { - if (data.get("WorldGenSettings") instanceof CompoundTag worldGenSettingsTag - && worldGenSettingsTag.get("dimensions") instanceof CompoundTag dimensionsTag - && dimensionsTag.get("minecraft:overworld") instanceof CompoundTag overworldTag - && overworldTag.get("generator") instanceof CompoundTag generatorTag) { - if (generatorTag.get("biome_source") instanceof CompoundTag biomeSourceTag - && biomeSourceTag.get("large_biomes") instanceof ByteTag largeBiomesTag) { //Valid between 1.16 and 1.16.2 + if (data.at("WorldGenSettings/dimensions/minecraft:overworld/generator") instanceof CompoundTag generatorTag) { + if (generatorTag.at("biome_source/large_biomes") instanceof ByteTag largeBiomesTag) { //Valid between 1.16 and 1.16.2 return largeBiomesTag.getClonedValue() == (byte) 1; } else if (generatorTag.get("settings") instanceof StringTag settingsTag) { //Valid after 1.16.2 return "minecraft:large_biomes".equals(settingsTag.getClonedValue()); From fddaa42e792114de5fe4c4903a05ea876a51af2b Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Wed, 4 Feb 2026 16:45:31 +0800 Subject: [PATCH 04/28] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E9=9A=BE?= =?UTF-8?q?=E5=BA=A6/=E9=9A=BE=E5=BA=A6=E9=94=81=E5=AE=9A=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/WorldInfoPage.java | 27 +++++++++++++++++-- .../java/org/jackhuang/hmcl/game/World.java | 5 ++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index 85dd239b01..a00577e5ba 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -286,6 +286,17 @@ private void updateControls() { } else { difficultyButton.setDisable(true); } + } else if (dataTag.at("difficulty_settings/difficulty") instanceof StringTag difficultyTag) { + Difficulty difficulty = Difficulty.of(difficultyTag.getClonedValue()); + if (difficulty != null) { + difficultyButton.setValue(difficulty); + difficultyButton.valueProperty().addListener((o, oldValue, newValue) -> { + if (newValue != null) { + difficultyTag.setValue(newValue.name().toLowerCase(Locale.ROOT)); + saveLevelDat(); + } + }); + } } else { difficultyButton.setDisable(true); } @@ -295,8 +306,11 @@ private void updateControls() { { difficultyLockPane.setTitle(i18n("world.info.difficulty_lock")); difficultyLockPane.setDisable(isReadOnly); - - bindTagAndToggleButton(dataTag.get("DifficultyLocked"), difficultyLockPane); + if (dataTag.get("DifficultyLocked") instanceof ByteTag difficultyLockedTag) { + bindTagAndToggleButton(difficultyLockedTag, difficultyLockPane); + } else { + bindTagAndToggleButton(dataTag.at("difficulty_settings/locked"), difficultyLockPane); + } } worldInfo.getContent().setAll( @@ -599,6 +613,15 @@ static Difficulty of(int d) { return (d >= 0 && d < items.size()) ? items.get(d) : null; } + static Difficulty of(String name) { + for (Difficulty item : items) { + if (item.name().toLowerCase(Locale.ROOT).equals(name)) { + return item; + } + } + return null; + } + @Override public String toString() { return i18n("world.info.difficulty." + name().toLowerCase(Locale.ROOT)); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index d1aeaf9c45..574096d796 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -118,11 +118,10 @@ public long getLastPlayed() { } public boolean isLargeBiomes() { - CompoundTag data = (CompoundTag) levelData.get("Data"); - if (data.get("generatorName") instanceof StringTag generatorNameTag) { //Valid before 1.16 + if (levelData.at("Data/generatorName") instanceof StringTag generatorNameTag) { //Valid before 1.16 return "largeBiomes".equals(generatorNameTag.getClonedValue()); } else { - if (data.at("WorldGenSettings/dimensions/minecraft:overworld/generator") instanceof CompoundTag generatorTag) { + if (levelData.at("Data/WorldGenSettings/dimensions/minecraft:overworld/generator") instanceof CompoundTag generatorTag) { if (generatorTag.at("biome_source/large_biomes") instanceof ByteTag largeBiomesTag) { //Valid between 1.16 and 1.16.2 return largeBiomesTag.getClonedValue() == (byte) 1; } else if (generatorTag.get("settings") instanceof StringTag settingsTag) { //Valid after 1.16.2 From 7bc1d25087cbf55bd9592855563720ce832e30b9 Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Wed, 4 Feb 2026 17:20:47 +0800 Subject: [PATCH 05/28] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84playerTag?= =?UTF-8?q?=E7=9A=84=E8=8E=B7=E5=8F=96=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/WorldInfoPage.java | 3 ++- .../java/org/jackhuang/hmcl/game/World.java | 26 ++++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index a00577e5ba..6e5eda45fa 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -87,6 +87,7 @@ private CompoundTag loadWorldInfo() throws IOException { private void updateControls() { CompoundTag dataTag = (CompoundTag) levelDat.get("Data"); + CompoundTag playerTag = world.getPlayerDat(); ScrollPane scrollPane = new ScrollPane(); scrollPane.setFitToHeight(true); @@ -320,7 +321,7 @@ private void updateControls() { rootPane.getChildren().addAll(ComponentList.createComponentListTitle(i18n("world.info")), worldInfo); } - if (dataTag.get("Player") instanceof CompoundTag playerTag) { + if (playerTag != null) { ComponentList playerInfo = new ComponentList(); var locationPane = new LineTextPane(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index 574096d796..71a9d86465 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -22,10 +22,7 @@ import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jetbrains.annotations.Nullable; import tech.minediamond.micanbt.NBT.NBT; -import tech.minediamond.micanbt.tag.ByteTag; -import tech.minediamond.micanbt.tag.CompoundTag; -import tech.minediamond.micanbt.tag.LongTag; -import tech.minediamond.micanbt.tag.StringTag; +import tech.minediamond.micanbt.tag.*; import java.io.IOException; import java.io.InputStream; @@ -36,6 +33,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.util.List; +import java.util.UUID; import java.util.stream.Stream; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -46,6 +44,7 @@ public final class World { private String fileName; private CompoundTag levelData; private CompoundTag worldGenSettingsDat; + private CompoundTag playerDat; private Image icon; private Path levelDataPath; @@ -95,6 +94,10 @@ public CompoundTag getWorldGenSettingsDat() { return worldGenSettingsDat; } + public CompoundTag getPlayerDat() { + return playerDat; + } + public long getLastPlayed() { return ((LongTag) levelData.at("Data/LastPlayed")).getClonedValue(); } @@ -240,6 +243,21 @@ private void loadAndCheckOtherDat() throws IOException { if (Files.exists(wgsd)) { this.worldGenSettingsDat = parseLevelDat(wgsd); } + + if (levelData.get("Player") instanceof CompoundTag playerTag) { + this.playerDat = playerTag; + } else if (levelData.at("Data/singleplayer_uuid") instanceof IntArrayTag uuidTag) { + int[] uuidValue = uuidTag.getClonedValue(); + if (uuidValue.length == 4) { + long mostSigBits = ((long) uuidValue[0] << 32) | (uuidValue[1] & 0xFFFFFFFFL); + long leastSigBits = ((long) uuidValue[2] << 32) | (uuidValue[3] & 0xFFFFFFFFL); + String playerUUID = new UUID(mostSigBits, leastSigBits).toString(); + Path playerDatPath = file.resolve("players\\data\\" + playerUUID + ".dat"); + if (Files.exists(playerDatPath)) { + this.playerDat = parseLevelDat(playerDatPath); + } + } + } } public void reloadLevelDat() throws IOException { From 96066e84c2dbac4c5751904ef5646d6c81286563 Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Wed, 4 Feb 2026 17:38:31 +0800 Subject: [PATCH 06/28] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0writeWorldDat()?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/WorldInfoPage.java | 2 +- .../java/org/jackhuang/hmcl/game/World.java | 27 ++++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index 6e5eda45fa..205e130294 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -513,7 +513,7 @@ private void bindTagAndTextField(FloatTag floatTag, JFXTextField jfxTextField) { private void saveLevelDat() { LOG.info("Saving level.dat of world " + world.getWorldName()); try { - this.world.writeLevelDat(levelDat); + this.world.writeWorldDat(); } catch (IOException e) { LOG.warning("Failed to save level.dat of world " + world.getWorldName(), e); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index 71a9d86465..8cf49b5b26 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -43,10 +43,12 @@ public final class World { private final Path file; private String fileName; private CompoundTag levelData; + private Path levelDataPath; private CompoundTag worldGenSettingsDat; + private Path worldGenSettingsDatPath; private CompoundTag playerDat; + private Path playerDatPath; private Image icon; - private Path levelDataPath; public World(Path file) throws IOException { this.file = file; @@ -239,9 +241,10 @@ private void loadAndCheckOtherDat() throws IOException { // // } - Path wgsd = file.resolve("data\\minecraft\\world_gen_settings.dat"); - if (Files.exists(wgsd)) { - this.worldGenSettingsDat = parseLevelDat(wgsd); + Path worldGenSettingsDatPath = file.resolve("data\\minecraft\\world_gen_settings.dat"); + if (Files.exists(worldGenSettingsDatPath)) { + this.worldGenSettingsDatPath = worldGenSettingsDatPath; + this.worldGenSettingsDat = parseLevelDat(worldGenSettingsDatPath); } if (levelData.get("Player") instanceof CompoundTag playerTag) { @@ -255,6 +258,7 @@ private void loadAndCheckOtherDat() throws IOException { Path playerDatPath = file.resolve("players\\data\\" + playerUUID + ".dat"); if (Files.exists(playerDatPath)) { this.playerDat = parseLevelDat(playerDatPath); + this.playerDatPath = playerDatPath; } } } @@ -371,6 +375,21 @@ public FileChannel lock() throws WorldLockedException { } } + public void writeWorldDat() throws IOException { + if (!Files.isDirectory(file)) + throw new IOException("Not a valid world directory"); + + NBT.write(this.levelData, getLevelDatFile()); + + if (worldGenSettingsDatPath != null) { + NBT.write(this.worldGenSettingsDat, worldGenSettingsDatPath); + } + + if (playerDatPath != null) { + NBT.write(this.playerDat, playerDatPath); + } + } + public void writeLevelDat(CompoundTag nbt) throws IOException { if (!Files.isDirectory(file)) throw new IOException("Not a valid world directory"); From 8968c670c388eff0328d6bb3d50e452f56c47886 Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Wed, 4 Feb 2026 18:14:09 +0800 Subject: [PATCH 07/28] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E6=B8=B8?= =?UTF-8?q?=E6=88=8F=E9=9A=BE=E5=BA=A6=E7=9A=84=E8=8E=B7=E5=8F=96=E6=96=B9?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/ui/versions/WorldInfoPage.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index 205e130294..d03d901de1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -56,6 +56,7 @@ import java.time.Instant; import java.util.Arrays; import java.util.Locale; +import java.util.Optional; import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -368,8 +369,16 @@ private void updateControls() { playerGameTypePane.setDisable(worldManagePage.isReadOnly()); playerGameTypePane.setItems(GameType.items); + ByteTag hardcoreTag = Optional.ofNullable(dataTag.get("hardcore")) + .filter(t -> t instanceof ByteTag) + .map(t -> (ByteTag) t) + .orElseGet(() -> { + if (dataTag.at("difficulty_settings/hardcore") instanceof ByteTag b) return b; + return null; + }); + if (playerTag.get("playerGameType") instanceof IntTag playerGameTypeTag - && dataTag.get("hardcore") instanceof ByteTag hardcoreTag) { + && hardcoreTag != null) { boolean isHardcore = hardcoreTag.getClonedValue() == 1; GameType gameType = GameType.of(playerGameTypeTag.getClonedValue(), isHardcore); if (gameType != null) { From f5a0eefcaca72be6c69233bf1bef1e972cb01a6b Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Wed, 4 Feb 2026 21:37:28 +0800 Subject: [PATCH 08/28] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/WorldInfoPage.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index d03d901de1..02d3a68fe0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -55,6 +55,7 @@ import java.time.Duration; import java.time.Instant; import java.util.Arrays; +import java.util.List; import java.util.Locale; import java.util.Optional; @@ -259,12 +260,11 @@ private void updateControls() { generateFeaturesButton.setTitle(i18n("world.info.generate_features")); generateFeaturesButton.setDisable(isReadOnly); - // generate_features was valid after 20w20a and MapFeatures was before that - if (dataTag.get("WorldGenSettings") instanceof CompoundTag worldGenSettings) { + if (dataTag.get("WorldGenSettings") instanceof CompoundTag worldGenSettings) { // Valid between 20w20a and 26.1 snapshot 6 bindTagAndToggleButton(worldGenSettings.get("generate_features"), generateFeaturesButton); - } else if (dataTag.get("MapFeatures") instanceof ByteTag mapFeatures) { + } else if (dataTag.get("MapFeatures") instanceof ByteTag mapFeatures) { // Valid before 20w20a bindTagAndToggleButton(mapFeatures, generateFeaturesButton); - } else if (world.getWorldGenSettingsDat() != null && world.getWorldGenSettingsDat().get("data") instanceof CompoundTag worldGenSettingsDataTag) { + } else if (world.getWorldGenSettingsDat() != null && world.getWorldGenSettingsDat().get("data") instanceof CompoundTag worldGenSettingsDataTag) { // Valid after 26.1 snapshot 6 bindTagAndToggleButton(worldGenSettingsDataTag.get("generate_structures"), generateFeaturesButton); } } @@ -275,7 +275,7 @@ private void updateControls() { difficultyButton.setDisable(worldManagePage.isReadOnly()); difficultyButton.setItems(Difficulty.items); - if (dataTag.get("Difficulty") instanceof ByteTag difficultyTag) { + if (dataTag.get("Difficulty") instanceof ByteTag difficultyTag) { //Valid before 26.1 snapshot 6 Difficulty difficulty = Difficulty.of(difficultyTag.getClonedValue()); if (difficulty != null) { difficultyButton.setValue(difficulty); @@ -288,13 +288,13 @@ private void updateControls() { } else { difficultyButton.setDisable(true); } - } else if (dataTag.at("difficulty_settings/difficulty") instanceof StringTag difficultyTag) { + } else if (dataTag.at("difficulty_settings/difficulty") instanceof StringTag difficultyTag) { // Valid after 26.1 snapshot 6 Difficulty difficulty = Difficulty.of(difficultyTag.getClonedValue()); if (difficulty != null) { difficultyButton.setValue(difficulty); difficultyButton.valueProperty().addListener((o, oldValue, newValue) -> { if (newValue != null) { - difficultyTag.setValue(newValue.name().toLowerCase(Locale.ROOT)); + difficultyTag.setValue(newValue.getTagStringValue()); saveLevelDat(); } }); @@ -632,6 +632,10 @@ static Difficulty of(String name) { return null; } + String getTagStringValue() { + return this.name().toLowerCase(Locale.ROOT); + } + @Override public String toString() { return i18n("world.info.difficulty." + name().toLowerCase(Locale.ROOT)); From cccca620d3fc3bd83786139db31d6de0f5d4d88a Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Wed, 4 Feb 2026 21:57:54 +0800 Subject: [PATCH 09/28] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/WorldInfoPage.java | 5 ++--- .../main/java/org/jackhuang/hmcl/game/World.java | 16 ++++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index 02d3a68fe0..b8316c2173 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -55,7 +55,6 @@ import java.time.Duration; import java.time.Instant; import java.util.Arrays; -import java.util.List; import java.util.Locale; import java.util.Optional; @@ -329,7 +328,7 @@ private void updateControls() { { locationPane.setTitle(i18n("world.info.player.location")); Dimension dimension = Dimension.of(playerTag.get("Dimension")); - if (dimension != null && playerTag.get("Pos") instanceof ListTag posTag) { + if (dimension != null && playerTag.get("Pos") instanceof ListTag posTag) { locationPane.setText(dimension.formatPosition(posTag)); } } @@ -572,7 +571,7 @@ static Dimension of(Tag tag) { } String formatPosition(Tag tag) { - if (tag instanceof ListTag listTag && listTag.size() == 3) { + if (tag instanceof ListTag listTag && listTag.size() == 3) { Tag x = listTag.get(0); Tag y = listTag.get(1); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index 8cf49b5b26..3e81f8cd1b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -22,10 +22,13 @@ import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jetbrains.annotations.Nullable; import tech.minediamond.micanbt.NBT.NBT; +import tech.minediamond.micanbt.NBT.NBTWriter; import tech.minediamond.micanbt.tag.*; +import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; @@ -35,6 +38,7 @@ import java.util.List; import java.util.UUID; import java.util.stream.Stream; +import java.util.zip.GZIPOutputStream; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -247,7 +251,7 @@ private void loadAndCheckOtherDat() throws IOException { this.worldGenSettingsDat = parseLevelDat(worldGenSettingsDatPath); } - if (levelData.get("Player") instanceof CompoundTag playerTag) { + if (levelData.at("Data/Player") instanceof CompoundTag playerTag) { this.playerDat = playerTag; } else if (levelData.at("Data/singleplayer_uuid") instanceof IntArrayTag uuidTag) { int[] uuidValue = uuidTag.getClonedValue(); @@ -394,11 +398,11 @@ public void writeLevelDat(CompoundTag nbt) throws IOException { if (!Files.isDirectory(file)) throw new IOException("Not a valid world directory"); -// FileUtils.saveSafely(getLevelDatFile(), os -> { -// try (OutputStream gos = new GZIPOutputStream(os)) { -// } -// }); - NBT.write(nbt, getLevelDatFile()); + FileUtils.saveSafely(getLevelDatFile(), os -> { + try (OutputStream bos = new BufferedOutputStream(os); OutputStream gos = new GZIPOutputStream(bos)) { + NBTWriter.writeTag(gos, nbt); + } + }); } private static CompoundTag parseLevelDat(Path path) throws IOException { From a64708b1702dc984a4ca113b74ff0cba31cff87c Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Thu, 5 Feb 2026 11:08:46 +0800 Subject: [PATCH 10/28] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/WorldManagePage.java | 4 +- .../java/org/jackhuang/hmcl/game/World.java | 39 +++++++++---------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java index 902116af0a..460c30ec61 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldManagePage.java @@ -75,7 +75,7 @@ public WorldManagePage(World world, Profile profile, String versionId) { updateSessionLockChannel(); try { - this.world.reloadLevelDat(); + this.world.reloadWorldData(); } catch (IOException e) { LOG.warning("Can not load world level.dat of world: " + this.world.getFile(), e); this.addEventHandler(Navigator.NavigationEvent.NAVIGATED, event -> closePageForLoadingFail()); @@ -100,7 +100,7 @@ public WorldManagePage(World world, Profile profile, String versionId) { public void refresh() { updateSessionLockChannel(); try { - world.reloadLevelDat(); + world.reloadWorldData(); } catch (IOException e) { LOG.warning("Can not load world level.dat of world: " + world.getFile(), e); closePageForLoadingFail(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index 3e81f8cd1b..50a555b2c8 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -96,11 +96,11 @@ public CompoundTag getLevelData() { return levelData; } - public CompoundTag getWorldGenSettingsDat() { + public @Nullable CompoundTag getWorldGenSettingsDat() { return worldGenSettingsDat; } - public CompoundTag getPlayerDat() { + public @Nullable CompoundTag getPlayerDat() { return playerDat; } @@ -170,8 +170,8 @@ private void loadFromDirectory() throws IOException { if (!Files.exists(levelDat)) { throw new IOException("Not a valid world directory since level.dat or special_level.dat cannot be found."); } - loadAndCheckLevelDat(levelDat); this.levelDataPath = levelDat; + loadAndCheckWorldData(); Path iconFile = file.resolve("icon.png"); if (Files.isRegularFile(iconFile)) { @@ -193,7 +193,7 @@ private void loadFromZipImpl(Path root) throws IOException { if (!Files.exists(levelDat)) { throw new IOException("Not a valid world zip file since level.dat or special_level.dat cannot be found."); } - loadAndCheckLevelDat(levelDat); + loadAndCheckLevelData(levelDat); Path iconFile = root.resolve("icon.png"); if (Files.isRegularFile(iconFile)) { @@ -225,8 +225,13 @@ private void loadFromZip() throws IOException { } } - private void loadAndCheckLevelDat(Path levelDat) throws IOException { - this.levelData = parseLevelDat(levelDat); + private void loadAndCheckWorldData() throws IOException { + loadAndCheckLevelData(levelDataPath); + loadOtherData(); + } + + private void loadAndCheckLevelData(Path levelDat) throws IOException { + this.levelData = NBT.read(levelDat); CompoundTag data = (CompoundTag) levelData.get("Data"); if (data == null) throw new IOException("level.dat missing Data"); @@ -236,19 +241,17 @@ private void loadAndCheckLevelDat(Path levelDat) throws IOException { if (!(data.get("LastPlayed") instanceof LongTag)) throw new IOException("level.dat missing LastPlayed"); - - loadAndCheckOtherDat(); } - private void loadAndCheckOtherDat() throws IOException { + private void loadOtherData() throws IOException { // if (getGameVersion().isAtLeast()) { // // } - Path worldGenSettingsDatPath = file.resolve("data\\minecraft\\world_gen_settings.dat"); + Path worldGenSettingsDatPath = file.resolve("data/minecraft/world_gen_settings.dat"); if (Files.exists(worldGenSettingsDatPath)) { this.worldGenSettingsDatPath = worldGenSettingsDatPath; - this.worldGenSettingsDat = parseLevelDat(worldGenSettingsDatPath); + this.worldGenSettingsDat = NBT.read(worldGenSettingsDatPath); } if (levelData.at("Data/Player") instanceof CompoundTag playerTag) { @@ -259,19 +262,17 @@ private void loadAndCheckOtherDat() throws IOException { long mostSigBits = ((long) uuidValue[0] << 32) | (uuidValue[1] & 0xFFFFFFFFL); long leastSigBits = ((long) uuidValue[2] << 32) | (uuidValue[3] & 0xFFFFFFFFL); String playerUUID = new UUID(mostSigBits, leastSigBits).toString(); - Path playerDatPath = file.resolve("players\\data\\" + playerUUID + ".dat"); + Path playerDatPath = file.resolve("players/data/" + playerUUID + ".dat"); if (Files.exists(playerDatPath)) { - this.playerDat = parseLevelDat(playerDatPath); + this.playerDat = NBT.read(playerDatPath); this.playerDatPath = playerDatPath; } } } } - public void reloadLevelDat() throws IOException { - if (levelDataPath != null) { - loadAndCheckLevelDat(this.levelDataPath); - } + public void reloadWorldData() throws IOException { + loadAndCheckWorldData(); } // The rename method is used to rename temporary world object during installation and copying, @@ -405,10 +406,6 @@ public void writeLevelDat(CompoundTag nbt) throws IOException { }); } - private static CompoundTag parseLevelDat(Path path) throws IOException { - return NBT.read(path); - } - private static boolean isLocked(Path sessionLockFile) { try (FileChannel fileChannel = FileChannel.open(sessionLockFile, StandardOpenOption.WRITE)) { return fileChannel.tryLock() == null; From 6afed3e478fb9083c08d19289b857572b0e4d4c9 Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Fri, 6 Feb 2026 14:34:48 +0800 Subject: [PATCH 11/28] feat: jump to micanbt version 0.3.1 --- .../hmcl/ui/versions/WorldInfoPage.java | 6 ++-- .../java/org/jackhuang/hmcl/game/World.java | 28 ++++++++----------- gradle/libs.versions.toml | 2 +- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index b8316c2173..c72fdfc096 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -287,7 +287,7 @@ private void updateControls() { } else { difficultyButton.setDisable(true); } - } else if (dataTag.at("difficulty_settings/difficulty") instanceof StringTag difficultyTag) { // Valid after 26.1 snapshot 6 + } else if (dataTag.at("difficulty_settings.difficulty") instanceof StringTag difficultyTag) { // Valid after 26.1 snapshot 6 Difficulty difficulty = Difficulty.of(difficultyTag.getClonedValue()); if (difficulty != null) { difficultyButton.setValue(difficulty); @@ -310,7 +310,7 @@ private void updateControls() { if (dataTag.get("DifficultyLocked") instanceof ByteTag difficultyLockedTag) { bindTagAndToggleButton(difficultyLockedTag, difficultyLockPane); } else { - bindTagAndToggleButton(dataTag.at("difficulty_settings/locked"), difficultyLockPane); + bindTagAndToggleButton(dataTag.at("difficulty_settings.locked"), difficultyLockPane); } } @@ -372,7 +372,7 @@ private void updateControls() { .filter(t -> t instanceof ByteTag) .map(t -> (ByteTag) t) .orElseGet(() -> { - if (dataTag.at("difficulty_settings/hardcore") instanceof ByteTag b) return b; + if (dataTag.at("difficulty_settings.hardcore") instanceof ByteTag b) return b; return null; }); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index 50a555b2c8..931614c43c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -74,11 +74,11 @@ public String getFileName() { } public String getWorldName() { - return ((StringTag) levelData.at("Data/LevelName")).getClonedValue(); + return ((StringTag) levelData.at("Data.LevelName")).getClonedValue(); } public void setWorldName(String worldName) throws IOException { - if (levelData.at("Data/LevelName") instanceof StringTag levelNameTag) { + if (levelData.at("Data.LevelName") instanceof StringTag levelNameTag) { levelNameTag.setValue(worldName); writeLevelDat(levelData); } @@ -105,33 +105,33 @@ public CompoundTag getLevelData() { } public long getLastPlayed() { - return ((LongTag) levelData.at("Data/LastPlayed")).getClonedValue(); + return ((LongTag) levelData.at("Data.LastPlayed")).getClonedValue(); } public @Nullable GameVersionNumber getGameVersion() { - if (levelData.at("Data/Version/Name") instanceof StringTag nameTag) { + if (levelData.at("Data.Version.Name") instanceof StringTag nameTag) { return GameVersionNumber.asGameVersion(nameTag.getClonedValue()); } return null; } public @Nullable Long getSeed() { - if (levelData.at("Data/WorldGenSettings/seed") instanceof LongTag seedTag) { //Valid after 1.16 + if (levelData.at("Data.WorldGenSettings.seed") instanceof LongTag seedTag) { //Valid after 1.16 return seedTag.getClonedValue(); - } else if (levelData.get("Data/RandomSeed") instanceof LongTag seedTag) { //Valid before 1.16 + } else if (levelData.get("Data.RandomSeed") instanceof LongTag seedTag) { //Valid before 1.16 return seedTag.getClonedValue(); - } else if (worldGenSettingsDat != null && worldGenSettingsDat.at("data/seed") instanceof LongTag seedTag) { //Valid after 26.1-snapshot-6 + } else if (worldGenSettingsDat != null && worldGenSettingsDat.at("data.seed") instanceof LongTag seedTag) { //Valid after 26.1-snapshot-6 return seedTag.getClonedValue(); } return null; } public boolean isLargeBiomes() { - if (levelData.at("Data/generatorName") instanceof StringTag generatorNameTag) { //Valid before 1.16 + if (levelData.at("Data.generatorName") instanceof StringTag generatorNameTag) { //Valid before 1.16 return "largeBiomes".equals(generatorNameTag.getClonedValue()); } else { - if (levelData.at("Data/WorldGenSettings/dimensions/minecraft:overworld/generator") instanceof CompoundTag generatorTag) { - if (generatorTag.at("biome_source/large_biomes") instanceof ByteTag largeBiomesTag) { //Valid between 1.16 and 1.16.2 + if (levelData.at("Data.WorldGenSettings.dimensions.minecraft:overworld.generator") instanceof CompoundTag generatorTag) { + if (generatorTag.at("biome_source.large_biomes") instanceof ByteTag largeBiomesTag) { //Valid between 1.16 and 1.16.2 return largeBiomesTag.getClonedValue() == (byte) 1; } else if (generatorTag.get("settings") instanceof StringTag settingsTag) { //Valid after 1.16.2 return "minecraft:large_biomes".equals(settingsTag.getClonedValue()); @@ -244,19 +244,15 @@ private void loadAndCheckLevelData(Path levelDat) throws IOException { } private void loadOtherData() throws IOException { -// if (getGameVersion().isAtLeast()) { -// -// } - Path worldGenSettingsDatPath = file.resolve("data/minecraft/world_gen_settings.dat"); if (Files.exists(worldGenSettingsDatPath)) { this.worldGenSettingsDatPath = worldGenSettingsDatPath; this.worldGenSettingsDat = NBT.read(worldGenSettingsDatPath); } - if (levelData.at("Data/Player") instanceof CompoundTag playerTag) { + if (levelData.at("Data.Player") instanceof CompoundTag playerTag) { this.playerDat = playerTag; - } else if (levelData.at("Data/singleplayer_uuid") instanceof IntArrayTag uuidTag) { + } else if (levelData.at("Data.singleplayer_uuid") instanceof IntArrayTag uuidTag) { int[] uuidValue = uuidTag.getClonedValue(); if (uuidValue.length == 4) { long mostSigBits = ((long) uuidValue[0] << 32) | (uuidValue[1] & 0xFFFFFFFFL); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4726e831c1..9063ceae2d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,7 +19,7 @@ java-info = "1.0" authlib-injector = "1.2.7" monet-fx = "0.4.0" terracotta = "0.4.1" -micanbt = "0.2.0" +micanbt = "0.3.1" # testing junit = "6.0.1" From 85fd2ad3387092668729202db64ab6d5e5eaf638 Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Fri, 6 Feb 2026 14:57:39 +0800 Subject: [PATCH 12/28] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=96=B0?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=94=BE=E5=A4=A7=E5=8C=96=E4=B8=96=E7=95=8C?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index 931614c43c..be37eef133 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -129,6 +129,10 @@ public long getLastPlayed() { public boolean isLargeBiomes() { if (levelData.at("Data.generatorName") instanceof StringTag generatorNameTag) { //Valid before 1.16 return "largeBiomes".equals(generatorNameTag.getClonedValue()); + } else if (worldGenSettingsDat != null) { + if (worldGenSettingsDat.at("data.dimensions.minecraft:overworld.generator.settings") instanceof StringTag settingsTag) { + return "minecraft:large_biomes".equals(settingsTag.getClonedValue()); + } } else { if (levelData.at("Data.WorldGenSettings.dimensions.minecraft:overworld.generator") instanceof CompoundTag generatorTag) { if (generatorTag.at("biome_source.large_biomes") instanceof ByteTag largeBiomesTag) { //Valid between 1.16 and 1.16.2 @@ -137,8 +141,8 @@ public boolean isLargeBiomes() { return "minecraft:large_biomes".equals(settingsTag.getClonedValue()); } } - return false; } + return false; } public Image getIcon() { From b49f239bd591bc60ac0a3a325b26f03876bd0183 Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Fri, 6 Feb 2026 16:03:27 +0800 Subject: [PATCH 13/28] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index be37eef133..9cbafb3c4e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -130,14 +130,14 @@ public boolean isLargeBiomes() { if (levelData.at("Data.generatorName") instanceof StringTag generatorNameTag) { //Valid before 1.16 return "largeBiomes".equals(generatorNameTag.getClonedValue()); } else if (worldGenSettingsDat != null) { - if (worldGenSettingsDat.at("data.dimensions.minecraft:overworld.generator.settings") instanceof StringTag settingsTag) { + if (worldGenSettingsDat.at("data.dimensions.minecraft:overworld.generator.settings") instanceof StringTag settingsTag) { //Valid after 26.1-snapshot-6 return "minecraft:large_biomes".equals(settingsTag.getClonedValue()); } } else { if (levelData.at("Data.WorldGenSettings.dimensions.minecraft:overworld.generator") instanceof CompoundTag generatorTag) { if (generatorTag.at("biome_source.large_biomes") instanceof ByteTag largeBiomesTag) { //Valid between 1.16 and 1.16.2 return largeBiomesTag.getClonedValue() == (byte) 1; - } else if (generatorTag.get("settings") instanceof StringTag settingsTag) { //Valid after 1.16.2 + } else if (generatorTag.get("settings") instanceof StringTag settingsTag) { //Valid between 1.16.2 and 26.1-snapshot-6 return "minecraft:large_biomes".equals(settingsTag.getClonedValue()); } } From 3c8e445406ecfde54049553e59872d0a4dea67d6 Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Fri, 6 Feb 2026 16:25:14 +0800 Subject: [PATCH 14/28] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=89=88?= =?UTF-8?q?=E6=9D=83=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HMCL/src/main/resources/assets/about/deps.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/HMCL/src/main/resources/assets/about/deps.json b/HMCL/src/main/resources/assets/about/deps.json index d72871ec0c..bfd8cec074 100644 --- a/HMCL/src/main/resources/assets/about/deps.json +++ b/HMCL/src/main/resources/assets/about/deps.json @@ -83,5 +83,10 @@ "title": "MonetFX", "subtitle": "Copyright © 2025 Glavo.\nLicensed under the Apache 2.0 License.", "externalLink": "https://github.com/Glavo/MonetFX" + }, + { + "title" : "MicaNBT", + "subtitle" : "Copyright © 2013-2021 Steveice10, 2026 Mine-diamond.\nLicensed under the MIT License.", + "externalLink" : "https://github.com/Mine-diamond/MicaNBT" } ] \ No newline at end of file From c7fb88a3a5d3ec7f570976465c963df72f1f8b81 Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Fri, 6 Feb 2026 19:42:24 +0800 Subject: [PATCH 15/28] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/WorldInfoPage.java | 26 +++++++++++-------- .../java/org/jackhuang/hmcl/game/World.java | 12 +++------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index c72fdfc096..af3fd712f1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -263,8 +263,10 @@ private void updateControls() { bindTagAndToggleButton(worldGenSettings.get("generate_features"), generateFeaturesButton); } else if (dataTag.get("MapFeatures") instanceof ByteTag mapFeatures) { // Valid before 20w20a bindTagAndToggleButton(mapFeatures, generateFeaturesButton); - } else if (world.getWorldGenSettingsDat() != null && world.getWorldGenSettingsDat().get("data") instanceof CompoundTag worldGenSettingsDataTag) { // Valid after 26.1 snapshot 6 - bindTagAndToggleButton(worldGenSettingsDataTag.get("generate_structures"), generateFeaturesButton); + } else if (world.getWorldGenSettingsDat() != null && world.getWorldGenSettingsDat().at("data.generate_structures") instanceof ByteTag generateStructures) { // Valid after 26.1 snapshot 6 + bindTagAndToggleButton(generateStructures, generateFeaturesButton); + } else { + generateFeaturesButton.setDisable(true); } } @@ -281,7 +283,7 @@ private void updateControls() { difficultyButton.valueProperty().addListener((o, oldValue, newValue) -> { if (newValue != null) { difficultyTag.setValue((byte) newValue.ordinal()); - saveLevelDat(); + saveWorldData(); } }); } else { @@ -294,7 +296,7 @@ private void updateControls() { difficultyButton.valueProperty().addListener((o, oldValue, newValue) -> { if (newValue != null) { difficultyTag.setValue(newValue.getTagStringValue()); - saveLevelDat(); + saveWorldData(); } }); } @@ -307,10 +309,12 @@ private void updateControls() { { difficultyLockPane.setTitle(i18n("world.info.difficulty_lock")); difficultyLockPane.setDisable(isReadOnly); - if (dataTag.get("DifficultyLocked") instanceof ByteTag difficultyLockedTag) { + if (dataTag.get("DifficultyLocked") instanceof ByteTag difficultyLockedTag) { // Valid before 26.1 snapshot 6 bindTagAndToggleButton(difficultyLockedTag, difficultyLockPane); + } else if (dataTag.at("difficulty_settings.locked") instanceof ByteTag LockedTag) { // Valid after 26.1 snapshot 6 + bindTagAndToggleButton(LockedTag, difficultyLockPane); } else { - bindTagAndToggleButton(dataTag.at("difficulty_settings.locked"), difficultyLockPane); + difficultyLockPane.setDisable(true); } } @@ -391,7 +395,7 @@ private void updateControls() { playerGameTypeTag.setValue(newValue.ordinal()); hardcoreTag.setValue((byte) 0); } - saveLevelDat(); + saveWorldData(); } }); } else { @@ -462,7 +466,7 @@ private void bindTagAndToggleButton(Tag tag, LineToggleButton toggleButton) { toggleButton.selectedProperty().addListener((o, oldValue, newValue) -> { try { byteTag.setValue((byte) (newValue ? 1 : 0)); - saveLevelDat(); + saveWorldData(); } catch (Exception e) { toggleButton.setSelected(oldValue); LOG.warning("Exception happened when saving level.dat", e); @@ -485,7 +489,7 @@ private void bindTagAndTextField(IntTag intTag, JFXTextField jfxTextField) { Integer integer = Lang.toIntOrNull(newValue); if (integer != null) { intTag.setValue(integer); - saveLevelDat(); + saveWorldData(); } } catch (Exception e) { jfxTextField.setText(oldValue); @@ -506,7 +510,7 @@ private void bindTagAndTextField(FloatTag floatTag, JFXTextField jfxTextField) { Float floatValue = Lang.toFloatOrNull(newValue); if (floatValue != null) { floatTag.setValue(floatValue); - saveLevelDat(); + saveWorldData(); } } catch (Exception e) { jfxTextField.setText(oldValue); @@ -518,7 +522,7 @@ private void bindTagAndTextField(FloatTag floatTag, JFXTextField jfxTextField) { jfxTextField.setValidators(new DoubleValidator(i18n("input.number"), true)); } - private void saveLevelDat() { + private void saveWorldData() { LOG.info("Saving level.dat of world " + world.getWorldName()); try { this.world.writeWorldDat(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index 9cbafb3c4e..ecab63f3ff 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -133,14 +133,10 @@ public boolean isLargeBiomes() { if (worldGenSettingsDat.at("data.dimensions.minecraft:overworld.generator.settings") instanceof StringTag settingsTag) { //Valid after 26.1-snapshot-6 return "minecraft:large_biomes".equals(settingsTag.getClonedValue()); } - } else { - if (levelData.at("Data.WorldGenSettings.dimensions.minecraft:overworld.generator") instanceof CompoundTag generatorTag) { - if (generatorTag.at("biome_source.large_biomes") instanceof ByteTag largeBiomesTag) { //Valid between 1.16 and 1.16.2 - return largeBiomesTag.getClonedValue() == (byte) 1; - } else if (generatorTag.get("settings") instanceof StringTag settingsTag) { //Valid between 1.16.2 and 26.1-snapshot-6 - return "minecraft:large_biomes".equals(settingsTag.getClonedValue()); - } - } + } else if (levelData.at("Data.WorldGenSettings.dimensions.minecraft:overworld.generator.biome_source.large_biomes") instanceof ByteTag largeBiomesTag) { //Valid between 1.16 and 1.16.2 + return largeBiomesTag.getClonedValue() == (byte) 1; + } else if (levelData.at("Data.WorldGenSettings.dimensions.minecraft:overworld.generator.settings") instanceof StringTag settingsTag) { //Valid between 1.16.2 and 26.1-snapshot-6 + return "minecraft:large_biomes".equals(settingsTag.getClonedValue()); } return false; } From 8c8d2318f194675f962a0bbfe5ed85057831414f Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Fri, 6 Feb 2026 20:06:57 +0800 Subject: [PATCH 16/28] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/WorldInfoPage.java | 18 +++--- .../java/org/jackhuang/hmcl/game/World.java | 58 ++++++++++--------- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index af3fd712f1..892716f26a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -88,7 +88,7 @@ private CompoundTag loadWorldInfo() throws IOException { private void updateControls() { CompoundTag dataTag = (CompoundTag) levelDat.get("Data"); - CompoundTag playerTag = world.getPlayerDat(); + CompoundTag playerTag = world.getPlayerData(); ScrollPane scrollPane = new ScrollPane(); scrollPane.setFitToHeight(true); @@ -263,7 +263,7 @@ private void updateControls() { bindTagAndToggleButton(worldGenSettings.get("generate_features"), generateFeaturesButton); } else if (dataTag.get("MapFeatures") instanceof ByteTag mapFeatures) { // Valid before 20w20a bindTagAndToggleButton(mapFeatures, generateFeaturesButton); - } else if (world.getWorldGenSettingsDat() != null && world.getWorldGenSettingsDat().at("data.generate_structures") instanceof ByteTag generateStructures) { // Valid after 26.1 snapshot 6 + } else if (world.getWorldGenSettingsData() != null && world.getWorldGenSettingsData().at("data.generate_structures") instanceof ByteTag generateStructures) { // Valid after 26.1 snapshot 6 bindTagAndToggleButton(generateStructures, generateFeaturesButton); } else { generateFeaturesButton.setDisable(true); @@ -469,7 +469,7 @@ private void bindTagAndToggleButton(Tag tag, LineToggleButton toggleButton) { saveWorldData(); } catch (Exception e) { toggleButton.setSelected(oldValue); - LOG.warning("Exception happened when saving level.dat", e); + LOG.warning("Exception happened when saving world info", e); } }); } else { @@ -493,7 +493,7 @@ private void bindTagAndTextField(IntTag intTag, JFXTextField jfxTextField) { } } catch (Exception e) { jfxTextField.setText(oldValue); - LOG.warning("Exception happened when saving level.dat", e); + LOG.warning("Exception happened when saving world info", e); } } }); @@ -514,7 +514,7 @@ private void bindTagAndTextField(FloatTag floatTag, JFXTextField jfxTextField) { } } catch (Exception e) { jfxTextField.setText(oldValue); - LOG.warning("Exception happened when saving level.dat", e); + LOG.warning("Exception happened when saving world info", e); } } }); @@ -523,11 +523,11 @@ private void bindTagAndTextField(FloatTag floatTag, JFXTextField jfxTextField) { } private void saveWorldData() { - LOG.info("Saving level.dat of world " + world.getWorldName()); + LOG.info("Saving world info of world " + world.getWorldName()); try { - this.world.writeWorldDat(); + this.world.writeWorldData(); } catch (IOException e) { - LOG.warning("Failed to save level.dat of world " + world.getWorldName(), e); + LOG.warning("Failed to save world info of world " + world.getWorldName(), e); } } @@ -542,7 +542,7 @@ public void refresh() { updateControls(); setLoading(false); } else { - LOG.warning("Failed to load level.dat", exception); + LOG.warning("Failed to load world info of world " + world.getWorldName(), exception); setFailedReason(i18n("world.info.failed")); } })).start(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index ecab63f3ff..92aed730dc 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -48,10 +48,10 @@ public final class World { private String fileName; private CompoundTag levelData; private Path levelDataPath; - private CompoundTag worldGenSettingsDat; - private Path worldGenSettingsDatPath; - private CompoundTag playerDat; - private Path playerDatPath; + private CompoundTag worldGenSettingsData; + private Path worldGenSettingsDataPath; + private CompoundTag playerData; + private Path playerDataPath; private Image icon; public World(Path file) throws IOException { @@ -80,11 +80,11 @@ public String getWorldName() { public void setWorldName(String worldName) throws IOException { if (levelData.at("Data.LevelName") instanceof StringTag levelNameTag) { levelNameTag.setValue(worldName); - writeLevelDat(levelData); + writeLevelDat(); } } - public Path getLevelDatFile() { + public Path getLevelDataFile() { return file.resolve("level.dat"); } @@ -96,12 +96,12 @@ public CompoundTag getLevelData() { return levelData; } - public @Nullable CompoundTag getWorldGenSettingsDat() { - return worldGenSettingsDat; + public @Nullable CompoundTag getWorldGenSettingsData() { + return worldGenSettingsData; } - public @Nullable CompoundTag getPlayerDat() { - return playerDat; + public @Nullable CompoundTag getPlayerData() { + return playerData; } public long getLastPlayed() { @@ -120,7 +120,7 @@ public long getLastPlayed() { return seedTag.getClonedValue(); } else if (levelData.get("Data.RandomSeed") instanceof LongTag seedTag) { //Valid before 1.16 return seedTag.getClonedValue(); - } else if (worldGenSettingsDat != null && worldGenSettingsDat.at("data.seed") instanceof LongTag seedTag) { //Valid after 26.1-snapshot-6 + } else if (worldGenSettingsData != null && worldGenSettingsData.at("data.seed") instanceof LongTag seedTag) { //Valid after 26.1-snapshot-6 return seedTag.getClonedValue(); } return null; @@ -129,8 +129,8 @@ public long getLastPlayed() { public boolean isLargeBiomes() { if (levelData.at("Data.generatorName") instanceof StringTag generatorNameTag) { //Valid before 1.16 return "largeBiomes".equals(generatorNameTag.getClonedValue()); - } else if (worldGenSettingsDat != null) { - if (worldGenSettingsDat.at("data.dimensions.minecraft:overworld.generator.settings") instanceof StringTag settingsTag) { //Valid after 26.1-snapshot-6 + } else if (worldGenSettingsData != null) { + if (worldGenSettingsData.at("data.dimensions.minecraft:overworld.generator.settings") instanceof StringTag settingsTag) { //Valid after 26.1-snapshot-6 return "minecraft:large_biomes".equals(settingsTag.getClonedValue()); } } else if (levelData.at("Data.WorldGenSettings.dimensions.minecraft:overworld.generator.biome_source.large_biomes") instanceof ByteTag largeBiomesTag) { //Valid between 1.16 and 1.16.2 @@ -246,12 +246,12 @@ private void loadAndCheckLevelData(Path levelDat) throws IOException { private void loadOtherData() throws IOException { Path worldGenSettingsDatPath = file.resolve("data/minecraft/world_gen_settings.dat"); if (Files.exists(worldGenSettingsDatPath)) { - this.worldGenSettingsDatPath = worldGenSettingsDatPath; - this.worldGenSettingsDat = NBT.read(worldGenSettingsDatPath); + this.worldGenSettingsDataPath = worldGenSettingsDatPath; + this.worldGenSettingsData = NBT.read(worldGenSettingsDatPath); } if (levelData.at("Data.Player") instanceof CompoundTag playerTag) { - this.playerDat = playerTag; + this.playerData = playerTag; } else if (levelData.at("Data.singleplayer_uuid") instanceof IntArrayTag uuidTag) { int[] uuidValue = uuidTag.getClonedValue(); if (uuidValue.length == 4) { @@ -260,8 +260,8 @@ private void loadOtherData() throws IOException { String playerUUID = new UUID(mostSigBits, leastSigBits).toString(); Path playerDatPath = file.resolve("players/data/" + playerUUID + ".dat"); if (Files.exists(playerDatPath)) { - this.playerDat = NBT.read(playerDatPath); - this.playerDatPath = playerDatPath; + this.playerData = NBT.read(playerDatPath); + this.playerDataPath = playerDatPath; } } } @@ -280,7 +280,7 @@ public void rename(String newName) throws IOException { // Change the name recorded in level.dat CompoundTag data = (CompoundTag) levelData.get("Data"); data.put(new StringTag("LevelName", newName)); - writeLevelDat(levelData); + writeLevelDat(); // then change the folder's name Files.move(file, file.resolveSibling(newName)); @@ -376,26 +376,30 @@ public FileChannel lock() throws WorldLockedException { } } - public void writeWorldDat() throws IOException { + public void writeWorldData() throws IOException { if (!Files.isDirectory(file)) throw new IOException("Not a valid world directory"); - NBT.write(this.levelData, getLevelDatFile()); + writeLevelDat(); - if (worldGenSettingsDatPath != null) { - NBT.write(this.worldGenSettingsDat, worldGenSettingsDatPath); + if (worldGenSettingsDataPath != null) { + writeTag(worldGenSettingsData, worldGenSettingsDataPath); } - if (playerDatPath != null) { - NBT.write(this.playerDat, playerDatPath); + if (playerDataPath != null) { + writeTag(playerData, playerDataPath); } } - public void writeLevelDat(CompoundTag nbt) throws IOException { + public void writeLevelDat() throws IOException { + writeTag(levelData, getLevelDataFile()); + } + + public void writeTag(CompoundTag nbt, Path path) throws IOException { if (!Files.isDirectory(file)) throw new IOException("Not a valid world directory"); - FileUtils.saveSafely(getLevelDatFile(), os -> { + FileUtils.saveSafely(path, os -> { try (OutputStream bos = new BufferedOutputStream(os); OutputStream gos = new GZIPOutputStream(bos)) { NBTWriter.writeTag(gos, nbt); } From 39c84cf71b3fb43d537b1fcdbf5a2c19e4c5646f Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Fri, 6 Feb 2026 21:23:18 +0800 Subject: [PATCH 17/28] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index 92aed730dc..13727f3f19 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -118,7 +118,7 @@ public long getLastPlayed() { public @Nullable Long getSeed() { if (levelData.at("Data.WorldGenSettings.seed") instanceof LongTag seedTag) { //Valid after 1.16 return seedTag.getClonedValue(); - } else if (levelData.get("Data.RandomSeed") instanceof LongTag seedTag) { //Valid before 1.16 + } else if (levelData.at("Data.RandomSeed") instanceof LongTag seedTag) { //Valid before 1.16 return seedTag.getClonedValue(); } else if (worldGenSettingsData != null && worldGenSettingsData.at("data.seed") instanceof LongTag seedTag) { //Valid after 26.1-snapshot-6 return seedTag.getClonedValue(); From d058eda1f0b7b90a174208d4cc1be47490eb94ce Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Fri, 6 Feb 2026 22:48:02 +0800 Subject: [PATCH 18/28] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/WorldInfoPage.java | 21 ++++++++++---- .../java/org/jackhuang/hmcl/game/World.java | 28 +++++++++++++------ 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index 892716f26a..52934b3e46 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -259,11 +259,16 @@ private void updateControls() { generateFeaturesButton.setTitle(i18n("world.info.generate_features")); generateFeaturesButton.setDisable(isReadOnly); - if (dataTag.get("WorldGenSettings") instanceof CompoundTag worldGenSettings) { // Valid between 20w20a and 26.1 snapshot 6 + // Valid between 20w20a and 26.1 snapshot 6 + if (dataTag.get("WorldGenSettings") instanceof CompoundTag worldGenSettings) { bindTagAndToggleButton(worldGenSettings.get("generate_features"), generateFeaturesButton); - } else if (dataTag.get("MapFeatures") instanceof ByteTag mapFeatures) { // Valid before 20w20a + } + // Valid before 20w20a + else if (dataTag.get("MapFeatures") instanceof ByteTag mapFeatures) { bindTagAndToggleButton(mapFeatures, generateFeaturesButton); - } else if (world.getWorldGenSettingsData() != null && world.getWorldGenSettingsData().at("data.generate_structures") instanceof ByteTag generateStructures) { // Valid after 26.1 snapshot 6 + } + // Valid after 26.1 snapshot 6 + else if (world.getWorldGenSettingsData() != null && world.getWorldGenSettingsData().at("data.generate_structures") instanceof ByteTag generateStructures) { bindTagAndToggleButton(generateStructures, generateFeaturesButton); } else { generateFeaturesButton.setDisable(true); @@ -309,9 +314,12 @@ private void updateControls() { { difficultyLockPane.setTitle(i18n("world.info.difficulty_lock")); difficultyLockPane.setDisable(isReadOnly); - if (dataTag.get("DifficultyLocked") instanceof ByteTag difficultyLockedTag) { // Valid before 26.1 snapshot 6 + // Valid before 26.1-snapshot-6 + if (dataTag.get("DifficultyLocked") instanceof ByteTag difficultyLockedTag) { bindTagAndToggleButton(difficultyLockedTag, difficultyLockPane); - } else if (dataTag.at("difficulty_settings.locked") instanceof ByteTag LockedTag) { // Valid after 26.1 snapshot 6 + } + // Valid after 26.1-snapshot-6 + else if (dataTag.at("difficulty_settings.locked") instanceof ByteTag LockedTag) { bindTagAndToggleButton(LockedTag, difficultyLockPane); } else { difficultyLockPane.setDisable(true); @@ -372,9 +380,10 @@ private void updateControls() { playerGameTypePane.setDisable(worldManagePage.isReadOnly()); playerGameTypePane.setItems(GameType.items); - ByteTag hardcoreTag = Optional.ofNullable(dataTag.get("hardcore")) + ByteTag hardcoreTag = Optional.ofNullable(dataTag.get("hardcore")) // Valid before 26.1-snapshot-6 .filter(t -> t instanceof ByteTag) .map(t -> (ByteTag) t) + // Valid after 26.1-snapshot-6 .orElseGet(() -> { if (dataTag.at("difficulty_settings.hardcore") instanceof ByteTag b) return b; return null; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index 13727f3f19..e726d6667e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -116,26 +116,38 @@ public long getLastPlayed() { } public @Nullable Long getSeed() { - if (levelData.at("Data.WorldGenSettings.seed") instanceof LongTag seedTag) { //Valid after 1.16 + // Valid between 1.16 and 26.1-snapshot-6 + if (levelData.at("Data.WorldGenSettings.seed") instanceof LongTag seedTag) { return seedTag.getClonedValue(); - } else if (levelData.at("Data.RandomSeed") instanceof LongTag seedTag) { //Valid before 1.16 + } + // Valid before 1.16 + else if (levelData.at("Data.RandomSeed") instanceof LongTag seedTag) { return seedTag.getClonedValue(); - } else if (worldGenSettingsData != null && worldGenSettingsData.at("data.seed") instanceof LongTag seedTag) { //Valid after 26.1-snapshot-6 + } + // Valid after 26.1-snapshot-6 + else if (worldGenSettingsData != null && worldGenSettingsData.at("data.seed") instanceof LongTag seedTag) { return seedTag.getClonedValue(); } return null; } public boolean isLargeBiomes() { - if (levelData.at("Data.generatorName") instanceof StringTag generatorNameTag) { //Valid before 1.16 + // Valid before 1.16 + if (levelData.at("Data.generatorName") instanceof StringTag generatorNameTag) { return "largeBiomes".equals(generatorNameTag.getClonedValue()); - } else if (worldGenSettingsData != null) { - if (worldGenSettingsData.at("data.dimensions.minecraft:overworld.generator.settings") instanceof StringTag settingsTag) { //Valid after 26.1-snapshot-6 + } + // Valid after 26.1-snapshot-6 + else if (worldGenSettingsData != null) { + if (worldGenSettingsData.at("data.dimensions.minecraft:overworld.generator.settings") instanceof StringTag settingsTag) { return "minecraft:large_biomes".equals(settingsTag.getClonedValue()); } - } else if (levelData.at("Data.WorldGenSettings.dimensions.minecraft:overworld.generator.biome_source.large_biomes") instanceof ByteTag largeBiomesTag) { //Valid between 1.16 and 1.16.2 + } + // Valid between 1.16 and 1.16.2 + else if (levelData.at("Data.WorldGenSettings.dimensions.minecraft:overworld.generator.biome_source.large_biomes") instanceof ByteTag largeBiomesTag) { return largeBiomesTag.getClonedValue() == (byte) 1; - } else if (levelData.at("Data.WorldGenSettings.dimensions.minecraft:overworld.generator.settings") instanceof StringTag settingsTag) { //Valid between 1.16.2 and 26.1-snapshot-6 + } + // Valid between 1.16.2 and 26.1-snapshot-6 + else if (levelData.at("Data.WorldGenSettings.dimensions.minecraft:overworld.generator.settings") instanceof StringTag settingsTag) { return "minecraft:large_biomes".equals(settingsTag.getClonedValue()); } return false; From 2084b8705efa3defa70c44c7e5fa717a3508dc16 Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Sat, 7 Feb 2026 21:03:28 +0800 Subject: [PATCH 19/28] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=EF=BC=8C=E4=BC=98=E5=8C=96=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/WorldInfoPage.java | 96 +++++++++---------- .../java/org/jackhuang/hmcl/game/World.java | 11 ++- 2 files changed, 53 insertions(+), 54 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index 52934b3e46..3d3b1feead 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -215,12 +215,14 @@ private void updateControls() { worldSpawnPoint.setTitle(i18n("world.info.spawn")); String value; - if (dataTag.get("spawn") instanceof CompoundTag spawnTag && spawnTag.get("pos") instanceof IntArrayTag posTag) { - value = Dimension.of(spawnTag.get("dimension") instanceof StringTag dimensionTag - ? dimensionTag - : new StringTag("SpawnDimension", "minecraft:overworld")) - .formatPosition(posTag); - } else if (dataTag.get("SpawnX") instanceof IntTag intX + // Valid after 1.21.9-pre1 + if (dataTag.at("spawn.pos") instanceof IntArrayTag posTag) { + value = dataTag.at("spawn.dimension") instanceof StringTag dimensionTag + ? Dimension.of(dimensionTag).formatPosition(posTag) + : Dimension.OVERWORLD.formatPosition(posTag); + } + // Valid before 1.21.9-pre1 + else if (dataTag.get("SpawnX") instanceof IntTag intX && dataTag.get("SpawnY") instanceof IntTag intY && dataTag.get("SpawnZ") instanceof IntTag intZ) { value = Dimension.OVERWORLD.formatPosition(intX.getClonedValue(), intY.getClonedValue(), intZ.getClonedValue()); @@ -259,15 +261,15 @@ private void updateControls() { generateFeaturesButton.setTitle(i18n("world.info.generate_features")); generateFeaturesButton.setDisable(isReadOnly); - // Valid between 20w20a and 26.1 snapshot 6 - if (dataTag.get("WorldGenSettings") instanceof CompoundTag worldGenSettings) { - bindTagAndToggleButton(worldGenSettings.get("generate_features"), generateFeaturesButton); + // Valid between (1.16)20w20a and 26.1-snapshot-6 + if (dataTag.at("WorldGenSettings.generate_features") instanceof ByteTag generateFeaturesTag) { + bindTagAndToggleButton(generateFeaturesTag, generateFeaturesButton); } - // Valid before 20w20a + // Valid before (1.16)20w20a else if (dataTag.get("MapFeatures") instanceof ByteTag mapFeatures) { bindTagAndToggleButton(mapFeatures, generateFeaturesButton); } - // Valid after 26.1 snapshot 6 + // Valid after 26.1-snapshot-6 else if (world.getWorldGenSettingsData() != null && world.getWorldGenSettingsData().at("data.generate_structures") instanceof ByteTag generateStructures) { bindTagAndToggleButton(generateStructures, generateFeaturesButton); } else { @@ -281,30 +283,28 @@ else if (world.getWorldGenSettingsData() != null && world.getWorldGenSettingsDat difficultyButton.setDisable(worldManagePage.isReadOnly()); difficultyButton.setItems(Difficulty.items); - if (dataTag.get("Difficulty") instanceof ByteTag difficultyTag) { //Valid before 26.1 snapshot 6 - Difficulty difficulty = Difficulty.of(difficultyTag.getClonedValue()); - if (difficulty != null) { - difficultyButton.setValue(difficulty); - difficultyButton.valueProperty().addListener((o, oldValue, newValue) -> { - if (newValue != null) { - difficultyTag.setValue((byte) newValue.ordinal()); - saveWorldData(); - } - }); - } else { - difficultyButton.setDisable(true); - } - } else if (dataTag.at("difficulty_settings.difficulty") instanceof StringTag difficultyTag) { // Valid after 26.1 snapshot 6 - Difficulty difficulty = Difficulty.of(difficultyTag.getClonedValue()); - if (difficulty != null) { - difficultyButton.setValue(difficulty); - difficultyButton.valueProperty().addListener((o, oldValue, newValue) -> { - if (newValue != null) { - difficultyTag.setValue(newValue.getTagStringValue()); - saveWorldData(); - } - }); - } + Difficulty difficulty; + // Valid before 26.1-snapshot-6 + if (dataTag.get("Difficulty") instanceof ByteTag difficultyTag + && (difficulty = Difficulty.of(difficultyTag.getClonedValue())) != null) { + difficultyButton.setValue(difficulty); + difficultyButton.valueProperty().addListener((o, oldValue, newValue) -> { + if (newValue != null) { + difficultyTag.setValue((byte) newValue.ordinal()); + saveWorldData(); + } + }); + } + // Valid after 26.1-snapshot-6 + else if (dataTag.at("difficulty_settings.difficulty") instanceof StringTag difficultyTag + && (difficulty = Difficulty.of(difficultyTag.getClonedValue())) != null) { + difficultyButton.setValue(difficulty); + difficultyButton.valueProperty().addListener((o, oldValue, newValue) -> { + if (newValue != null) { + difficultyTag.setValue(newValue.getTagStringValue()); + saveWorldData(); + } + }); } else { difficultyButton.setDisable(true); } @@ -360,15 +360,17 @@ else if (dataTag.at("difficulty_settings.locked") instanceof ByteTag LockedTag) var spawnPane = new LineTextPane(); { spawnPane.setTitle(i18n("world.info.player.spawn")); - if (playerTag.get("respawn") instanceof CompoundTag respawnTag - && respawnTag.get("dimension") instanceof StringTag dimensionTag - && respawnTag.get("pos") instanceof IntArrayTag intArrayTag - && intArrayTag.length() >= 3) { // Valid after 25w07a + // Valid after 1.21.5(25w07a) + if (levelDat.at("respawn.dimension") instanceof StringTag dimensionTag + && levelDat.at("respawn.pos") instanceof IntArrayTag intArrayTag + && intArrayTag.length() == 3) { spawnPane.setText(Dimension.of(dimensionTag).formatPosition(intArrayTag)); - } else if (playerTag.get("SpawnX") instanceof IntTag intX + } + // Valid before 1.21.5(25w07a) + else if (playerTag.get("SpawnX") instanceof IntTag intX && playerTag.get("SpawnY") instanceof IntTag intY - && playerTag.get("SpawnZ") instanceof IntTag intZ) { // Valid before 25w07a - // SpawnDimension tag is valid after 20w12a. Prior to this version, the game did not record the respawn point dimension and respawned in the Overworld. + && playerTag.get("SpawnZ") instanceof IntTag intZ) { + // SpawnDimension tag is valid after 1.16(20w12a). Prior to this version, the game did not record the respawn point dimension and respawned in the Overworld. spawnPane.setText((playerTag.get("SpawnDimension") instanceof StringTag dimensionTag ? Dimension.of(dimensionTag) : Dimension.OVERWORLD) .formatPosition(intX.getClonedValue(), intY.getClonedValue(), intZ.getClonedValue())); } @@ -380,14 +382,10 @@ else if (dataTag.at("difficulty_settings.locked") instanceof ByteTag LockedTag) playerGameTypePane.setDisable(worldManagePage.isReadOnly()); playerGameTypePane.setItems(GameType.items); - ByteTag hardcoreTag = Optional.ofNullable(dataTag.get("hardcore")) // Valid before 26.1-snapshot-6 - .filter(t -> t instanceof ByteTag) - .map(t -> (ByteTag) t) + // Valid before 26.1-snapshot-6 + ByteTag hardcoreTag = Optional.ofNullable(dataTag.get("hardcore") instanceof ByteTag b ? b : null) // Valid after 26.1-snapshot-6 - .orElseGet(() -> { - if (dataTag.at("difficulty_settings.hardcore") instanceof ByteTag b) return b; - return null; - }); + .orElseGet(() -> dataTag.at("difficulty_settings.hardcore") instanceof ByteTag b ? b : null); if (playerTag.get("playerGameType") instanceof IntTag playerGameTypeTag && hardcoreTag != null) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index e726d6667e..cc571cf167 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -116,11 +116,11 @@ public long getLastPlayed() { } public @Nullable Long getSeed() { - // Valid between 1.16 and 26.1-snapshot-6 + // Valid between 1.16(20w20a) and 26.1-snapshot-6 if (levelData.at("Data.WorldGenSettings.seed") instanceof LongTag seedTag) { return seedTag.getClonedValue(); } - // Valid before 1.16 + // Valid before 1.16(20w20a) else if (levelData.at("Data.RandomSeed") instanceof LongTag seedTag) { return seedTag.getClonedValue(); } @@ -132,7 +132,7 @@ else if (worldGenSettingsData != null && worldGenSettingsData.at("data.seed") in } public boolean isLargeBiomes() { - // Valid before 1.16 + // Valid before 1.16(20w20a) if (levelData.at("Data.generatorName") instanceof StringTag generatorNameTag) { return "largeBiomes".equals(generatorNameTag.getClonedValue()); } @@ -142,11 +142,12 @@ else if (worldGenSettingsData != null) { return "minecraft:large_biomes".equals(settingsTag.getClonedValue()); } } - // Valid between 1.16 and 1.16.2 + // Valid between 1.16(20w20a) and 1.18(21w37a) else if (levelData.at("Data.WorldGenSettings.dimensions.minecraft:overworld.generator.biome_source.large_biomes") instanceof ByteTag largeBiomesTag) { return largeBiomesTag.getClonedValue() == (byte) 1; } - // Valid between 1.16.2 and 26.1-snapshot-6 + // Valid between 1.18(21w37a) and 26.1-snapshot-6 + // Note: In versions 1.16(20w20a) and 1.18(21w37a), the settings tag exists but does not indicate large biomes information else if (levelData.at("Data.WorldGenSettings.dimensions.minecraft:overworld.generator.settings") instanceof StringTag settingsTag) { return "minecraft:large_biomes".equals(settingsTag.getClonedValue()); } From c44b680bbeaeac5c35102c21f4f17387bf6ec85f Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Sat, 7 Feb 2026 21:32:00 +0800 Subject: [PATCH 20/28] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/ui/versions/WorldInfoPage.java | 4 ++-- HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index db70a7637d..325d2bc428 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -317,8 +317,8 @@ else if (dataTag.at("difficulty_settings.difficulty") instanceof StringTag diffi bindTagAndToggleButton(difficultyLockedTag, difficultyLockPane); } // Valid after 26.1-snapshot-6 - else if (dataTag.at("difficulty_settings.locked") instanceof ByteTag LockedTag) { - bindTagAndToggleButton(LockedTag, difficultyLockPane); + else if (dataTag.at("difficulty_settings.locked") instanceof ByteTag lockedTag) { + bindTagAndToggleButton(lockedTag, difficultyLockPane); } else { difficultyLockPane.setDisable(true); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index cc571cf167..a6ae9597d9 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -80,7 +80,7 @@ public String getWorldName() { public void setWorldName(String worldName) throws IOException { if (levelData.at("Data.LevelName") instanceof StringTag levelNameTag) { levelNameTag.setValue(worldName); - writeLevelDat(); + writeLevelData(); } } @@ -293,7 +293,7 @@ public void rename(String newName) throws IOException { // Change the name recorded in level.dat CompoundTag data = (CompoundTag) levelData.get("Data"); data.put(new StringTag("LevelName", newName)); - writeLevelDat(); + writeLevelData(); // then change the folder's name Files.move(file, file.resolveSibling(newName)); @@ -393,7 +393,7 @@ public void writeWorldData() throws IOException { if (!Files.isDirectory(file)) throw new IOException("Not a valid world directory"); - writeLevelDat(); + writeLevelData(); if (worldGenSettingsDataPath != null) { writeTag(worldGenSettingsData, worldGenSettingsDataPath); @@ -404,7 +404,7 @@ public void writeWorldData() throws IOException { } } - public void writeLevelDat() throws IOException { + public void writeLevelData() throws IOException { writeTag(levelData, getLevelDataFile()); } From b95c0784bbb2c9dc910b58020af1d92f0e98a513 Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Sat, 7 Feb 2026 21:58:54 +0800 Subject: [PATCH 21/28] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Dbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java | 4 ++-- HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index 325d2bc428..3a6469ebce 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -359,8 +359,8 @@ else if (dataTag.at("difficulty_settings.locked") instanceof ByteTag lockedTag) { spawnPane.setTitle(i18n("world.info.player.spawn")); // Valid after 1.21.5(25w07a) - if (levelDat.at("respawn.dimension") instanceof StringTag dimensionTag - && levelDat.at("respawn.pos") instanceof IntArrayTag intArrayTag + if (playerTag.at("respawn.dimension") instanceof StringTag dimensionTag + && playerTag.at("respawn.pos") instanceof IntArrayTag intArrayTag && intArrayTag.length() == 3) { spawnPane.setText(Dimension.of(dimensionTag).formatPosition(intArrayTag)); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index a6ae9597d9..3229bff72c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -261,10 +261,14 @@ private void loadOtherData() throws IOException { if (Files.exists(worldGenSettingsDatPath)) { this.worldGenSettingsDataPath = worldGenSettingsDatPath; this.worldGenSettingsData = NBT.read(worldGenSettingsDatPath); + } else { + this.worldGenSettingsDataPath = null; + this.worldGenSettingsData = null; } if (levelData.at("Data.Player") instanceof CompoundTag playerTag) { this.playerData = playerTag; + this.playerDataPath = null; } else if (levelData.at("Data.singleplayer_uuid") instanceof IntArrayTag uuidTag) { int[] uuidValue = uuidTag.getClonedValue(); if (uuidValue.length == 4) { @@ -277,6 +281,9 @@ private void loadOtherData() throws IOException { this.playerDataPath = playerDatPath; } } + } else { + this.playerData = null; + this.playerDataPath = null; } } From 192437eef10f6a43bd09ccebcc9581ac80353d1d Mon Sep 17 00:00:00 2001 From: Mine-diamond Date: Sat, 7 Feb 2026 23:09:26 +0800 Subject: [PATCH 22/28] =?UTF-8?q?feat:=20=E6=9B=B4=E6=94=B9=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E9=A1=BA=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9063ceae2d..a4987b133b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,6 +9,7 @@ xz = "1.11" fx-gson = "5.0.0" constant-pool-scanner = "1.2" opennbt = "1.5" +micanbt = "0.3.1" nanohttpd = "2.3.1" jsoup = "1.21.2" chardet = "2.5.0" @@ -19,7 +20,6 @@ java-info = "1.0" authlib-injector = "1.2.7" monet-fx = "0.4.0" terracotta = "0.4.1" -micanbt = "0.3.1" # testing junit = "6.0.1" @@ -40,6 +40,7 @@ xz = { module = "org.tukaani:xz", version.ref = "xz" } fx-gson = { module = "org.hildan.fxgson:fx-gson", version.ref = "fx-gson" } constant-pool-scanner = { module = "org.jenkins-ci:constant-pool-scanner", version.ref = "constant-pool-scanner" } opennbt = { module = "com.github.steveice10:opennbt", version.ref = "opennbt" } +micanbt = {module = "tech.minediamond:micanbt", version.ref = "micanbt"} nanohttpd = { module = "org.nanohttpd:nanohttpd", version.ref = "nanohttpd" } jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } chardet = { module = "org.glavo:chardet", version.ref = "chardet" } @@ -50,7 +51,6 @@ pci-ids = { module = "org.glavo:pci-ids", version.ref = "pci-ids" } java-info = { module = "org.glavo:java-info", version.ref = "java-info" } authlib-injector = { module = "org.glavo.hmcl:authlib-injector", version.ref = "authlib-injector" } monet-fx = { module = "org.glavo:MonetFX", version.ref = "monet-fx" } -micanbt = {module = "tech.minediamond:micanbt", version.ref = "micanbt"} # testing junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } From d7ba7aa0caf08318487ed37008131bbcd782bea2 Mon Sep 17 00:00:00 2001 From: mineDiamond Date: Mon, 9 Feb 2026 18:03:48 +0800 Subject: [PATCH 23/28] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/WorldInfoPage.java | 32 +++++++------------ .../java/org/jackhuang/hmcl/game/World.java | 9 ++---- 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index 3a6469ebce..6e1c997e38 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -68,7 +68,8 @@ public final class WorldInfoPage extends SpinnerPane implements WorldManagePage. private final WorldManagePage worldManagePage; private boolean isReadOnly; private final World world; - private CompoundTag levelDat; + private CompoundTag levelData; + private CompoundTag playerData; ImageView iconImageView = new ImageView(); @@ -78,16 +79,9 @@ public WorldInfoPage(WorldManagePage worldManagePage) { refresh(); } - private CompoundTag loadWorldInfo() throws IOException { - if (!Files.isDirectory(world.getFile())) - throw new IOException("Not a valid world directory"); - - return world.getLevelData(); - } - private void updateControls() { - CompoundTag dataTag = (CompoundTag) levelDat.get("Data"); - CompoundTag playerTag = world.getPlayerData(); + CompoundTag dataTag = (CompoundTag) levelData.get("Data"); + CompoundTag playerTag = playerData; ScrollPane scrollPane = new ScrollPane(); scrollPane.setFitToHeight(true); @@ -539,17 +533,13 @@ private void saveWorldData() { public void refresh() { this.isReadOnly = worldManagePage.isReadOnly(); this.setLoading(true); - Task.supplyAsync(this::loadWorldInfo) - .whenComplete(Schedulers.javafx(), ((result, exception) -> { - if (exception == null) { - this.levelDat = result; - updateControls(); - setLoading(false); - } else { - LOG.warning("Failed to load world info of world " + world.getWorldName(), exception); - setFailedReason(i18n("world.info.failed")); - } - })).start(); + Task.runAsync(Schedulers.javafx(), () -> { + this.levelData = world.getLevelData(); + this.playerData = world.getPlayerData(); + + updateControls(); + setLoading(false); + }).start(); } private record Dimension(String name) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index 3229bff72c..61d7c7d0ab 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -46,13 +46,14 @@ public final class World { private final Path file; private String fileName; + private Image icon; + private CompoundTag levelData; private Path levelDataPath; private CompoundTag worldGenSettingsData; private Path worldGenSettingsDataPath; private CompoundTag playerData; private Path playerDataPath; - private Image icon; public World(Path file) throws IOException { this.file = file; @@ -84,10 +85,6 @@ public void setWorldName(String worldName) throws IOException { } } - public Path getLevelDataFile() { - return file.resolve("level.dat"); - } - public Path getSessionLockFile() { return file.resolve("session.lock"); } @@ -412,7 +409,7 @@ public void writeWorldData() throws IOException { } public void writeLevelData() throws IOException { - writeTag(levelData, getLevelDataFile()); + writeTag(levelData, levelDataPath); } public void writeTag(CompoundTag nbt, Path path) throws IOException { From 81c3e9f35d0e676a19b3fb9ba25bf7d393a36a62 Mon Sep 17 00:00:00 2001 From: mineDiamond Date: Mon, 9 Feb 2026 18:26:41 +0800 Subject: [PATCH 24/28] =?UTF-8?q?feat:=20=E7=A7=BB=E5=8A=A8=E5=85=B3?= =?UTF-8?q?=E4=BA=8E=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HMCL/src/main/resources/assets/about/deps.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/HMCL/src/main/resources/assets/about/deps.json b/HMCL/src/main/resources/assets/about/deps.json index 686c19b687..c9ee0cbcaf 100644 --- a/HMCL/src/main/resources/assets/about/deps.json +++ b/HMCL/src/main/resources/assets/about/deps.json @@ -49,6 +49,11 @@ "subtitle" : "Copyright © 2013-2021 Steveice10.\nLicensed under the MIT License.", "externalLink" : "https://github.com/GeyserMC/OpenNBT" }, + { + "title" : "MicaNBT", + "subtitle" : "Copyright © 2013-2021 Steveice10, 2026 Mine-diamond.\nLicensed under the MIT License.", + "externalLink" : "https://github.com/Mine-diamond/MicaNBT" + }, { "title" : "minecraft-jfx-skin", "subtitle" : "Copyright © 2016 InfinityStudio.\nLicensed under the GPL 3.", @@ -83,10 +88,5 @@ "title": "MonetFX", "subtitle": "Copyright © 2025 Glavo.\nLicensed under the Apache 2.0 License.", "externalLink": "https://github.com/Glavo/MonetFX" - }, - { - "title" : "MicaNBT", - "subtitle" : "Copyright © 2013-2021 Steveice10, 2026 Mine-diamond.\nLicensed under the MIT License.", - "externalLink" : "https://github.com/Mine-diamond/MicaNBT" } ] \ No newline at end of file From 84145bf833a3952aa197d63d1cfacb127cef331f Mon Sep 17 00:00:00 2001 From: mineDiamond Date: Mon, 9 Feb 2026 20:34:54 +0800 Subject: [PATCH 25/28] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/WorldInfoPage.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index 6e1c997e38..0f4c97995f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -104,7 +104,7 @@ private void updateControls() { setRightTextField(worldNamePane, worldNameField, 200); if (dataTag.get("LevelName") instanceof StringTag worldNameTag) { - var worldName = new SimpleStringProperty(worldNameTag.getRawValue()); + var worldName = new SimpleStringProperty(worldNameTag.getClonedValue()); FXUtils.bindString(worldNameField, worldName); worldNameField.getProperties().put(WorldInfoPage.class.getName() + ".worldNameProperty", worldName); worldName.addListener((observable, oldValue, newValue) -> { @@ -219,7 +219,7 @@ else if (dataTag.get("SpawnX") instanceof IntTag intX && dataTag.get("SpawnZ") instanceof IntTag intZ) { value = Dimension.OVERWORLD.formatPosition(intX.getClonedValue(), intY.getClonedValue(), intZ.getClonedValue()); } else { - value = null; + value = ""; } worldSpawnPoint.setText(value); @@ -341,9 +341,11 @@ else if (dataTag.at("difficulty_settings.locked") instanceof ByteTag lockedTag) { lastDeathLocationPane.setTitle(i18n("world.info.player.last_death_location")); // Valid after 22w14a; prior to this version, the game did not record the last death location data. - if (playerTag.get("LastDeathLocation") instanceof CompoundTag LastDeathLocationTag) { - Dimension dimension = Dimension.of(LastDeathLocationTag.get("dimension")); - if (dimension != null && LastDeathLocationTag.get("pos") instanceof IntArrayTag posTag) { + if (playerTag.at("LastDeathLocation.dimension") instanceof StringTag dimensionTag + && playerTag.at("LastDeathLocation.pos") instanceof IntArrayTag posTag + && posTag.length() == 3) { + Dimension dimension = Dimension.of(dimensionTag); + if (dimension != null) { lastDeathLocationPane.setText(dimension.formatPosition(posTag)); } } @@ -356,7 +358,10 @@ else if (dataTag.at("difficulty_settings.locked") instanceof ByteTag lockedTag) if (playerTag.at("respawn.dimension") instanceof StringTag dimensionTag && playerTag.at("respawn.pos") instanceof IntArrayTag intArrayTag && intArrayTag.length() == 3) { - spawnPane.setText(Dimension.of(dimensionTag).formatPosition(intArrayTag)); + Dimension dimension = Dimension.of(dimensionTag); + if (dimension != null) { + spawnPane.setText(dimension.formatPosition(intArrayTag)); + } } // Valid before 1.21.5(25w07a) else if (playerTag.get("SpawnX") instanceof IntTag intX From 6fda6dc82b42f409eafaace7c3a4ddccdca1cc58 Mon Sep 17 00:00:00 2001 From: mineDiamond Date: Thu, 26 Feb 2026 10:46:48 +0800 Subject: [PATCH 26/28] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0micanbt?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/jackhuang/hmcl/ui/versions/WorldInfoPage.java | 4 ++-- .../src/main/java/org/jackhuang/hmcl/game/World.java | 11 ++--------- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index 0f4c97995f..5bfef295e1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -343,7 +343,7 @@ else if (dataTag.at("difficulty_settings.locked") instanceof ByteTag lockedTag) // Valid after 22w14a; prior to this version, the game did not record the last death location data. if (playerTag.at("LastDeathLocation.dimension") instanceof StringTag dimensionTag && playerTag.at("LastDeathLocation.pos") instanceof IntArrayTag posTag - && posTag.length() == 3) { + && posTag.size() == 3) { Dimension dimension = Dimension.of(dimensionTag); if (dimension != null) { lastDeathLocationPane.setText(dimension.formatPosition(posTag)); @@ -357,7 +357,7 @@ else if (dataTag.at("difficulty_settings.locked") instanceof ByteTag lockedTag) // Valid after 1.21.5(25w07a) if (playerTag.at("respawn.dimension") instanceof StringTag dimensionTag && playerTag.at("respawn.pos") instanceof IntArrayTag intArrayTag - && intArrayTag.length() == 3) { + && intArrayTag.size() == 3) { Dimension dimension = Dimension.of(dimensionTag); if (dimension != null) { spawnPane.setText(dimension.formatPosition(intArrayTag)); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index 61d7c7d0ab..2a32872103 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -22,13 +22,11 @@ import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jetbrains.annotations.Nullable; import tech.minediamond.micanbt.NBT.NBT; -import tech.minediamond.micanbt.NBT.NBTWriter; +import tech.minediamond.micanbt.NBT.NBTCompressType; import tech.minediamond.micanbt.tag.*; -import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; @@ -38,7 +36,6 @@ import java.util.List; import java.util.UUID; import java.util.stream.Stream; -import java.util.zip.GZIPOutputStream; import static org.jackhuang.hmcl.util.logging.Logger.LOG; @@ -416,11 +413,7 @@ public void writeTag(CompoundTag nbt, Path path) throws IOException { if (!Files.isDirectory(file)) throw new IOException("Not a valid world directory"); - FileUtils.saveSafely(path, os -> { - try (OutputStream bos = new BufferedOutputStream(os); OutputStream gos = new GZIPOutputStream(bos)) { - NBTWriter.writeTag(gos, nbt); - } - }); + FileUtils.saveSafely(path, os -> NBT.toStream(nbt, os).compressType(NBTCompressType.GZIP).write()); } private static boolean isLocked(Path sessionLockFile) { From 470070c496257d95872a37b6cdef144e280ac14c Mon Sep 17 00:00:00 2001 From: mineDiamond Date: Thu, 26 Feb 2026 12:56:59 +0800 Subject: [PATCH 27/28] =?UTF-8?q?feat:=20=E4=BD=BF=E7=94=A8=20atAny=20?= =?UTF-8?q?=E7=AE=80=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/WorldInfoPage.java | 25 ++++++------------- .../java/org/jackhuang/hmcl/game/World.java | 8 ++---- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index 5bfef295e1..a548eba78c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -55,7 +55,6 @@ import java.time.Instant; import java.util.Arrays; import java.util.Locale; -import java.util.Optional; import static org.jackhuang.hmcl.util.i18n.I18n.formatDateTime; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -253,14 +252,10 @@ else if (dataTag.get("SpawnX") instanceof IntTag intX generateFeaturesButton.setTitle(i18n("world.info.generate_features")); generateFeaturesButton.setDisable(isReadOnly); - // Valid between (1.16)20w20a and 26.1-snapshot-6 - if (dataTag.at("WorldGenSettings.generate_features") instanceof ByteTag generateFeaturesTag) { + // Valid between (1.16)20w20a and 26.1-snapshot-6 / Valid before (1.16)20w20a + if (dataTag.atAny("WorldGenSettings.generate_features", "MapFeatures") instanceof ByteTag generateFeaturesTag) { bindTagAndToggleButton(generateFeaturesTag, generateFeaturesButton); } - // Valid before (1.16)20w20a - else if (dataTag.get("MapFeatures") instanceof ByteTag mapFeatures) { - bindTagAndToggleButton(mapFeatures, generateFeaturesButton); - } // Valid after 26.1-snapshot-6 else if (world.getWorldGenSettingsData() != null && world.getWorldGenSettingsData().at("data.generate_structures") instanceof ByteTag generateStructures) { bindTagAndToggleButton(generateStructures, generateFeaturesButton); @@ -306,13 +301,9 @@ else if (dataTag.at("difficulty_settings.difficulty") instanceof StringTag diffi { difficultyLockPane.setTitle(i18n("world.info.difficulty_lock")); difficultyLockPane.setDisable(isReadOnly); - // Valid before 26.1-snapshot-6 - if (dataTag.get("DifficultyLocked") instanceof ByteTag difficultyLockedTag) { + // Valid before 26.1-snapshot-6 / Valid after 26.1-snapshot-6 + if (dataTag.atAny("DifficultyLocked", "difficulty_settings.locked") instanceof ByteTag difficultyLockedTag) { bindTagAndToggleButton(difficultyLockedTag, difficultyLockPane); - } - // Valid after 26.1-snapshot-6 - else if (dataTag.at("difficulty_settings.locked") instanceof ByteTag lockedTag) { - bindTagAndToggleButton(lockedTag, difficultyLockPane); } else { difficultyLockPane.setDisable(true); } @@ -379,13 +370,11 @@ else if (playerTag.get("SpawnX") instanceof IntTag intX playerGameTypePane.setDisable(worldManagePage.isReadOnly()); playerGameTypePane.setItems(GameType.items); - // Valid before 26.1-snapshot-6 - ByteTag hardcoreTag = Optional.ofNullable(dataTag.get("hardcore") instanceof ByteTag b ? b : null) - // Valid after 26.1-snapshot-6 - .orElseGet(() -> dataTag.at("difficulty_settings.hardcore") instanceof ByteTag b ? b : null); + // Valid before 26.1-snapshot-6 / Valid after 26.1-snapshot-6 + Tag hardcore = dataTag.atAny("hardcore", "difficulty_settings.hardcore"); if (playerTag.get("playerGameType") instanceof IntTag playerGameTypeTag - && hardcoreTag != null) { + && hardcore instanceof ByteTag hardcoreTag) { boolean isHardcore = hardcoreTag.getClonedValue() == 1; GameType gameType = GameType.of(playerGameTypeTag.getClonedValue(), isHardcore); if (gameType != null) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index 2a32872103..1c8c169240 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -110,12 +110,8 @@ public long getLastPlayed() { } public @Nullable Long getSeed() { - // Valid between 1.16(20w20a) and 26.1-snapshot-6 - if (levelData.at("Data.WorldGenSettings.seed") instanceof LongTag seedTag) { - return seedTag.getClonedValue(); - } - // Valid before 1.16(20w20a) - else if (levelData.at("Data.RandomSeed") instanceof LongTag seedTag) { + // Valid between 1.16(20w20a) and 26.1-snapshot-6 / Valid before 1.16(20w20a) + if (levelData.atAny("Data.WorldGenSettings.seed", "Data.RandomSeed") instanceof LongTag seedTag) { return seedTag.getClonedValue(); } // Valid after 26.1-snapshot-6 From fc893bf7a103a70534694f29c603bb18ab532b20 Mon Sep 17 00:00:00 2001 From: mineDiamond Date: Thu, 26 Feb 2026 22:35:11 +0800 Subject: [PATCH 28/28] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hmcl/ui/versions/WorldInfoPage.java | 9 ++--- .../java/org/jackhuang/hmcl/game/World.java | 34 ++++++++++--------- gradle/libs.versions.toml | 2 +- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java index a548eba78c..dd58359b82 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldInfoPage.java @@ -45,6 +45,7 @@ import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.io.FileUtils; +import tech.minediamond.micanbt.path.NBTFinder; import tech.minediamond.micanbt.tag.*; import java.io.IOException; @@ -252,12 +253,12 @@ else if (dataTag.get("SpawnX") instanceof IntTag intX generateFeaturesButton.setTitle(i18n("world.info.generate_features")); generateFeaturesButton.setDisable(isReadOnly); - // Valid between (1.16)20w20a and 26.1-snapshot-6 / Valid before (1.16)20w20a - if (dataTag.atAny("WorldGenSettings.generate_features", "MapFeatures") instanceof ByteTag generateFeaturesTag) { + // Valid before (1.16)20w20a + if (dataTag.at("MapFeatures") instanceof ByteTag generateFeaturesTag) { bindTagAndToggleButton(generateFeaturesTag, generateFeaturesButton); } - // Valid after 26.1-snapshot-6 - else if (world.getWorldGenSettingsData() != null && world.getWorldGenSettingsData().at("data.generate_structures") instanceof ByteTag generateStructures) { + // Valid after (1.16)20w20a + else if (NBTFinder.findFirst(world.getUnifiedWorldGenSettingsData(), "generate_features", "generate_structures") instanceof ByteTag generateStructures) { bindTagAndToggleButton(generateStructures, generateFeaturesButton); } else { generateFeaturesButton.setDisable(true); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java index 1c8c169240..c1fc85f557 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/World.java @@ -23,6 +23,7 @@ import org.jetbrains.annotations.Nullable; import tech.minediamond.micanbt.NBT.NBT; import tech.minediamond.micanbt.NBT.NBTCompressType; +import tech.minediamond.micanbt.path.NBTFinder; import tech.minediamond.micanbt.tag.*; import java.io.IOException; @@ -48,6 +49,7 @@ public final class World { private CompoundTag levelData; private Path levelDataPath; private CompoundTag worldGenSettingsData; + private CompoundTag unifiedWorldGenSettingsData; private Path worldGenSettingsDataPath; private CompoundTag playerData; private Path playerDataPath; @@ -90,8 +92,8 @@ public CompoundTag getLevelData() { return levelData; } - public @Nullable CompoundTag getWorldGenSettingsData() { - return worldGenSettingsData; + public @Nullable CompoundTag getUnifiedWorldGenSettingsData() { + return unifiedWorldGenSettingsData; } public @Nullable CompoundTag getPlayerData() { @@ -110,12 +112,12 @@ public long getLastPlayed() { } public @Nullable Long getSeed() { - // Valid between 1.16(20w20a) and 26.1-snapshot-6 / Valid before 1.16(20w20a) - if (levelData.atAny("Data.WorldGenSettings.seed", "Data.RandomSeed") instanceof LongTag seedTag) { + // Valid after 1.16(20w20a) + if (NBTFinder.get(unifiedWorldGenSettingsData, "seed") instanceof LongTag seedTag) { return seedTag.getClonedValue(); } - // Valid after 26.1-snapshot-6 - else if (worldGenSettingsData != null && worldGenSettingsData.at("data.seed") instanceof LongTag seedTag) { + // Valid before 1.16(20w20a) + else if (levelData.at("Data.RandomSeed") instanceof LongTag seedTag) { return seedTag.getClonedValue(); } return null; @@ -126,19 +128,13 @@ public boolean isLargeBiomes() { if (levelData.at("Data.generatorName") instanceof StringTag generatorNameTag) { return "largeBiomes".equals(generatorNameTag.getClonedValue()); } - // Valid after 26.1-snapshot-6 - else if (worldGenSettingsData != null) { - if (worldGenSettingsData.at("data.dimensions.minecraft:overworld.generator.settings") instanceof StringTag settingsTag) { - return "minecraft:large_biomes".equals(settingsTag.getClonedValue()); - } - } // Valid between 1.16(20w20a) and 1.18(21w37a) - else if (levelData.at("Data.WorldGenSettings.dimensions.minecraft:overworld.generator.biome_source.large_biomes") instanceof ByteTag largeBiomesTag) { + else if (NBTFinder.get(unifiedWorldGenSettingsData, "dimensions.minecraft:overworld.generator.biome_source.large_biomes") instanceof ByteTag largeBiomesTag) { return largeBiomesTag.getClonedValue() == (byte) 1; } - // Valid between 1.18(21w37a) and 26.1-snapshot-6 + // Valid after 1.18(21w37a) // Note: In versions 1.16(20w20a) and 1.18(21w37a), the settings tag exists but does not indicate large biomes information - else if (levelData.at("Data.WorldGenSettings.dimensions.minecraft:overworld.generator.settings") instanceof StringTag settingsTag) { + else if (NBTFinder.get(unifiedWorldGenSettingsData, "dimensions.minecraft:overworld.generator.settings") instanceof StringTag settingsTag) { return "minecraft:large_biomes".equals(settingsTag.getClonedValue()); } return false; @@ -248,12 +244,18 @@ private void loadAndCheckLevelData(Path levelDat) throws IOException { private void loadOtherData() throws IOException { Path worldGenSettingsDatPath = file.resolve("data/minecraft/world_gen_settings.dat"); - if (Files.exists(worldGenSettingsDatPath)) { + if (levelData.at("Data.WorldGenSettings") instanceof CompoundTag worldGenSettingsTag) { + this.worldGenSettingsDataPath = null; + this.worldGenSettingsData = worldGenSettingsTag; + this.unifiedWorldGenSettingsData = worldGenSettingsData; + } else if (Files.exists(worldGenSettingsDatPath)) { this.worldGenSettingsDataPath = worldGenSettingsDatPath; this.worldGenSettingsData = NBT.read(worldGenSettingsDatPath); + this.unifiedWorldGenSettingsData = (CompoundTag) worldGenSettingsData.get("data"); } else { this.worldGenSettingsDataPath = null; this.worldGenSettingsData = null; + this.unifiedWorldGenSettingsData = null; } if (levelData.at("Data.Player") instanceof CompoundTag playerTag) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a4987b133b..d3909117c8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ xz = "1.11" fx-gson = "5.0.0" constant-pool-scanner = "1.2" opennbt = "1.5" -micanbt = "0.3.1" +micanbt = "0.5.0" nanohttpd = "2.3.1" jsoup = "1.21.2" chardet = "2.5.0"