Skip to content

Commit

Permalink
Fix ck2 compressed save game corruption on write
Browse files Browse the repository at this point in the history
  • Loading branch information
crschnick committed Nov 23, 2023
1 parent 852a66d commit e484551
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 24 deletions.
2 changes: 1 addition & 1 deletion canonical_version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.13.7
2.13.8
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,12 @@ public final synchronized ArrayNode parse(Path file) throws IOException, ParseEx
return parse(file.getFileName().toString(), Files.readAllBytes(file), 0, false);
}

public final synchronized ArrayNode parse(String name, Path file, boolean strict) throws IOException, ParseException {
return parse(name, Files.readAllBytes(file), 0, strict);
public final synchronized ArrayNode parse(String displayName, Path file, boolean strict) throws IOException, ParseException {
return parse(displayName, Files.readAllBytes(file), 0, strict);
}

public final synchronized ArrayNode parse(String name, byte[] input, int start) throws ParseException {
return parse(name, input, start, false);
public final synchronized ArrayNode parse(String displayName, byte[] input, int start) throws ParseException {
return parse(displayName, input, start, false);
}

public final synchronized ArrayNode parse(String name, byte[] input, int start, boolean strict) throws ParseException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public void writeData(OutputStream out, ArrayNode node) throws IOException {
"CK2txt".getBytes(),
SavegameType.CK2,
Set.of(new ZipSavegameStructure.SavegamePart("meta", "meta"),
new ZipSavegameStructure.SavegamePart("gamestate", "*"))) {
new ZipSavegameStructure.SavegamePart("*", "gamestate"))) {

@Override
public void writeData(OutputStream out, ArrayNode node) throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

Expand All @@ -34,32 +31,32 @@ public static byte[] getFirstHeader(byte[] input, int maxLength) {
private final Set<SavegamePart> parts;
private final String[] ignored;

public ZipSavegameStructure(byte[] header,SavegameType type, Set<SavegamePart> parts, String... ignored) {
public ZipSavegameStructure(byte[] header,SavegameType type, Set<SavegamePart> parts, String... ignoredFiles) {
this.header = header;
this.type = type;
this.parts = parts;
this.ignored = ignored;
this.ignored = ignoredFiles;
}

protected SavegameParseResult parseInput(byte[] input, int offset) {
var wildcard = parts.stream()
.filter(p -> p.identifier().equals("*"))
.filter(p -> p.fileName().equals("*"))
.findAny();

try {
try (var zipIn = new ZipInputStream(new ByteArrayInputStream(input, offset, input.length - offset))) {
Map<String, ArrayNode> nodes = new LinkedHashMap<>();
ZipEntry entry;
while ((entry = zipIn.getNextEntry()) != null) {
ZipEntry finalEntry = entry;
ZipEntry finalZipEntry = entry;

// Skip ignored entries
if (Arrays.stream(ignored).anyMatch(s -> s.equals(finalEntry.getName()))) {
if (Arrays.stream(ignored).anyMatch(s -> s.equals(finalZipEntry.getName()))) {
continue;
}

var part = parts.stream()
.filter(p -> p.identifier().equals(finalEntry.getName()))
.filter(p -> p.fileName().equals(finalZipEntry.getName()))
.findAny().or(() -> wildcard);

// Ignore unknown entry
Expand All @@ -72,16 +69,16 @@ protected SavegameParseResult parseInput(byte[] input, int offset) {
return new SavegameParseResult.Invalid("File " + part.get().identifier() + " has an invalid header");
}

var node = type.getParser().parse(part.get().name, bytes, header != null ? header.length + 1 : 0);
var node = type.getParser().parse(part.get().identifier(), bytes, header != null ? header.length + 1 : 0);
if (node.size() == 0) {
return new SavegameParseResult.Invalid("File " + entry.getName() + " is empty");
return new SavegameParseResult.Invalid("File " + part.get().identifier() + " is empty");
}

nodes.put(part.get().name(), node);
nodes.put(part.get().identifier(), node);
}

var missingParts = parts.stream()
.map(SavegamePart::name)
.map(SavegamePart::identifier)
.filter(s -> !nodes.containsKey(s))
.toList();
if (missingParts.size() > 0) {
Expand All @@ -98,15 +95,27 @@ protected SavegameParseResult parseInput(byte[] input, int offset) {
@Override
public void write(Path out, SavegameContent content) throws IOException {
try (var fs = FileSystems.newFileSystem(out, Map.of("create", true))) {
Optional<SavegamePart> wildcardPart;
try (var list = Files.list(fs.getPath("/"))) {
wildcardPart = list.map(path -> path.getFileName().toString()).filter(p -> content.entrySet().stream().noneMatch(e -> p.equals(e.getKey())))
.map(s -> new SavegamePart(s, "gamestate"))
.findAny();
}

for (var e : content.entrySet()) {
var usedPart = parts.stream()
.filter(part -> part.name().equals(e.getKey()))
.filter(part -> part.fileName().equals(e.getKey()))
.findAny();

if (usedPart.isEmpty() && wildcardPart.isPresent() && wildcardPart.get().identifier().equals(e.getKey())) {
usedPart = wildcardPart;
}

if (usedPart.isEmpty()) {
continue;
}

var path = fs.getPath(usedPart.get().identifier());
var path = fs.getPath(usedPart.get().fileName());
try (var partOut = Files.newOutputStream(path)) {
if (header != null) {
partOut.write(header);
Expand All @@ -128,6 +137,6 @@ public SavegameType getType() {
return type;
}

public record SavegamePart(String name, String identifier) {
public record SavegamePart(String fileName, String identifier) {
}
}
2 changes: 1 addition & 1 deletion version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.13.7
2.13.8-SNAPSHOT

0 comments on commit e484551

Please sign in to comment.