From fd03900219ee8f97311f4ccb07de91109d293f45 Mon Sep 17 00:00:00 2001 From: emd4600 Date: Sat, 6 Jan 2024 22:49:57 +0100 Subject: [PATCH] Add `scan-simulator` console command, which shows attributes and their file offsets of a simulator data file. This can help editing save game data. --- src/sporemodder/HashManager.java | 1 + src/sporemodder/Launcher.java | 23 +++- .../file/simulator/SimulatorClass.java | 111 ++++++++++++++---- 3 files changed, 110 insertions(+), 25 deletions(-) diff --git a/src/sporemodder/HashManager.java b/src/sporemodder/HashManager.java index ebc659f..6224559 100644 --- a/src/sporemodder/HashManager.java +++ b/src/sporemodder/HashManager.java @@ -95,6 +95,7 @@ public void initialize(Properties properties) { UIManager.get().tryAction(() -> { simulatorRegistry.read(PathManager.get().getProgramFile(simulatorRegistry.getFileName())); + simulatorRegistry.read(PathManager.get().getProgramFile("reg_simulator_stub.txt")); }, "The simulator attributes registry (reg_simulator.txt) is corrupt or missing."); CnvUnit.loadNameRegistry(); diff --git a/src/sporemodder/Launcher.java b/src/sporemodder/Launcher.java index 574ca7b..86efcf9 100644 --- a/src/sporemodder/Launcher.java +++ b/src/sporemodder/Launcher.java @@ -2,6 +2,7 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; @@ -19,8 +20,10 @@ import sporemodder.file.dbpf.DBPFPackingTask; import sporemodder.file.dbpf.DBPFUnpackingTask; import sporemodder.file.filestructures.FileStream; +import sporemodder.file.filestructures.MemoryStream; import sporemodder.file.filestructures.StreamReader; import sporemodder.file.filestructures.StreamWriter; +import sporemodder.file.simulator.SimulatorClass; import sporemodder.file.spui.SporeUserInterface; import sporemodder.util.NameRegistry; @@ -60,7 +63,8 @@ public String[] getVersion() throws Exception { EncodeCommand.class, UnpackCommand.class, PackCommand.class, - FindSpuiForControlId.class + FindSpuiForControlIdCommand.class, + ScanSimulatorCommand.class }) public static class SMFXCommand implements Callable { @@ -393,7 +397,7 @@ public Integer call() throws Exception { } @Command(name = "find-spui", description = "Find all SPUIs that have a specific control ID", mixinStandardHelpOptions = true) - public static class FindSpuiForControlId implements Callable { + public static class FindSpuiForControlIdCommand implements Callable { @Parameters(index = "0", description = "The program will look all .spui files in this folder and subfolders") private File inputFolder; @@ -407,4 +411,19 @@ public Integer call() throws Exception { return 0; } } + + @Command(name = "scan-simulator", description = "Scan file offsets of attributes in simulator data files", mixinStandardHelpOptions = true) + public static class ScanSimulatorCommand implements Callable { + @Parameters(index = "0", description = "Input simulator data file") + private File inputFile; + + @Override + public Integer call() throws Exception { + try (StreamReader stream = new MemoryStream(Files.readAllBytes(inputFile.toPath()))) { + SimulatorClass.scanClasses(stream); + } + + return 0; + } + } } diff --git a/src/sporemodder/file/simulator/SimulatorClass.java b/src/sporemodder/file/simulator/SimulatorClass.java index 03a87e9..80bde1c 100644 --- a/src/sporemodder/file/simulator/SimulatorClass.java +++ b/src/sporemodder/file/simulator/SimulatorClass.java @@ -18,18 +18,37 @@ ****************************************************************************/ package sporemodder.file.simulator; +import java.io.IOException; +import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import sporemodder.file.filestructures.StreamReader; import sporemodder.file.filestructures.StreamWriter; import sporemodder.HashManager; import sporemodder.file.simulator.attributes.SimulatorAttribute; +import sporemodder.util.IntPair; + +import javax.management.Attribute; public abstract class SimulatorClass { + public static int GENERIC_SERIALIZER_ID = 0x1A80D26; + + public class AttributeHeader { + public int id; + public int size; + + public AttributeHeader(int id, int size) { + this.id = id; + this.size = size; + } + } + protected final LinkedHashMap attributes = new LinkedHashMap(); protected int classID; + protected int classSize; public SimulatorClass(int classID) { this.classID = classID; @@ -42,6 +61,9 @@ public SimulatorClass(int classID) { public int getClassID() { return classID; } + public int getClassSize() { + return classSize; + } /** * Returns the correct attribute type for the given attribute name. This must be implemented in subclasses to @@ -60,33 +82,31 @@ public int calculateSize() { return size; } - - public void read(StreamReader stream) throws Exception { - + + public List readHeader(StreamReader stream) throws IOException { classID = stream.readInt(); - - // size - stream.readInt(); - + classSize = stream.readInt(); + int count = stream.readInt(); - int[] ids = new int[count]; - int[] sizes = new int[count]; - - HashManager hasher = HashManager.get(); - - for (int i = 0; i < count; i++) { - ids[i] = stream.readInt(); - sizes[i] = stream.readInt(); - } - + List headers = new ArrayList<>(); for (int i = 0; i < count; i++) { - String name = hasher.getSimulatorName(ids[i]); - - SimulatorAttribute attribute = createAttribute(name); - attribute.read(stream, sizes[i]); - - setAttribute(name, attribute); + headers.add(new AttributeHeader(stream.readInt(), stream.readInt())); } + return headers; + } + + public void read(StreamReader stream) throws Exception { + List headers = readHeader(stream); + + HashManager hasher = HashManager.get(); + for (AttributeHeader header : headers) { + String name = hasher.getSimulatorName(header.id); + + SimulatorAttribute attribute = createAttribute(name); + attribute.read(stream, header.size); + + setAttribute(name, attribute); + } } public void write(StreamWriter stream) throws Exception { @@ -168,4 +188,49 @@ public void printXML(StringBuilder sb, String tabulation) { // default: return null; // } // } + + public static class UnknownSimulatorClass extends SimulatorClass { + public UnknownSimulatorClass(int classID) { + super(classID); + } + + @Override + public SimulatorAttribute createAttribute(String name) { + return null; + } + } + + public static void scanClasses(StreamReader stream) throws IOException { + while (stream.getFilePointer() + 4 < stream.length()) { + int testValue = stream.readInt(); + if (testValue == GENERIC_SERIALIZER_ID) { + stream.seek(stream.getFilePointer() - 4); + SimulatorClass simulatorClass = new UnknownSimulatorClass(GENERIC_SERIALIZER_ID); + long classOffset = stream.getFilePointer(); + List headers = simulatorClass.readHeader(stream); + long offset = stream.getFilePointer(); + + System.out.println("Found class at offset " + classOffset + ", attributes:"); + for (AttributeHeader header : headers) { + StringBuilder sb = new StringBuilder(); + sb.append(" "); + sb.append(HashManager.get().hexToString(header.id)); + String attributeName = HashManager.get().getSimulatorName(header.id); + if (attributeName != null) { + sb.append(" "); + sb.append(attributeName); + } + sb.append(": offset = "); + sb.append(offset); + sb.append(" size = "); + sb.append(header.size); + System.out.println(sb.toString()); + + offset += header.size; + } + System.out.println(); + stream.seek(offset); + } + } + } }