From d65138050dc9ea169571f8d5a09b02a3dd483d7b Mon Sep 17 00:00:00 2001 From: ShadelessFox Date: Mon, 3 Jun 2024 21:51:58 +0200 Subject: [PATCH] Application: Embed type metadata and file paths --- .../com/shade/decima/model/app/Project.java | 30 ++------- .../decima/model/app/ProjectContainer.java | 50 ++++++++------- .../com/shade/decima/model/base/GameType.java | 25 -------- .../providers/ExternalTypeProvider.java | 2 +- .../main/resources/metadata}/ds_paths.txt.gz | Bin .../main/resources/metadata}/ds_types.json.gz | Bin .../resources/metadata}/dsdc_paths.txt.gz | Bin .../resources/metadata}/dsdc_types.json.gz | Bin .../main/resources/metadata}/hzd_paths.txt.gz | Bin .../resources/metadata}/hzd_types.json.gz | Bin .../decima/cli/commands/ProjectsList.java | 4 +- .../cli/converters/ProjectConverter.java | 4 +- .../decima/ui/dialogs/ProjectEditDialog.java | 57 +----------------- .../shade/decima/ui/menu/menus/FileMenu.java | 2 +- .../shade/platform/model/util/IOUtils.java | 29 ++++++--- 15 files changed, 58 insertions(+), 145 deletions(-) rename {data => modules/decima-model/src/main/resources/metadata}/ds_paths.txt.gz (100%) rename {data => modules/decima-model/src/main/resources/metadata}/ds_types.json.gz (100%) rename {data => modules/decima-model/src/main/resources/metadata}/dsdc_paths.txt.gz (100%) rename {data => modules/decima-model/src/main/resources/metadata}/dsdc_types.json.gz (100%) rename {data => modules/decima-model/src/main/resources/metadata}/hzd_paths.txt.gz (100%) rename {data => modules/decima-model/src/main/resources/metadata}/hzd_types.json.gz (100%) diff --git a/modules/decima-model/src/main/java/com/shade/decima/model/app/Project.java b/modules/decima-model/src/main/java/com/shade/decima/model/app/Project.java index d164cb06f..1e89ccd2b 100644 --- a/modules/decima-model/src/main/java/com/shade/decima/model/app/Project.java +++ b/modules/decima-model/src/main/java/com/shade/decima/model/app/Project.java @@ -23,8 +23,6 @@ import java.io.BufferedReader; import java.io.Closeable; import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Path; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -96,13 +94,11 @@ public Compressor getCompressor() { @NotNull public Stream listAllFiles() throws IOException { - final Path fileListingsPath = container.getFileListingsPath(); + final BufferedReader reader = container.getFilePaths(); - if (fileListingsPath != null) { - return getListedFiles(fileListingsPath); - } else { - return getPrefetchFiles(); - } + return reader + .lines() + .onClose(IOUtils.asUncheckedRunnable(reader)); } @NotNull @@ -149,24 +145,6 @@ public void close() throws IOException { compressor.close(); } - @NotNull - private Stream getListedFiles(@NotNull Path path) throws IOException { - final BufferedReader reader = IOUtils.newCompressedReader(path); - - try { - return reader.lines().onClose(() -> { - try { - reader.close(); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }); - } catch (Exception e) { - reader.close(); - throw e; - } - } - @NotNull private Stream getPrefetchFiles() throws IOException { final RTTIObject list = getPrefetchList(); diff --git a/modules/decima-model/src/main/java/com/shade/decima/model/app/ProjectContainer.java b/modules/decima-model/src/main/java/com/shade/decima/model/app/ProjectContainer.java index 66ed60a0b..1bc1879ca 100644 --- a/modules/decima-model/src/main/java/com/shade/decima/model/app/ProjectContainer.java +++ b/modules/decima-model/src/main/java/com/shade/decima/model/app/ProjectContainer.java @@ -1,9 +1,12 @@ package com.shade.decima.model.app; import com.shade.decima.model.base.GameType; +import com.shade.platform.model.util.IOUtils; import com.shade.util.NotNull; -import com.shade.util.Nullable; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; import java.nio.file.Path; import java.util.UUID; @@ -16,18 +19,13 @@ public class ProjectContainer { private Path packfilesPath; private Path compressorPath; - private Path typeMetadataPath; - private Path fileListingsPath; - public ProjectContainer( @NotNull UUID id, @NotNull String name, @NotNull GameType type, @NotNull Path executablePath, @NotNull Path packfilesPath, - @NotNull Path compressorPath, - @NotNull Path typeMetadataPath, - @Nullable Path fileListingsPath + @NotNull Path compressorPath ) { this.id = id; this.name = name; @@ -35,8 +33,6 @@ public ProjectContainer( this.executablePath = executablePath; this.packfilesPath = packfilesPath; this.compressorPath = compressorPath; - this.typeMetadataPath = typeMetadataPath; - this.fileListingsPath = fileListingsPath; } @NotNull @@ -90,21 +86,31 @@ public void setPackfilesPath(@NotNull Path packfilesPath) { } @NotNull - public Path getTypeMetadataPath() { - return typeMetadataPath; - } - - public void setTypeMetadataPath(@NotNull Path typeMetadataPath) { - this.typeMetadataPath = typeMetadataPath; + public BufferedReader getTypeMetadata() throws IOException { + final String path = switch (type) { + case DS -> "metadata/ds_types.json.gz"; + case DSDC -> "metadata/dsdc_types.json.gz"; + case HZD -> "metadata/hzd_types.json.gz"; + }; + final InputStream is = type.getClass().getClassLoader().getResourceAsStream(path); + if (is == null) { + throw new IllegalStateException("Internal error: failed to locate file containing type metadata for " + type); + } + return IOUtils.newCompressedReader(is); } - @Nullable - public Path getFileListingsPath() { - return fileListingsPath; - } - - public void setFileListingsPath(@Nullable Path fileListingsPath) { - this.fileListingsPath = fileListingsPath; + @NotNull + public BufferedReader getFilePaths() throws IOException { + final String path = switch (type) { + case DS -> "metadata/ds_paths.txt.gz"; + case DSDC -> "metadata/dsdc_paths.txt.gz"; + case HZD -> "metadata/hzd_paths.txt.gz"; + }; + final InputStream is = type.getClass().getClassLoader().getResourceAsStream(path); + if (is == null) { + throw new IllegalStateException("Internal error: failed to locate file containing file paths for " + type); + } + return IOUtils.newCompressedReader(is); } @Override diff --git a/modules/decima-model/src/main/java/com/shade/decima/model/base/GameType.java b/modules/decima-model/src/main/java/com/shade/decima/model/base/GameType.java index 16882f1a1..b803f3f74 100644 --- a/modules/decima-model/src/main/java/com/shade/decima/model/base/GameType.java +++ b/modules/decima-model/src/main/java/com/shade/decima/model/base/GameType.java @@ -2,8 +2,6 @@ import com.shade.util.NotNull; -import java.nio.file.Path; - public enum GameType { DS("Death Stranding"), DSDC("Death Stranding (Director's Cut)"), @@ -15,29 +13,6 @@ public enum GameType { this.name = name; } - // Note regarding "known" values: - // We do know that these files exist, and we may use this information wisely. - // I'd like to embed these files in resources and choose based on the selected - // game type, but it might be useful to be able to specify custom values for these - - @NotNull - public Path getKnownRttiTypesPath() { - return switch (this) { - case DS -> Path.of("data/ds_types.json.gz"); - case DSDC -> Path.of("data/dsdc_types.json.gz"); - case HZD -> Path.of("data/hzd_types.json.gz"); - }; - } - - @NotNull - public Path getKnownFileListingsPath() { - return switch (this) { - case DS -> Path.of("data/ds_paths.txt.gz"); - case DSDC -> Path.of("data/dsdc_paths.txt.gz"); - case HZD -> Path.of("data/hzd_paths.txt.gz"); - }; - } - @Override public String toString() { return name; diff --git a/modules/decima-model/src/main/java/com/shade/decima/model/rtti/registry/providers/ExternalTypeProvider.java b/modules/decima-model/src/main/java/com/shade/decima/model/rtti/registry/providers/ExternalTypeProvider.java index 77ddd3639..4427f9fc8 100644 --- a/modules/decima-model/src/main/java/com/shade/decima/model/rtti/registry/providers/ExternalTypeProvider.java +++ b/modules/decima-model/src/main/java/com/shade/decima/model/rtti/registry/providers/ExternalTypeProvider.java @@ -34,7 +34,7 @@ public class ExternalTypeProvider implements RTTITypeProvider { @SuppressWarnings("unchecked") @Override public void initialize(@NotNull RTTITypeRegistry registry, @NotNull ProjectContainer container) throws IOException { - try (Reader reader = IOUtils.newCompressedReader(container.getTypeMetadataPath())) { + try (Reader reader = container.getTypeMetadata()) { declarations.putAll(new Gson().fromJson(reader, Map.class)); } diff --git a/data/ds_paths.txt.gz b/modules/decima-model/src/main/resources/metadata/ds_paths.txt.gz similarity index 100% rename from data/ds_paths.txt.gz rename to modules/decima-model/src/main/resources/metadata/ds_paths.txt.gz diff --git a/data/ds_types.json.gz b/modules/decima-model/src/main/resources/metadata/ds_types.json.gz similarity index 100% rename from data/ds_types.json.gz rename to modules/decima-model/src/main/resources/metadata/ds_types.json.gz diff --git a/data/dsdc_paths.txt.gz b/modules/decima-model/src/main/resources/metadata/dsdc_paths.txt.gz similarity index 100% rename from data/dsdc_paths.txt.gz rename to modules/decima-model/src/main/resources/metadata/dsdc_paths.txt.gz diff --git a/data/dsdc_types.json.gz b/modules/decima-model/src/main/resources/metadata/dsdc_types.json.gz similarity index 100% rename from data/dsdc_types.json.gz rename to modules/decima-model/src/main/resources/metadata/dsdc_types.json.gz diff --git a/data/hzd_paths.txt.gz b/modules/decima-model/src/main/resources/metadata/hzd_paths.txt.gz similarity index 100% rename from data/hzd_paths.txt.gz rename to modules/decima-model/src/main/resources/metadata/hzd_paths.txt.gz diff --git a/data/hzd_types.json.gz b/modules/decima-model/src/main/resources/metadata/hzd_types.json.gz similarity index 100% rename from data/hzd_types.json.gz rename to modules/decima-model/src/main/resources/metadata/hzd_types.json.gz diff --git a/modules/decima-ui/src/main/java/com/shade/decima/cli/commands/ProjectsList.java b/modules/decima-ui/src/main/java/com/shade/decima/cli/commands/ProjectsList.java index bb9318a88..e4a40c8da 100644 --- a/modules/decima-ui/src/main/java/com/shade/decima/cli/commands/ProjectsList.java +++ b/modules/decima-ui/src/main/java/com/shade/decima/cli/commands/ProjectsList.java @@ -20,9 +20,7 @@ public void run() { "Type: " + container.getType() + '\n' + "ExecutablePath: " + container.getExecutablePath() + '\n' + "CompressorPath: " + container.getCompressorPath() + '\n' + - "PackfilesPath: " + container.getPackfilesPath() + '\n' + - "TypeMetadataPath: " + container.getTypeMetadataPath() + '\n' + - "FileListingsPath: " + container.getFileListingsPath() + '\n' + "PackfilesPath: " + container.getPackfilesPath() + '\n' // @formatter:on ); } diff --git a/modules/decima-ui/src/main/java/com/shade/decima/cli/converters/ProjectConverter.java b/modules/decima-ui/src/main/java/com/shade/decima/cli/converters/ProjectConverter.java index c0e35900d..5d7b825c2 100644 --- a/modules/decima-ui/src/main/java/com/shade/decima/cli/converters/ProjectConverter.java +++ b/modules/decima-ui/src/main/java/com/shade/decima/cli/converters/ProjectConverter.java @@ -81,9 +81,7 @@ private static Project createTempProject(@NotNull Path path) throws IOException type, gamePath, dataPath, - oodlePath, - type.getKnownRttiTypesPath(), - type.getKnownFileListingsPath() + oodlePath ); manager.addProject(container); diff --git a/modules/decima-ui/src/main/java/com/shade/decima/ui/dialogs/ProjectEditDialog.java b/modules/decima-ui/src/main/java/com/shade/decima/ui/dialogs/ProjectEditDialog.java index 72e99773e..1193c1c60 100644 --- a/modules/decima-ui/src/main/java/com/shade/decima/ui/dialogs/ProjectEditDialog.java +++ b/modules/decima-ui/src/main/java/com/shade/decima/ui/dialogs/ProjectEditDialog.java @@ -34,8 +34,6 @@ public class ProjectEditDialog extends BaseEditDialog { private final JTextField archiveFolderPath; private final JTextField compressorPath; private final ColoredComponent compressorNote; - private final JTextField rttiInfoFilePath; - private final JTextField fileListingsPath; public ProjectEditDialog(boolean persisted, boolean editable) { super(persisted ? "Edit Project" : "New Project"); @@ -50,7 +48,6 @@ public ProjectEditDialog(boolean persisted, boolean editable) { this.projectType = new JComboBox<>(GameType.values()); this.projectType.setEnabled(editable); - this.projectType.addItemListener(e -> fillValuesBasedOnGameType((GameType) e.getItem(), projectType.getItemAt(projectType.getSelectedIndex()))); this.executableFilePath = new JTextField(); this.executableFilePath.setEnabled(editable); @@ -66,12 +63,6 @@ public ProjectEditDialog(boolean persisted, boolean editable) { this.compressorPath = new JTextField(); this.compressorPath.setEnabled(editable); - this.rttiInfoFilePath = new JTextField(); - this.rttiInfoFilePath.setEnabled(editable); - - this.fileListingsPath = new JTextField(); - this.fileListingsPath.setEnabled(editable); - this.compressorNote = new ColoredComponent(); this.compressorNote.setVisible(false); this.compressorPath.getDocument().addDocumentListener((DocumentAdapter) e -> { @@ -93,8 +84,6 @@ public ProjectEditDialog(boolean persisted, boolean editable) { }); if (!persisted) { - projectType.addItemListener(e -> fillValuesBasedOnGameType((GameType) e.getItem(), projectType.getItemAt(projectType.getSelectedIndex()))); - executableFilePath.getDocument().addDocumentListener((DocumentAdapter) e -> { if (UIUtils.isValid(executableFilePath)) { fillValuesBasedOnGameExecutable(Path.of(executableFilePath.getText())); @@ -112,7 +101,7 @@ protected JComponent createContentsPane() { panel.add(new LabeledSeparator("Project"), "span,wrap"); if (persisted) { - panel.add(new JLabel("UUID:"), "gap ind"); + panel.add(new JLabel("Id:"), "gap ind"); panel.add(projectId, "wrap"); UIUtils.addCopyAction(projectId); @@ -126,7 +115,7 @@ protected JComponent createContentsPane() { } { - panel.add(new JLabel("Type:"), "gap ind"); + panel.add(new JLabel("Game:"), "gap ind"); panel.add(projectType, "wrap"); } @@ -170,38 +159,6 @@ protected JComponent createContentsPane() { UIUtils.installInputValidator(compressorPath, new ExistingFileValidator(compressorPath, filter), this); } - panel.add(new LabeledSeparator("Metadata"), "span,wrap"); - - { - final FileExtensionFilter filter = new FileExtensionFilter("RTTI information", "json", "json.gz"); - - final JLabel label = new JLabel("Type information:"); - label.setToolTipText("Path to a file containing information about all data types found in game files."); - - panel.add(label, "gap ind"); - panel.add(rttiInfoFilePath, "wrap"); - - UIUtils.addOpenFileAction(rttiInfoFilePath, "Select RTTI information file", filter); - UIUtils.installInputValidator(rttiInfoFilePath, new ExistingFileValidator(rttiInfoFilePath, filter), this); - } - - { - final FileExtensionFilter filter = new FileExtensionFilter("File listings", "txt", "txt.gz"); - - final JLabel label = new JLabel("File listings:"); - label.setToolTipText("Path to a file containing information about the complete list of files.
This file is not required, but all projects will benefit from it as it includes files that can't be normally seen
under their original names (instead, you would see a bunch of files under the <unnamed> folder in the navigator tree)"); - - panel.add(label, "gap ind"); - panel.add(fileListingsPath, "wrap"); - - UIUtils.addOpenFileAction(fileListingsPath, "Select file containing file listings", filter); - UIUtils.installInputValidator(fileListingsPath, new ExistingFileValidator(fileListingsPath, filter, false), this); - } - - if (!persisted) { - fillValuesBasedOnGameType(projectType.getItemAt(0), projectType.getItemAt(0)); - } - return panel; } @@ -231,8 +188,6 @@ public void load(@NotNull ProjectContainer container) { executableFilePath.setText(container.getExecutablePath().toString()); archiveFolderPath.setText(container.getPackfilesPath().toString()); compressorPath.setText(container.getCompressorPath().toString()); - rttiInfoFilePath.setText(container.getTypeMetadataPath().toString()); - fileListingsPath.setText(container.getFileListingsPath() == null ? null : container.getFileListingsPath().toString()); } public void save(@NotNull ProjectContainer container) { @@ -241,8 +196,6 @@ public void save(@NotNull ProjectContainer container) { container.setExecutablePath(Path.of(executableFilePath.getText())); container.setPackfilesPath(Path.of(archiveFolderPath.getText())); container.setCompressorPath(Path.of(compressorPath.getText())); - container.setTypeMetadataPath(Path.of(rttiInfoFilePath.getText())); - container.setFileListingsPath(fileListingsPath.getText().isEmpty() ? null : Path.of(fileListingsPath.getText())); } @Override @@ -250,15 +203,9 @@ public boolean isComplete() { return UIUtils.isValid(projectName) && UIUtils.isValid(executableFilePath) && UIUtils.isValid(archiveFolderPath) - && UIUtils.isValid(rttiInfoFilePath) && UIUtils.isValid(compressorPath); } - private void fillValuesBasedOnGameType(@NotNull GameType oldType, @NotNull GameType newType) { - setIfEmptyOrOldValue(rttiInfoFilePath, oldType.getKnownRttiTypesPath(), newType.getKnownRttiTypesPath()); - setIfEmptyOrOldValue(fileListingsPath, oldType.getKnownFileListingsPath(), newType.getKnownFileListingsPath()); - } - private void fillValuesBasedOnGameExecutable(@NotNull Path path) { final String newFilename = IOUtils.getBasename(path).toLowerCase(Locale.ROOT); diff --git a/modules/decima-ui/src/main/java/com/shade/decima/ui/menu/menus/FileMenu.java b/modules/decima-ui/src/main/java/com/shade/decima/ui/menu/menus/FileMenu.java index e8adb5c58..a31e1fa08 100644 --- a/modules/decima-ui/src/main/java/com/shade/decima/ui/menu/menus/FileMenu.java +++ b/modules/decima-ui/src/main/java/com/shade/decima/ui/menu/menus/FileMenu.java @@ -43,7 +43,7 @@ public static class NewProjectItem extends MenuItem { @Override public void perform(@NotNull MenuItemContext ctx) { final ProjectEditDialog dialog = new ProjectEditDialog(false, true); - final ProjectContainer container = new ProjectContainer(UUID.randomUUID(), "New project", GameType.values()[0], Path.of(""), Path.of(""), Path.of(""), Path.of(""), Path.of("")); + final ProjectContainer container = new ProjectContainer(UUID.randomUUID(), "New project", GameType.values()[0], Path.of(""), Path.of(""), Path.of("")); dialog.load(container); diff --git a/modules/platform-model/src/main/java/com/shade/platform/model/util/IOUtils.java b/modules/platform-model/src/main/java/com/shade/platform/model/util/IOUtils.java index fc37beb3f..04e46f722 100644 --- a/modules/platform-model/src/main/java/com/shade/platform/model/util/IOUtils.java +++ b/modules/platform-model/src/main/java/com/shade/platform/model/util/IOUtils.java @@ -75,22 +75,22 @@ private static String getExtension(@NotNull String filename, boolean lastPartOnl } @NotNull - public static BufferedReader newCompressedReader(@NotNull Path path) throws IOException { - return new BufferedReader(new InputStreamReader(newCompressedInputStream(path), StandardCharsets.UTF_8)); + public static BufferedReader newCompressedReader(@NotNull InputStream is) throws IOException { + return new BufferedReader(new InputStreamReader(newCompressedInputStream(is), StandardCharsets.UTF_8)); } @NotNull - public static InputStream newCompressedInputStream(@NotNull Path path) throws IOException { - final InputStream is = new BufferedInputStream(Files.newInputStream(path)); + public static InputStream newCompressedInputStream(@NotNull InputStream is) throws IOException { + final InputStream bis = new BufferedInputStream(is); - is.mark(2); - final int magic = is.read() | is.read() << 8; - is.reset(); + bis.mark(2); + final int magic = bis.read() | bis.read() << 8; + bis.reset(); if (magic == GZIPInputStream.GZIP_MAGIC) { - return new GZIPInputStream(is); + return new GZIPInputStream(bis); } else { - return is; + return bis; } } @@ -277,6 +277,17 @@ public static T unchecked(@NotNull Callable supplier) { } } + @NotNull + public static Runnable asUncheckedRunnable(@NotNull Closeable c) { + return () -> { + try { + c.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }; + } + @SuppressWarnings("unchecked") public static T sneakyThrow(@NotNull Throwable throwable) throws E { throw (E) throwable;