Skip to content

Commit 6bd2f92

Browse files
committed
Improve field parsing, fix StringVector error
1 parent af15017 commit 6bd2f92

File tree

6 files changed

+55
-52
lines changed

6 files changed

+55
-52
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ bin/
5353
.settings/
5454
.project
5555
.classpath
56+
test-output/
5657

5758
# Gradle Build
5859
bin/

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
displayname=Darkest Dungeon Save Editor
2-
version=v0.0.57
2+
version=v0.0.60
33
githuburl=https://github.com/robojumper/DarkestDungeonSaveEditor
44
releasesurl=https://api.github.com/repos/robojumper/DarkestDungeonSaveEditor/releases/latest
55
jarname=DDSaveEditor

src/main/java/de/robojumper/ddsavereader/file/DsonField.java

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import java.nio.charset.StandardCharsets;
66
import java.text.ParseException;
77
import java.util.Arrays;
8-
import java.util.function.Function;
8+
import java.util.Iterator;
99

1010
import de.robojumper.ddsavereader.file.DsonFile.UnhashBehavior;
1111
import de.robojumper.ddsavereader.file.DsonTypes.FieldType;
@@ -21,23 +21,6 @@ public class DsonField {
2121

2222
public String name;
2323

24-
private Function<Integer, String> nameMapper = new Function<Integer, String>() {
25-
26-
@Override
27-
public String apply(Integer i) {
28-
if (i == 0) {
29-
return name;
30-
} else {
31-
DsonField field = parent;
32-
for (int p = 1; p < i && field != null; p++) {
33-
field = field.parent;
34-
}
35-
return field != null ? field.name : null;
36-
}
37-
}
38-
39-
};
40-
4124
public String dataString = "\"UNKNOWN. PLEASE PARSE TYPE\"";
4225

4326
private String hashedValue;
@@ -113,7 +96,7 @@ private boolean parseHardcodedType(UnhashBehavior behavior) {
11396
}
11497

11598
private boolean parseFloat() {
116-
if (DsonTypes.isA(FieldType.TYPE_FLOAT, nameMapper)) {
99+
if (DsonTypes.isA(FieldType.TYPE_FLOAT, this::nameIterator)) {
117100
if (alignedSize() == 4) {
118101
type = FieldType.TYPE_FLOAT;
119102
byte[] tempArr = Arrays.copyOfRange(rawData, alignmentSkip(), alignmentSkip() + 4);
@@ -126,23 +109,24 @@ private boolean parseFloat() {
126109
}
127110

128111
private boolean parseStringVector() {
129-
if (DsonTypes.isA(FieldType.TYPE_STRINGVECTOR, nameMapper)) {
112+
if (DsonTypes.isA(FieldType.TYPE_STRINGVECTOR, this::nameIterator)) {
130113
type = FieldType.TYPE_STRINGVECTOR;
131114
byte[] tempArr = Arrays.copyOfRange(rawData, alignmentSkip(), alignmentSkip() + 4);
132-
@SuppressWarnings("unused")
133115
int arrLen = ByteBuffer.wrap(tempArr).order(ByteOrder.LITTLE_ENDIAN).getInt();
134116
// read the rest
135117
byte[] strings = Arrays.copyOfRange(rawData, alignmentSkip() + 4, alignmentSkip() + alignedSize());
136118
ByteBuffer bf = ByteBuffer.wrap(strings).order(ByteOrder.LITTLE_ENDIAN);
137119
StringBuilder sb = new StringBuilder();
138120
sb.append("[");
139-
while (bf.remaining() > 0) {
121+
for (int i = 0; i < arrLen; i++) {
140122
int strlen = bf.getInt();
141123
byte[] tempArr2 = Arrays.copyOfRange(rawData, alignmentSkip() + 4 + bf.position(),
142124
alignmentSkip() + 4 + bf.position() + strlen - 1);
143125
sb.append("\"" + new String(tempArr2, StandardCharsets.UTF_8).replaceAll("\n", "\\\\n") + "\"");
144126
bf.position(bf.position() + strlen);
145-
if (bf.remaining() > 0) {
127+
if (i < arrLen - 1) {
128+
// Skip for alignment, but only if we have things following
129+
bf.position(bf.position() + ((4 - (bf.position() % 4)) % 4));
146130
sb.append(", ");
147131
}
148132
}
@@ -154,7 +138,7 @@ private boolean parseStringVector() {
154138
}
155139

156140
private boolean parseIntVector(UnhashBehavior behavior) {
157-
if (DsonTypes.isA(FieldType.TYPE_INTVECTOR, nameMapper)) {
141+
if (DsonTypes.isA(FieldType.TYPE_INTVECTOR, this::nameIterator)) {
158142
byte[] tempArr = Arrays.copyOfRange(rawData, alignmentSkip(), alignmentSkip() + 4);
159143
int arrLen = ByteBuffer.wrap(tempArr).order(ByteOrder.LITTLE_ENDIAN).getInt();
160144
if (alignedSize() == (arrLen + 1) * 4) {
@@ -204,7 +188,7 @@ private boolean parseIntVector(UnhashBehavior behavior) {
204188
}
205189

206190
private boolean parseFloatArray() {
207-
if (DsonTypes.isA(FieldType.TYPE_FLOATARRAY, nameMapper)) {
191+
if (DsonTypes.isA(FieldType.TYPE_FLOATARRAY, this::nameIterator)) {
208192
type = FieldType.TYPE_FLOATARRAY;
209193
byte[] floats = Arrays.copyOfRange(rawData, alignmentSkip(), alignmentSkip() + alignedSize());
210194
ByteBuffer bf = ByteBuffer.wrap(floats).order(ByteOrder.LITTLE_ENDIAN);
@@ -298,4 +282,20 @@ public String getExtraComments() {
298282
}
299283
return sb.toString();
300284
}
285+
286+
private Iterator<String> nameIterator() {
287+
return new Iterator<String>() {
288+
private DsonField field = DsonField.this;
289+
@Override
290+
public boolean hasNext() {
291+
return field != null;
292+
}
293+
294+
@Override
295+
public String next() {
296+
String f = field.name;
297+
field = field.parent;
298+
return f;
299+
}};
300+
}
301301
}

src/main/java/de/robojumper/ddsavereader/file/DsonTypes.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
import java.util.Collection;
44
import java.util.HashMap;
5-
import java.util.function.Function;
5+
import java.util.Iterator;
6+
import java.util.function.Supplier;
67

78
public class DsonTypes {
89

@@ -31,7 +32,7 @@ public enum FieldType {
3132
new String[][] { { "read_page_indexes" }, { "raid_read_page_indexes" }, { "raid_unread_page_indexes" }, // journal.json
3233
{ "dungeons_unlocked" }, { "played_video_list" }, // game_knowledge.json
3334
{ "trinket_retention_ids" }, // quest.json
34-
{ "last_party_guids" }, { "dungeon_history" }, { "buff_group_guids" }, // roster.json
35+
{ "last_party_guids" }, { "dungeon_history" }, { "buff_group_guids" }, // roster.json
3536
{ "result_event_history" }, // town_event.json
3637
{ "additional_mash_disabled_infestation_monster_class_ids" }, // campaign_mash.json
3738
{ "party", "heroes" }, // raid.json
@@ -43,6 +44,7 @@ public enum FieldType {
4344
// string
4445
TYPE_STRINGVECTOR(new String[][] { { "goal_ids" }, // quest.json
4546
{ "roaming_dungeon_2_ids", "*", "s" }, // campaign_mash.json
47+
{ "quirk_group" }, // campaign_log.json
4648
}),
4749
// aligned, arbitrary number of 4-byte floats. emitted as [1.0, 2.0, ...]
4850
TYPE_FLOATARRAY(new String[][] { { "map", "bounds" }, { "areas", "*", "bounds" },
@@ -88,15 +90,13 @@ public static int stringHash(String str) {
8890
/**
8991
* Determines whether a field is hardcoded as specific type.
9092
*
91-
* @param type One of TYPE_CHAR, TYPE_FLOAT, TYPE_INTVECTOR,
92-
* TYPE_STRINGVECTOR, TYPE_FLOATARRAY
93-
* @param nameMapper Function that returns field and parent field names.
94-
* nameMapper.apply(0) is the field's name,
95-
* nameMapper.apply(1) is the parent field's name, ... If
96-
* there are "not enough" parents, returns null
93+
* @param type One of TYPE_CHAR, TYPE_FLOAT, TYPE_INTVECTOR, TYPE_STRINGVECTOR,
94+
* TYPE_FLOATARRAY
95+
* @param names Supplied that gives an Iterator that yields field names, first
96+
* the current field name, then the parent, ...
9797
* @return True if a matching field is found.
9898
*/
99-
static boolean isA(FieldType type, Function<Integer, String> nameMapper) {
99+
static boolean isA(FieldType type, Supplier<Iterator<String>> names) {
100100

101101
String[][] arr = type.names;
102102

@@ -108,13 +108,14 @@ static boolean isA(FieldType type, Function<Integer, String> nameMapper) {
108108
boolean match;
109109
for (int i = 0; i < arr.length; i++) {
110110
match = true;
111-
checkString = nameMapper.apply(0);
111+
Iterator<String> nameIter = names.get();
112+
checkString = nameIter.next();
112113
for (int j = arr[i].length - 1; j >= 0; j--) {
113114
if (checkString == null || !(arr[i][j].equals("*") || arr[i][j].equals(checkString))) {
114115
match = false;
115116
break;
116117
}
117-
checkString = nameMapper.apply(arr[i].length - j);
118+
checkString = nameIter.hasNext() ? nameIter.next() : null;
118119
}
119120
if (match) {
120121
return true;

src/main/java/de/robojumper/ddsavereader/file/DsonWriter.java

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
import java.nio.ByteOrder;
1212
import java.nio.charset.StandardCharsets;
1313
import java.text.ParseException;
14+
import java.util.ArrayDeque;
1415
import java.util.ArrayList;
15-
import java.util.Stack;
16-
import java.util.function.Function;
16+
import java.util.Deque;
1717

1818
import com.fasterxml.jackson.core.JsonFactory;
1919
import com.fasterxml.jackson.core.JsonParseException;
@@ -25,9 +25,8 @@ public class DsonWriter {
2525
HeaderBlock header;
2626
ByteArrayOutputStream data;
2727
ArrayList<Meta1BlockEntry> meta1Entries;
28-
// Stack over Deque for random access .get
29-
Stack<Integer> hierarchyHintStack;
30-
Stack<String> nameStack;
28+
Deque<Integer> hierarchyHintStack;
29+
Deque<String> nameStack;
3130
ArrayList<Meta2BlockEntry> meta2Entries;
3231

3332
public DsonWriter(String jsonData) throws IOException, ParseException {
@@ -47,8 +46,8 @@ private DsonWriter(JsonParser reader) throws IOException, ParseException {
4746

4847
meta1Entries = new ArrayList<>();
4948
meta2Entries = new ArrayList<>();
50-
hierarchyHintStack = new Stack<>();
51-
nameStack = new Stack<>();
49+
hierarchyHintStack = new ArrayDeque<>();
50+
nameStack = new ArrayDeque<>();
5251
hierarchyHintStack.push(-1);
5352

5453
try {
@@ -92,9 +91,7 @@ private void writeField(String name, JsonParser reader) throws IOException, Pars
9291
e2.offset = data.size();
9392
data.write(name.getBytes(StandardCharsets.UTF_8));
9493
data.write(0);
95-
96-
Function<Integer, String> nameMapper = (i) -> i == 0 ? name
97-
: (i <= nameStack.size() ? nameStack.get(nameStack.size() - i) : null);
94+
9895
try {
9996
reader.nextToken();
10097
if (reader.getCurrentToken() == JsonToken.START_OBJECT) {
@@ -137,7 +134,9 @@ private void writeField(String name, JsonParser reader) throws IOException, Pars
137134
} else {
138135
// Now for the tricky part: Not an object, now we need to determine the type
139136
// Same as in DsonField, we first check the hardcoded types
140-
if (DsonTypes.isA(FieldType.TYPE_FLOATARRAY, nameMapper)) {
137+
// In order to easily use the nameStack's iterator, we temporarily push the field name
138+
nameStack.push(name);
139+
if (DsonTypes.isA(FieldType.TYPE_FLOATARRAY, nameStack::iterator)) {
141140
align();
142141
if (reader.getCurrentToken() != JsonToken.START_ARRAY) {
143142
throw new ParseException("Expected START_ARRAY",
@@ -150,7 +149,7 @@ private void writeField(String name, JsonParser reader) throws IOException, Pars
150149
throw new ParseException("Expected VALUE_NUMBER_FLOAT or END_ARRAY",
151150
(int) reader.getCurrentLocation().getCharOffset());
152151
}
153-
} else if (DsonTypes.isA(FieldType.TYPE_INTVECTOR, nameMapper)) {
152+
} else if (DsonTypes.isA(FieldType.TYPE_INTVECTOR, nameStack::iterator)) {
154153
align();
155154
if (reader.getCurrentToken() != JsonToken.START_ARRAY) {
156155
throw new ParseException("Expected START_ARRAY",
@@ -177,7 +176,7 @@ private void writeField(String name, JsonParser reader) throws IOException, Pars
177176
}
178177
data.write(intBytes(numElem));
179178
data.write(vecData.toByteArray());
180-
} else if (DsonTypes.isA(FieldType.TYPE_STRINGVECTOR, nameMapper)) {
179+
} else if (DsonTypes.isA(FieldType.TYPE_STRINGVECTOR, nameStack::iterator)) {
181180
align();
182181
if (reader.getCurrentToken() != JsonToken.START_ARRAY) {
183182
throw new ParseException("Expected START_ARRAY",
@@ -187,6 +186,7 @@ private void writeField(String name, JsonParser reader) throws IOException, Pars
187186
int numElem = 0;
188187
while (reader.nextToken() == JsonToken.VALUE_STRING) {
189188
numElem += 1;
189+
vecData.write(new byte[(4 - (vecData.size() % 4)) % 4]);
190190
vecData.write(stringBytes(reader.getValueAsString()));
191191
}
192192
if (reader.getCurrentToken() != JsonToken.END_ARRAY) {
@@ -195,14 +195,14 @@ private void writeField(String name, JsonParser reader) throws IOException, Pars
195195
}
196196
data.write(intBytes(numElem));
197197
data.write(vecData.toByteArray());
198-
} else if (DsonTypes.isA(FieldType.TYPE_FLOAT, nameMapper)) {
198+
} else if (DsonTypes.isA(FieldType.TYPE_FLOAT, nameStack::iterator)) {
199199
align();
200200
if (reader.getCurrentToken() != JsonToken.VALUE_NUMBER_FLOAT) {
201201
throw new ParseException("Expected VALUE_NUMBER_FLOAT",
202202
(int) reader.getCurrentLocation().getCharOffset());
203203
}
204204
data.write(floatBytes(reader.getFloatValue()));
205-
} else if (DsonTypes.isA(FieldType.TYPE_CHAR, nameMapper)) {
205+
} else if (DsonTypes.isA(FieldType.TYPE_CHAR, nameStack::iterator)) {
206206
if (reader.getCurrentToken() != JsonToken.VALUE_STRING) {
207207
throw new ParseException(
208208
name + ": Expected VALUE_STRING, got " + reader.getCurrentToken().asString(),
@@ -238,6 +238,7 @@ private void writeField(String name, JsonParser reader) throws IOException, Pars
238238
throw new ParseException("Field " + name + " not identified",
239239
(int) reader.getCurrentLocation().getCharOffset());
240240
}
241+
nameStack.pop();
241242
}
242243
} catch (ClassCastException | IllegalStateException e) {
243244
throw new ParseException("Error writing " + name, (int) reader.getCurrentLocation().getCharOffset());
553 KB
Binary file not shown.

0 commit comments

Comments
 (0)