Skip to content

Commit fd03900

Browse files
committed
Add scan-simulator console command, which shows attributes and their file offsets of a simulator data file. This can help editing save game data.
1 parent e6b1e86 commit fd03900

File tree

3 files changed

+110
-25
lines changed

3 files changed

+110
-25
lines changed

src/sporemodder/HashManager.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public void initialize(Properties properties) {
9595

9696
UIManager.get().tryAction(() -> {
9797
simulatorRegistry.read(PathManager.get().getProgramFile(simulatorRegistry.getFileName()));
98+
simulatorRegistry.read(PathManager.get().getProgramFile("reg_simulator_stub.txt"));
9899
}, "The simulator attributes registry (reg_simulator.txt) is corrupt or missing.");
99100

100101
CnvUnit.loadNameRegistry();

src/sporemodder/Launcher.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.io.File;
44
import java.io.IOException;
5+
import java.nio.file.Files;
56
import java.nio.file.Path;
67
import java.util.ArrayList;
78
import java.util.List;
@@ -19,8 +20,10 @@
1920
import sporemodder.file.dbpf.DBPFPackingTask;
2021
import sporemodder.file.dbpf.DBPFUnpackingTask;
2122
import sporemodder.file.filestructures.FileStream;
23+
import sporemodder.file.filestructures.MemoryStream;
2224
import sporemodder.file.filestructures.StreamReader;
2325
import sporemodder.file.filestructures.StreamWriter;
26+
import sporemodder.file.simulator.SimulatorClass;
2427
import sporemodder.file.spui.SporeUserInterface;
2528
import sporemodder.util.NameRegistry;
2629

@@ -60,7 +63,8 @@ public String[] getVersion() throws Exception {
6063
EncodeCommand.class,
6164
UnpackCommand.class,
6265
PackCommand.class,
63-
FindSpuiForControlId.class
66+
FindSpuiForControlIdCommand.class,
67+
ScanSimulatorCommand.class
6468
})
6569
public static class SMFXCommand implements Callable<Integer> {
6670

@@ -393,7 +397,7 @@ public Integer call() throws Exception {
393397
}
394398

395399
@Command(name = "find-spui", description = "Find all SPUIs that have a specific control ID", mixinStandardHelpOptions = true)
396-
public static class FindSpuiForControlId implements Callable<Integer> {
400+
public static class FindSpuiForControlIdCommand implements Callable<Integer> {
397401
@Parameters(index = "0", description = "The program will look all .spui files in this folder and subfolders")
398402
private File inputFolder;
399403

@@ -407,4 +411,19 @@ public Integer call() throws Exception {
407411
return 0;
408412
}
409413
}
414+
415+
@Command(name = "scan-simulator", description = "Scan file offsets of attributes in simulator data files", mixinStandardHelpOptions = true)
416+
public static class ScanSimulatorCommand implements Callable<Integer> {
417+
@Parameters(index = "0", description = "Input simulator data file")
418+
private File inputFile;
419+
420+
@Override
421+
public Integer call() throws Exception {
422+
try (StreamReader stream = new MemoryStream(Files.readAllBytes(inputFile.toPath()))) {
423+
SimulatorClass.scanClasses(stream);
424+
}
425+
426+
return 0;
427+
}
428+
}
410429
}

src/sporemodder/file/simulator/SimulatorClass.java

Lines changed: 88 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,37 @@
1818
****************************************************************************/
1919
package sporemodder.file.simulator;
2020

21+
import java.io.IOException;
22+
import java.util.ArrayList;
2123
import java.util.LinkedHashMap;
24+
import java.util.List;
2225
import java.util.Map;
2326

2427
import sporemodder.file.filestructures.StreamReader;
2528
import sporemodder.file.filestructures.StreamWriter;
2629
import sporemodder.HashManager;
2730
import sporemodder.file.simulator.attributes.SimulatorAttribute;
31+
import sporemodder.util.IntPair;
32+
33+
import javax.management.Attribute;
2834

2935
public abstract class SimulatorClass {
3036

37+
public static int GENERIC_SERIALIZER_ID = 0x1A80D26;
38+
39+
public class AttributeHeader {
40+
public int id;
41+
public int size;
42+
43+
public AttributeHeader(int id, int size) {
44+
this.id = id;
45+
this.size = size;
46+
}
47+
}
48+
3149
protected final LinkedHashMap<String, SimulatorAttribute> attributes = new LinkedHashMap<String, SimulatorAttribute>();
3250
protected int classID;
51+
protected int classSize;
3352

3453
public SimulatorClass(int classID) {
3554
this.classID = classID;
@@ -42,6 +61,9 @@ public SimulatorClass(int classID) {
4261
public int getClassID() {
4362
return classID;
4463
}
64+
public int getClassSize() {
65+
return classSize;
66+
}
4567

4668
/**
4769
* Returns the correct attribute type for the given attribute name. This must be implemented in subclasses to
@@ -60,33 +82,31 @@ public int calculateSize() {
6082

6183
return size;
6284
}
63-
64-
public void read(StreamReader stream) throws Exception {
65-
85+
86+
public List<AttributeHeader> readHeader(StreamReader stream) throws IOException {
6687
classID = stream.readInt();
67-
68-
// size
69-
stream.readInt();
70-
88+
classSize = stream.readInt();
89+
7190
int count = stream.readInt();
72-
int[] ids = new int[count];
73-
int[] sizes = new int[count];
74-
75-
HashManager hasher = HashManager.get();
76-
77-
for (int i = 0; i < count; i++) {
78-
ids[i] = stream.readInt();
79-
sizes[i] = stream.readInt();
80-
}
81-
91+
List<AttributeHeader> headers = new ArrayList<>();
8292
for (int i = 0; i < count; i++) {
83-
String name = hasher.getSimulatorName(ids[i]);
84-
85-
SimulatorAttribute attribute = createAttribute(name);
86-
attribute.read(stream, sizes[i]);
87-
88-
setAttribute(name, attribute);
93+
headers.add(new AttributeHeader(stream.readInt(), stream.readInt()));
8994
}
95+
return headers;
96+
}
97+
98+
public void read(StreamReader stream) throws Exception {
99+
List<AttributeHeader> headers = readHeader(stream);
100+
101+
HashManager hasher = HashManager.get();
102+
for (AttributeHeader header : headers) {
103+
String name = hasher.getSimulatorName(header.id);
104+
105+
SimulatorAttribute attribute = createAttribute(name);
106+
attribute.read(stream, header.size);
107+
108+
setAttribute(name, attribute);
109+
}
90110
}
91111

92112
public void write(StreamWriter stream) throws Exception {
@@ -168,4 +188,49 @@ public void printXML(StringBuilder sb, String tabulation) {
168188
// default: return null;
169189
// }
170190
// }
191+
192+
public static class UnknownSimulatorClass extends SimulatorClass {
193+
public UnknownSimulatorClass(int classID) {
194+
super(classID);
195+
}
196+
197+
@Override
198+
public SimulatorAttribute createAttribute(String name) {
199+
return null;
200+
}
201+
}
202+
203+
public static void scanClasses(StreamReader stream) throws IOException {
204+
while (stream.getFilePointer() + 4 < stream.length()) {
205+
int testValue = stream.readInt();
206+
if (testValue == GENERIC_SERIALIZER_ID) {
207+
stream.seek(stream.getFilePointer() - 4);
208+
SimulatorClass simulatorClass = new UnknownSimulatorClass(GENERIC_SERIALIZER_ID);
209+
long classOffset = stream.getFilePointer();
210+
List<AttributeHeader> headers = simulatorClass.readHeader(stream);
211+
long offset = stream.getFilePointer();
212+
213+
System.out.println("Found class at offset " + classOffset + ", attributes:");
214+
for (AttributeHeader header : headers) {
215+
StringBuilder sb = new StringBuilder();
216+
sb.append(" ");
217+
sb.append(HashManager.get().hexToString(header.id));
218+
String attributeName = HashManager.get().getSimulatorName(header.id);
219+
if (attributeName != null) {
220+
sb.append(" ");
221+
sb.append(attributeName);
222+
}
223+
sb.append(": offset = ");
224+
sb.append(offset);
225+
sb.append(" size = ");
226+
sb.append(header.size);
227+
System.out.println(sb.toString());
228+
229+
offset += header.size;
230+
}
231+
System.out.println();
232+
stream.seek(offset);
233+
}
234+
}
235+
}
171236
}

0 commit comments

Comments
 (0)