@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user