mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-03-01 17:25:53 +08:00
Datapack resolving
This commit is contained in:
parent
2bfc85b18f
commit
1753b4d27e
@ -0,0 +1,10 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import org.jackhuang.hmcl.ui.ListPage;
|
||||
|
||||
public class DatapackList extends ListPage<DatapackListItem> {
|
||||
@Override
|
||||
public void add() {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import org.jackhuang.hmcl.mod.Datapack;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class DatapackListItem extends BorderPane {
|
||||
|
||||
public DatapackListItem(Datapack root, Datapack.Pack info, Consumer<DatapackListItem> deleteCallback) {
|
||||
JFXCheckBox chkEnabled = new JFXCheckBox();
|
||||
BorderPane.setAlignment(chkEnabled, Pos.CENTER);
|
||||
setLeft(chkEnabled);
|
||||
|
||||
TwoLineListItem modItem = new TwoLineListItem();
|
||||
BorderPane.setAlignment(modItem, Pos.CENTER);
|
||||
setCenter(modItem);
|
||||
|
||||
JFXButton btnRemove = new JFXButton();
|
||||
JFXUtilities.runInFX(() -> {
|
||||
FXUtils.installTooltip(btnRemove, i18n("mods.remove"));
|
||||
});
|
||||
btnRemove.setOnMouseClicked(e -> deleteCallback.accept(this));
|
||||
btnRemove.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnRemove, Pos.CENTER);
|
||||
btnRemove.setGraphic(SVG.close(Theme.blackFillBinding(), 15, 15));
|
||||
setRight(btnRemove);
|
||||
|
||||
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
modItem.setTitle(info.getId());
|
||||
modItem.setSubtitle(info.getDescription());
|
||||
chkEnabled.selectedProperty().bindBidirectional(info.activeProperty());
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
@ -25,6 +25,8 @@ import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import org.jackhuang.hmcl.mod.ModInfo;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
@ -28,7 +28,6 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.ListPage;
|
||||
import org.jackhuang.hmcl.ui.ModItem;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
|
||||
|
@ -0,0 +1,12 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import org.jackhuang.hmcl.ui.ListPage;
|
||||
|
||||
public class WorldList extends ListPage<WorldListItem> {
|
||||
|
||||
|
||||
@Override
|
||||
public void add() {
|
||||
// Not adding world here.
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.World;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class WorldListItem extends Control {
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final StringProperty subtitle = new SimpleStringProperty();
|
||||
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
|
||||
private final World world;
|
||||
|
||||
public WorldListItem(World world) {
|
||||
this.world = world;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new WorldListItemSkin(this);
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public StringProperty subtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public ObjectProperty<Image> imageProperty() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public void export() {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(i18n("world.export.title"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("world"), "*.zip"));
|
||||
File file = fileChooser.showSaveDialog(Controllers.getStage());
|
||||
if (file == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void manageDatapacks() {
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package org.jackhuang.hmcl.ui.versions;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedMenuItem;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class WorldListItemSkin extends SkinBase<WorldListItem> {
|
||||
|
||||
public WorldListItemSkin(WorldListItem skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
|
||||
|
||||
HBox center = new HBox();
|
||||
center.setSpacing(8);
|
||||
center.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
StackPane imageViewContainer = new StackPane();
|
||||
FXUtils.setLimitWidth(imageViewContainer, 32);
|
||||
FXUtils.setLimitHeight(imageViewContainer, 32);
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.imageProperty().bind(skinnable.imageProperty());
|
||||
imageViewContainer.getChildren().setAll(imageView);
|
||||
|
||||
TwoLineListItem item = new TwoLineListItem();
|
||||
BorderPane.setAlignment(item, Pos.CENTER);
|
||||
center.getChildren().setAll(imageView, item);
|
||||
root.setCenter(center);
|
||||
|
||||
VBox menu = new VBox();
|
||||
JFXPopup popup = new JFXPopup(menu);
|
||||
|
||||
Function<Runnable, Runnable> wrap = r -> () -> {
|
||||
r.run();
|
||||
popup.hide();
|
||||
};
|
||||
|
||||
Function<Node, Node> limitWidth = node -> {
|
||||
StackPane pane = new StackPane(node);
|
||||
pane.setAlignment(Pos.CENTER);
|
||||
FXUtils.setLimitWidth(pane, 14);
|
||||
FXUtils.setLimitHeight(pane, 14);
|
||||
return pane;
|
||||
};
|
||||
|
||||
menu.getChildren().setAll(
|
||||
new IconedMenuItem(limitWidth.apply(SVG.gear(Theme.blackFillBinding(), 14, 14)), i18n("world.datapack"), wrap.apply(skinnable::manageDatapacks)),
|
||||
new IconedMenuItem(limitWidth.apply(SVG.export(Theme.blackFillBinding(), 14, 14)), i18n("world.export"), wrap.apply(skinnable::export)));
|
||||
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
JFXButton btnManage = new JFXButton();
|
||||
btnManage.setOnMouseClicked(e -> {
|
||||
popup.show(root, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, 0, root.getHeight());
|
||||
});
|
||||
btnManage.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnManage, Pos.CENTER);
|
||||
btnManage.setGraphic(SVG.dotsVertical(Theme.blackFillBinding(), -1, -1));
|
||||
right.getChildren().add(btnManage);
|
||||
root.setRight(right);
|
||||
|
||||
root.setStyle("-fx-background-color: white; -fx-padding: 8 8 8 0;");
|
||||
JFXDepthManager.setDepth(root, 1);
|
||||
item.titleProperty().bind(skinnable.titleProperty());
|
||||
item.subtitleProperty().bind(skinnable.subtitleProperty());
|
||||
|
||||
getChildren().setAll(root);
|
||||
}
|
||||
}
|
@ -240,6 +240,15 @@ mods.add.success=Successfully added mods %s.
|
||||
mods.choose_mod=Choose your mods
|
||||
mods.remove=Remove
|
||||
|
||||
datapack=Data packs
|
||||
datapack.add=Add data pack
|
||||
datapack.remove=Remove
|
||||
|
||||
world=Worlds
|
||||
world.datapack=Manage data packs
|
||||
world.export=Export this world
|
||||
world.export.title=Choose a file location to hold your world
|
||||
|
||||
profile=Game Directories
|
||||
profile.default=Current directory
|
||||
profile.home=User home
|
||||
|
@ -15,6 +15,7 @@ import org.tukaani.xz.XZInputStream;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
@ -75,7 +76,7 @@ public class LibraryDownloadTask extends Task {
|
||||
else
|
||||
throw new LibraryDownloadException(library, t);
|
||||
} else {
|
||||
if (xz) unpackLibrary(jar, FileUtils.readBytes(xzFile));
|
||||
if (xz) unpackLibrary(jar, Files.readAllBytes(xzFile.toPath()));
|
||||
if (!checksumValid(jar, library.getChecksums())) {
|
||||
jar.delete();
|
||||
throw new IOException("Checksum failed for " + library);
|
||||
@ -125,7 +126,7 @@ public class LibraryDownloadTask extends Task {
|
||||
if (checksums == null || checksums.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
byte[] fileData = FileUtils.readBytes(libPath);
|
||||
byte[] fileData = Files.readAllBytes(libPath.toPath());
|
||||
boolean valid = checksums.contains(encodeHex(digest("SHA-1", fileData)));
|
||||
if (!valid && libPath.getName().endsWith(".jar")) {
|
||||
valid = validateJar(fileData, checksums);
|
||||
|
@ -0,0 +1,4 @@
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
public class World {
|
||||
}
|
185
HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Datapack.java
Normal file
185
HMCLCore/src/main/java/org/jackhuang/hmcl/mod/Datapack.java
Normal file
@ -0,0 +1,185 @@
|
||||
package org.jackhuang.hmcl.mod;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public class Datapack {
|
||||
private final Path path;
|
||||
private final List<Pack> info;
|
||||
|
||||
private Datapack(Path path, List<Pack> info) {
|
||||
this.path = path;
|
||||
this.info = Collections.unmodifiableList(info);
|
||||
|
||||
for (Pack pack : info) {
|
||||
pack.datapack = this;
|
||||
}
|
||||
}
|
||||
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public List<Pack> getInfo() {
|
||||
return info;
|
||||
}
|
||||
|
||||
public void installTo(Path worldPath) throws IOException {
|
||||
Path datapacks = worldPath.resolve("datapacks");
|
||||
|
||||
Set<String> packs = new HashSet<>();
|
||||
for (Pack pack : info) packs.add(pack.getId());
|
||||
|
||||
for (Path datapack : Files.newDirectoryStream(datapacks)) {
|
||||
if (packs.contains(FileUtils.getName(datapack)))
|
||||
FileUtils.deleteDirectory(datapack.toFile());
|
||||
}
|
||||
|
||||
new Unzipper(path, worldPath).setReplaceExistentFile(true).unzip();
|
||||
}
|
||||
|
||||
public static Datapack fromZip(Path path) throws IOException {
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(path)) {
|
||||
Datapack datapack = fromDir(fs.getPath("/datapacks/"));
|
||||
return new Datapack(path, datapack.info);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param dir
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static Datapack fromDir(Path dir) throws IOException {
|
||||
List<Pack> info = new LinkedList<>();
|
||||
|
||||
for (Path subDir : Files.newDirectoryStream(dir)) {
|
||||
Path mcmeta = subDir.resolve("pack.mcmeta");
|
||||
|
||||
if (!Files.exists(mcmeta))
|
||||
continue;
|
||||
|
||||
PackMcMeta pack = JsonUtils.fromNonNullJson(FileUtils.readText(mcmeta), PackMcMeta.class);
|
||||
info.add(new Pack(mcmeta, FileUtils.getName(subDir), pack.getPackInfo().getDescription()));
|
||||
}
|
||||
return new Datapack(dir, info);
|
||||
}
|
||||
|
||||
public static class Pack {
|
||||
private Path packMcMeta;
|
||||
private final BooleanProperty active;
|
||||
private final String id;
|
||||
private final String description;
|
||||
private Datapack datapack;
|
||||
|
||||
public Pack(Path packMcMeta, String id, String description) {
|
||||
this.packMcMeta = packMcMeta;
|
||||
this.id = id;
|
||||
this.description = description;
|
||||
|
||||
active = new SimpleBooleanProperty(this, "active", !DISABLED_EXT.equals(FileUtils.getExtension(packMcMeta))) {
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
Path f = Pack.this.packMcMeta.toAbsolutePath(), newF;
|
||||
if (DISABLED_EXT.equals(FileUtils.getExtension(f)))
|
||||
newF = f.getParent().resolve(FileUtils.getNameWithoutExtension(f));
|
||||
else
|
||||
newF = f.getParent().resolve(FileUtils.getName(f) + DISABLED_EXT);
|
||||
|
||||
try {
|
||||
Files.move(f, newF);
|
||||
Pack.this.packMcMeta = newF;
|
||||
} catch (IOException e) {
|
||||
// Mod file is occupied.
|
||||
Logging.LOG.warning("Unable to rename file " + f + " to " + newF);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public Datapack getDatapack() {
|
||||
return datapack;
|
||||
}
|
||||
|
||||
public BooleanProperty activeProperty() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active.get();
|
||||
}
|
||||
|
||||
public void setActive(boolean active) {
|
||||
this.active.set(active);
|
||||
}
|
||||
}
|
||||
|
||||
private static class PackMcMeta implements Validation {
|
||||
|
||||
@SerializedName("pack")
|
||||
private final PackInfo pack;
|
||||
|
||||
public PackMcMeta() {
|
||||
this(new PackInfo());
|
||||
}
|
||||
|
||||
public PackMcMeta(PackInfo packInfo) {
|
||||
this.pack = packInfo;
|
||||
}
|
||||
|
||||
public PackInfo getPackInfo() {
|
||||
return pack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate() throws JsonParseException {
|
||||
if (pack == null)
|
||||
throw new JsonParseException("pack cannot be null");
|
||||
}
|
||||
|
||||
public static class PackInfo {
|
||||
@SerializedName("pack_format")
|
||||
private final int packFormat;
|
||||
|
||||
@SerializedName("description")
|
||||
private final String description;
|
||||
|
||||
public PackInfo() {
|
||||
this(0, "");
|
||||
}
|
||||
|
||||
public PackInfo(int packFormat, String description) {
|
||||
this.packFormat = packFormat;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public int getPackFormat() {
|
||||
return packFormat;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final String DISABLED_EXT = ".disabled";
|
||||
}
|
@ -43,10 +43,18 @@ public final class FileUtils {
|
||||
return StringUtils.substringBeforeLast(file.getName(), '.');
|
||||
}
|
||||
|
||||
public static String getNameWithoutExtension(Path file) {
|
||||
return StringUtils.substringBeforeLast(getName(file), '.');
|
||||
}
|
||||
|
||||
public static String getExtension(File file) {
|
||||
return StringUtils.substringAfterLast(file.getName(), '.');
|
||||
}
|
||||
|
||||
public static String getExtension(Path file) {
|
||||
return StringUtils.substringAfterLast(getName(file), '.');
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is for normalizing ZipPath since Path.normalize of ZipFileSystem does not work properly.
|
||||
*/
|
||||
@ -63,11 +71,15 @@ public final class FileUtils {
|
||||
}
|
||||
|
||||
public static String readText(File file, Charset charset) throws IOException {
|
||||
return new String(readBytes(file), charset);
|
||||
return new String(Files.readAllBytes(file.toPath()), charset);
|
||||
}
|
||||
|
||||
public static byte[] readBytes(File file) throws IOException {
|
||||
return Files.readAllBytes(file.toPath());
|
||||
public static String readText(Path file) throws IOException {
|
||||
return readText(file, UTF_8);
|
||||
}
|
||||
|
||||
public static String readText(Path file, Charset charset) throws IOException {
|
||||
return new String(Files.readAllBytes(file), charset);
|
||||
}
|
||||
|
||||
public static void writeText(File file, String text) throws IOException {
|
||||
|
@ -45,6 +45,7 @@ subprojects {
|
||||
compile group: 'org.tukaani', name: 'xz', version: '1.8'
|
||||
compile group: 'org.hildan.fxgson', name: 'fx-gson', version: '3.1.0'
|
||||
compile group: 'org.jenkins-ci', name: 'constant-pool-scanner', version: '1.2'
|
||||
compile group: 'org.spacehq', name: 'opennbt', version: '1.0'
|
||||
testCompile group: 'junit', name: 'junit', version: '4.12'
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user