优化 NBT 查看器 (#3653)

* update

* update

* update
This commit is contained in:
Glavo
2025-02-26 01:40:36 +08:00
committed by GitHub
parent ff4940e79f
commit 618243350e
7 changed files with 127 additions and 67 deletions

View File

@@ -157,23 +157,5 @@ public class SpinnerPane extends Control {
} }
} }
public interface State {}
public static class LoadedState implements State {}
public static class LoadingState implements State {}
public static class FailedState implements State {
private final String reason;
public FailedState(String reason) {
this.reason = reason;
}
public String getReason() {
return reason;
}
}
public static final EventType<Event> FAILED_ACTION = new EventType<>(Event.ANY, "FAILED_ACTION"); public static final EventType<Event> FAILED_ACTION = new EventType<>(Event.ANY, "FAILED_ACTION");
} }

View File

@@ -39,7 +39,7 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider; import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
import org.jackhuang.hmcl.ui.nbt.NBTEditorPage; import org.jackhuang.hmcl.ui.nbt.NBTEditorPage;
import org.jackhuang.hmcl.ui.nbt.NBTHelper; import org.jackhuang.hmcl.ui.nbt.NBTFileType;
import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem; import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem;
import org.jackhuang.hmcl.ui.versions.Versions; import org.jackhuang.hmcl.ui.versions.Versions;
import org.jackhuang.hmcl.upgrade.UpdateChecker; import org.jackhuang.hmcl.upgrade.UpdateChecker;
@@ -90,16 +90,16 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
if (mainPage == null) { if (mainPage == null) {
MainPage mainPage = new MainPage(); MainPage mainPage = new MainPage();
FXUtils.applyDragListener(mainPage, FXUtils.applyDragListener(mainPage,
file -> ModpackHelper.isFileModpackByExtension(file) || NBTHelper.isNBTFileByExtension(file), file -> ModpackHelper.isFileModpackByExtension(file) || NBTFileType.isNBTFileByExtension(file.toPath()),
modpacks -> { modpacks -> {
File file = modpacks.get(0); File file = modpacks.get(0);
if (ModpackHelper.isFileModpackByExtension(file)) { if (ModpackHelper.isFileModpackByExtension(file)) {
Controllers.getDecorator().startWizard( Controllers.getDecorator().startWizard(
new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), file), new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), file),
i18n("install.modpack")); i18n("install.modpack"));
} else if (NBTHelper.isNBTFileByExtension(file)) { } else if (NBTFileType.isNBTFileByExtension(file.toPath())) {
try { try {
Controllers.navigate(new NBTEditorPage(file)); Controllers.navigate(new NBTEditorPage(file.toPath()));
} catch (Throwable e) { } catch (Throwable e) {
LOG.warning("Fail to open nbt file", e); LOG.warning("Fail to open nbt file", e);
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e), Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e),

View File

@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.nbt; package org.jackhuang.hmcl.ui.nbt;
import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXButton;
@@ -5,33 +22,38 @@ import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane; import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.PageCloseEvent; import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.StringUtils;
import java.io.*; import java.io.*;
import java.util.concurrent.CompletableFuture; import java.nio.file.Path;
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed; import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
import static org.jackhuang.hmcl.util.logging.Logger.LOG; import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class NBTEditorPage extends BorderPane implements DecoratorPage { /**
* @author Glavo
*/
public final class NBTEditorPage extends SpinnerPane implements DecoratorPage {
private final ReadOnlyObjectWrapper<State> state; private final ReadOnlyObjectWrapper<State> state;
private final File file; private final Path file;
private final NBTFileType type; private final NBTFileType type;
public NBTEditorPage(File file) throws IOException { private final BorderPane root = new BorderPane();
public NBTEditorPage(Path file) throws IOException {
getStyleClass().add("gray-background"); getStyleClass().add("gray-background");
this.state = new ReadOnlyObjectWrapper<>(DecoratorPage.State.fromTitle(i18n("nbt.title", file.getAbsolutePath()))); this.state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("nbt.title", file.toString())));
this.file = file; this.file = file;
this.type = NBTFileType.ofFile(file); this.type = NBTFileType.ofFile(file);
@@ -39,7 +61,8 @@ public class NBTEditorPage extends BorderPane implements DecoratorPage {
throw new IOException("Unknown type of file " + file); throw new IOException("Unknown type of file " + file);
} }
setCenter(new ProgressIndicator()); setContent(root);
setLoading(true);
HBox actions = new HBox(8); HBox actions = new HBox(8);
actions.setPadding(new Insets(8)); actions.setPadding(new Insets(8));
@@ -65,18 +88,16 @@ public class NBTEditorPage extends BorderPane implements DecoratorPage {
actions.getChildren().setAll(saveButton, cancelButton); actions.getChildren().setAll(saveButton, cancelButton);
CompletableFuture.supplyAsync(Lang.wrap(() -> type.readAsTree(file))) Task.supplyAsync(() -> type.readAsTree(file))
.thenAcceptAsync(tree -> { .whenComplete(Schedulers.javafx(), (result, exception) -> {
setCenter(new NBTTreeView(tree)); if (exception == null) {
// setBottom(actions); setLoading(false);
}, Schedulers.javafx()) root.setCenter(new NBTTreeView(result));
.handleAsync((result, e) -> { } else {
if (e != null) { LOG.warning("Fail to open nbt file", exception);
LOG.warning("Fail to open nbt file", e); Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(exception), null, MessageDialogPane.MessageType.WARNING, cancelButton::fire);
Controllers.dialog(i18n("nbt.open.failed") + "\n\n" + StringUtils.getStackTrace(e), null, MessageDialogPane.MessageType.WARNING, cancelButton::fire);
} }
return null; }).start();
}, Schedulers.javafx());
} }
public void save() throws IOException { public void save() throws IOException {

View File

@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.nbt; package org.jackhuang.hmcl.ui.nbt;
import com.github.steveice10.opennbt.NBTIO; import com.github.steveice10.opennbt.NBTIO;
@@ -9,16 +26,21 @@ import org.apache.commons.compress.utils.BoundedInputStream;
import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.*; import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater; import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream; import java.util.zip.InflaterInputStream;
/**
* @author Glavo
*/
public enum NBTFileType { public enum NBTFileType {
COMPRESSED("dat", "dat_old") { COMPRESSED("dat", "dat_old") {
@Override @Override
public Tag read(File file) throws IOException { public Tag read(Path file) throws IOException {
try (BufferedInputStream fileInputStream = new BufferedInputStream(new FileInputStream(file))) { try (BufferedInputStream fileInputStream = new BufferedInputStream(Files.newInputStream(file))) {
fileInputStream.mark(3); fileInputStream.mark(3);
byte[] header = new byte[3]; byte[] header = new byte[3];
if (fileInputStream.read(header) < 3) { if (fileInputStream.read(header) < 3) {
@@ -42,25 +64,28 @@ public enum NBTFileType {
}, },
ANVIL("mca") { ANVIL("mca") {
@Override @Override
public Tag read(File file) throws IOException { public Tag read(Path file) throws IOException {
return REGION.read(file); return REGION.read(file);
} }
@Override @Override
public NBTTreeView.Item readAsTree(File file) throws IOException { public NBTTreeView.Item readAsTree(Path file) throws IOException {
return REGION.readAsTree(file); return REGION.readAsTree(file);
} }
}, },
REGION("mcr") { REGION("mcr") {
@Override @Override
public Tag read(File file) throws IOException { public Tag read(Path file) throws IOException {
try (RandomAccessFile r = new RandomAccessFile(file, "r")) { try (RandomAccessFile r = new RandomAccessFile(file.toFile(), "r")) {
ListTag tag = new ListTag(file.getFileName().toString(), CompoundTag.class);
if (r.length() == 0) {
return tag;
}
byte[] header = new byte[4096]; byte[] header = new byte[4096];
byte[] buffer = new byte[1 * 1024 * 1024]; // The maximum size of each chunk is 1MiB byte[] buffer = new byte[1 * 1024 * 1024]; // The maximum size of each chunk is 1MiB
Inflater inflater = new Inflater(); Inflater inflater = new Inflater();
ListTag tag = new ListTag(file.getName(), CompoundTag.class);
r.readFully(header); r.readFully(header);
for (int i = 0; i < 4096; i += 4) { for (int i = 0; i < 4096; i += 4) {
int offset = ((header[i] & 0xff) << 16) + ((header[i + 1] & 0xff) << 8) + (header[i + 2] & 0xff); int offset = ((header[i] & 0xff) << 16) + ((header[i + 1] & 0xff) << 8) + (header[i + 2] & 0xff);
@@ -109,7 +134,7 @@ public enum NBTFileType {
} }
@Override @Override
public NBTTreeView.Item readAsTree(File file) throws IOException { public NBTTreeView.Item readAsTree(Path file) throws IOException {
NBTTreeView.Item item = new NBTTreeView.Item(read(file)); NBTTreeView.Item item = new NBTTreeView.Item(read(file));
for (Tag tag : ((ListTag) item.getValue())) { for (Tag tag : ((ListTag) item.getValue())) {
@@ -135,7 +160,11 @@ public enum NBTFileType {
static final NBTFileType[] types = values(); static final NBTFileType[] types = values();
public static NBTFileType ofFile(File file) { public static boolean isNBTFileByExtension(Path file) {
return NBTFileType.ofFile(file) != null;
}
public static NBTFileType ofFile(Path file) {
String ext = FileUtils.getExtension(file); String ext = FileUtils.getExtension(file);
for (NBTFileType type : types) { for (NBTFileType type : types) {
for (String extension : type.extensions) { for (String extension : type.extensions) {
@@ -153,11 +182,11 @@ public enum NBTFileType {
this.extensions = extensions; this.extensions = extensions;
} }
public abstract Tag read(File file) throws IOException; public abstract Tag read(Path file) throws IOException;
public NBTTreeView.Item readAsTree(File file) throws IOException { public NBTTreeView.Item readAsTree(Path file) throws IOException {
NBTTreeView.Item root = NBTTreeView.buildTree(read(file)); NBTTreeView.Item root = NBTTreeView.buildTree(read(file));
root.setName(file.getName()); root.setName(file.getFileName().toString());
return root; return root;
} }
} }

View File

@@ -1,12 +0,0 @@
package org.jackhuang.hmcl.ui.nbt;
import java.io.File;
public final class NBTHelper {
private NBTHelper() {
}
public static boolean isNBTFileByExtension(File file) {
return NBTFileType.ofFile(file) != null;
}
}

View File

@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.nbt; package org.jackhuang.hmcl.ui.nbt;
import com.github.steveice10.opennbt.tag.builtin.Tag; import com.github.steveice10.opennbt.tag.builtin.Tag;
@@ -6,6 +23,9 @@ import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
/**
* @author Glavo
*/
public enum NBTTagType { public enum NBTTagType {
BYTE, SHORT, INT, LONG, FLOAT, DOUBLE, BYTE, SHORT, INT, LONG, FLOAT, DOUBLE,
BYTE_ARRAY, INT_ARRAY, LONG_ARRAY, BYTE_ARRAY, INT_ARRAY, LONG_ARRAY,

View File

@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.nbt; package org.jackhuang.hmcl.ui.nbt;
import com.github.steveice10.opennbt.tag.builtin.CompoundTag; import com.github.steveice10.opennbt.tag.builtin.CompoundTag;
@@ -14,7 +31,10 @@ import java.util.EnumMap;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class NBTTreeView extends TreeView<Tag> { /**
* @author Glavo
*/
public final class NBTTreeView extends TreeView<Tag> {
public NBTTreeView(NBTTreeView.Item tree) { public NBTTreeView(NBTTreeView.Item tree) {
this.setRoot(tree); this.setRoot(tree);