Skip to content

Commit

Permalink
CLI: Resolve project from the game directory
Browse files Browse the repository at this point in the history
  • Loading branch information
ShadelessFox committed Mar 15, 2024
1 parent a55dc5c commit cc82d5e
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.shade.util.NotNull;

import java.nio.file.Path;

public enum GameType {
DS("Death Stranding"),
DSDC("Death Stranding (Director's Cut)"),
Expand All @@ -13,6 +15,29 @@ 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,126 @@
import com.shade.decima.model.app.Project;
import com.shade.decima.model.app.ProjectContainer;
import com.shade.decima.model.app.ProjectManager;
import com.shade.decima.model.base.GameType;
import com.shade.platform.model.util.IOUtils;
import com.shade.util.NotImplementedException;
import com.shade.util.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import picocli.CommandLine.ITypeConverter;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID;
import java.util.stream.Stream;

public class ProjectConverter implements ITypeConverter<Project> {
private static final Logger log = LoggerFactory.getLogger(ProjectConverter.class);

@Override
public Project convert(String value) throws Exception {
final UUID id = UUID.fromString(value);
final Source source = determineSource(value);

if (source instanceof Source.FromUUID s) {
return openProject(s.id());
} else if (source instanceof Source.FromPath s) {
return createTempProject(s.path());
} else {
throw new NotImplementedException();
}
}

@NotNull
private static Project createTempProject(@NotNull Path path) throws IOException {
final ProjectManager manager = ProjectManager.getInstance();
final GameType type;
final Path oodlePath;
final Path gamePath;
final Path dataPath;

log.debug("Resolving a project from '{}'", path);

for (ProjectContainer container : manager.getProjects()) {
if (container.getId().equals(id)) {
if (Files.isSameFile(container.getExecutablePath().getParent(), path)) {
log.debug("Found existing project '{}' ({})", container.getName(), container.getId());
return manager.openProject(container);
}
}

throw new IllegalArgumentException("Can't find project '" + value + "'");
log.debug("No existing project found; creating a temporary one");
try (Stream<Path> stream = Files.list(path)) {
oodlePath = stream
.filter(p -> IOUtils.getBasename(p).startsWith("oo2core"))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Can't find oodle library in '" + path + "'"));
}

if (Files.exists(path.resolve("ds.exe"))) {
// It's not good to rely on the presence of XeFX because it wasn't there on the initial release
type = Files.exists(path.resolve("xefx.dll")) ? GameType.DSDC : GameType.DS;
gamePath = path.resolve("ds.exe");
dataPath = path.resolve("data");
} else if (Files.exists(path.resolve("horizonzerodawn.exe"))) {
type = GameType.HZD;
gamePath = path.resolve("horizonzerodawn.exe");
dataPath = path.resolve("Packed_DX12");
} else {
throw new IllegalArgumentException("Can't determine game type from '" + path + "'");
}

log.debug("Detected project type: {}", type);
log.debug("Detected data location: {}", dataPath);
log.debug("Detected oodle library: {}", oodlePath);

final ProjectContainer container = new ProjectContainer(
UUID.randomUUID(),
"CLI",
type,
gamePath,
dataPath,
oodlePath,
type.getKnownRttiTypesPath(),
type.getKnownFileListingsPath()
);

manager.addProject(container);

return manager.openProject(container);
}

@NotNull
private static Project openProject(@NotNull UUID id) throws IOException {
final ProjectManager manager = ProjectManager.getInstance();
final ProjectContainer container = manager.getProject(id);

if (container == null) {
throw new IllegalArgumentException("Can't find project '" + id + "'");
}

log.debug("Found project '{}' ({})", container.getName(), container.getId());

return manager.openProject(container);
}

@NotNull
private static Source determineSource(@NotNull String value) {
try {
return new Source.FromUUID(UUID.fromString(value));
} catch (Exception ignored) {
}

try {
return new Source.FromPath(Path.of(value));
} catch (Exception ignored) {
}

throw new IllegalArgumentException("Can't determine project source from '" + value + "'");
}

private sealed interface Source {
record FromUUID(@NotNull UUID id) implements Source {}

record FromPath(@NotNull Path path) implements Source {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -257,11 +257,8 @@ public boolean isComplete() {
}

private void fillValuesBasedOnGameType(@NotNull GameType oldType, @NotNull GameType newType) {
final KnownValues oldValues = KnownValues.of(oldType);
final KnownValues newValues = KnownValues.of(newType);

setIfEmptyOrOldValue(rttiInfoFilePath, oldValues.rttiInfo, newValues.rttiInfo);
setIfEmptyOrOldValue(fileListingsPath, oldValues.fileListings, newValues.fileListings);
setIfEmptyOrOldValue(rttiInfoFilePath, oldType.getKnownRttiTypesPath(), newType.getKnownRttiTypesPath());
setIfEmptyOrOldValue(fileListingsPath, oldType.getKnownFileListingsPath(), newType.getKnownFileListingsPath());
}

private void fillValuesBasedOnGameExecutable(@NotNull Path path) {
Expand Down Expand Up @@ -293,30 +290,4 @@ private static void setIfEmptyOrOldValue(@NotNull JTextComponent component, @Not

component.setText(newText);
}

// 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
private record KnownValues(@NotNull Path rttiInfo, @NotNull Path archiveInfo, @NotNull Path fileListings) {
@NotNull
public static KnownValues of(@NotNull GameType type) {
return switch (type) {
case DS -> new KnownValues(
Path.of("data/ds_types.json.gz"),
Path.of("data/ds_archives.json.gz"),
Path.of("data/ds_paths.txt.gz")
);
case DSDC -> new KnownValues(
Path.of("data/dsdc_types.json.gz"),
Path.of("data/dsdc_archives.json.gz"),
Path.of("data/dsdc_paths.txt.gz")
);
case HZD -> new KnownValues(
Path.of("data/hzd_types.json.gz"),
Path.of("data/hzd_archives.json.gz"),
Path.of("data/hzd_paths.txt.gz")
);
};
}
}
}

0 comments on commit cc82d5e

Please sign in to comment.