Skip to content

Commit

Permalink
Separate out the reading and interpreting of the file
Browse files Browse the repository at this point in the history
  • Loading branch information
Garanas committed May 6, 2024
1 parent deb9e70 commit 61d4105
Show file tree
Hide file tree
Showing 21 changed files with 264 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.faforever.commons.replay;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.google.common.io.BaseEncoding;
import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.compressors.CompressorStreamFactory;
import org.apache.commons.compress.utils.IOUtils;
import org.jetbrains.annotations.NotNull;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Objects;

public class LoadReplay {

public static ReplayContainer loadReplayFromMemory(ReplayMetadata metadata, ReplayBinaryFormat.BinarySCFA bytes) {
return new ReplayContainer(metadata, bytes);
}

public static ReplayContainer loadReplayFromDisk(Path scfaReplayFile) throws IOException {
byte[] bytes = Files.readAllBytes(scfaReplayFile);
return loadReplayFromMemory(null, new ReplayBinaryFormat.BinarySCFA(bytes));
}

public static ReplayContainer loadFAFReplayFromMemory(ReplayBinaryFormat.WithContext fafReplayBytes) throws IOException, CompressorException {
int separator = findSeparatorIndex(fafReplayBytes.bytes());
byte[] metadataBytes = Arrays.copyOfRange(fafReplayBytes.bytes(), 0, separator);
String metadataString = new String(metadataBytes, StandardCharsets.UTF_8);

ObjectMapper parsedMetadata = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
ReplayMetadata replayMetadata = parsedMetadata.readValue(metadataString, ReplayMetadata.class);

byte[] compressedReplayBytes = Arrays.copyOfRange(fafReplayBytes.bytes(), separator + 1, fafReplayBytes.bytes().length);
byte[] replayBytes = decompress(compressedReplayBytes, replayMetadata);

return loadReplayFromMemory(replayMetadata, new ReplayBinaryFormat.BinarySCFA(replayBytes));
}

public static ReplayContainer loadFAFReplayFromDisk(Path fafReplayFile) throws IOException, CompressorException {
byte[] replayBytes = Files.readAllBytes(fafReplayFile);
return loadFAFReplayFromMemory(new ReplayBinaryFormat.WithContext(replayBytes));
}

private static int findSeparatorIndex(byte[] replayData) {
int headerEnd;
for (headerEnd = 0; headerEnd < replayData.length; headerEnd++) {
if (replayData[headerEnd] == '\n') {
return headerEnd;
}
}
throw new IllegalArgumentException("Missing separator between replay header and body");
}

private static byte[] decompress(byte[] data, @NotNull ReplayMetadata metadata) throws IOException, CompressorException {
CompressionType compressionType = Objects.requireNonNullElse(metadata.getCompression(), CompressionType.QTCOMPRESS);

switch (compressionType) {
case QTCOMPRESS: {
return QtCompress.qUncompress(BaseEncoding.base64().decode(new String(data)));
}
case ZSTD: {
ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(data);
CompressorInputStream compressorInputStream = new CompressorStreamFactory()
.createCompressorInputStream(arrayInputStream);

ByteArrayOutputStream out = new ByteArrayOutputStream();
IOUtils.copy(compressorInputStream, out);
return out.toByteArray();
}
case UNKNOWN:
default:
throw new IOException("Unknown replay format in replay file");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.faforever.commons.replay;

public sealed interface ReplayBinaryFormat {

record WithContext (byte[] bytes) implements ReplayBinaryFormat {}

record CompressedSCFA (byte[] bytes) implements ReplayBinaryFormat {}

record BinarySCFA(byte[] bytes) implements ReplayBinaryFormat {}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.faforever.commons.replay;

public record ReplayContainer(ReplayMetadata metadata, ReplayBinaryFormat.BinarySCFA replay) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package com.faforever.commons.replay;

import ch.qos.logback.classic.encoder.JsonEncoder;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.LittleEndianDataInputStream;
import org.apache.commons.compress.compressors.CompressorException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.*;

class LoadReplayTest {

@TempDir
public Path temporaryFolder;

private final ObjectMapper objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

@Test
public void parseBinary01() throws CompressorException, IOException {
assertDoesNotThrow(
() -> {
Path fafReplayFile = temporaryFolder.resolve("TestCommands01.fafreplay");
Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/replay/TestCommands01.fafreplay")), fafReplayFile);
ReplayContainer fafReplayContainer = LoadReplay.loadFAFReplayFromDisk(fafReplayFile);
}
);
}

@Test
public void parseBinary02() throws CompressorException, IOException {
assertDoesNotThrow(
() -> {
Path fafReplayFile = temporaryFolder.resolve("TestModeratorEvents.fafreplay");
Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/replay/TestModeratorEvents.fafreplay")), fafReplayFile);
ReplayContainer fafReplayContainer = LoadReplay.loadFAFReplayFromDisk(fafReplayFile);
}
);
}

@Test
public void parseBinary03() throws CompressorException, IOException {
assertDoesNotThrow(
() -> {
Path fafReplayFile = temporaryFolder.resolve("zstd_reference.fafreplay");
Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/replay/zstd_reference.fafreplay")), fafReplayFile);
ReplayContainer fafReplayContainer = LoadReplay.loadFAFReplayFromDisk(fafReplayFile);
}
);
}

@Test
public void parseBinary04() throws CompressorException, IOException {
assertDoesNotThrow(
() -> {
Path fafReplayFile = temporaryFolder.resolve("test.fafreplay");
Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/replay/test.fafreplay")), fafReplayFile);
ReplayContainer fafReplayContainer = LoadReplay.loadFAFReplayFromDisk(fafReplayFile);
}
);
}

@Test
public void compareBinary01() throws CompressorException, IOException {
Path fafReplayFile = temporaryFolder.resolve("22338092.fafreplay");
Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/replay/load/22338092.fafreplay")), fafReplayFile);

Path scfaReplayFile = temporaryFolder.resolve("22338092.scfareplay");
Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/replay/load/22338092.scfareplay")), scfaReplayFile);

ReplayContainer fafReplayContainer = LoadReplay.loadFAFReplayFromDisk(fafReplayFile);
ReplayContainer scfaReplayContainer = LoadReplay.loadReplayFromDisk(scfaReplayFile);

assertEquals(scfaReplayContainer.replay().bytes().length, fafReplayContainer.replay().bytes().length);
assertEquals(Arrays.hashCode(scfaReplayContainer.replay().bytes()), Arrays.hashCode(fafReplayContainer.replay().bytes()));
assertArrayEquals( scfaReplayContainer.replay().bytes(), fafReplayContainer.replay().bytes());
}

@Test
public void compareBinary02() throws CompressorException, IOException {
Path fafReplayFile = temporaryFolder.resolve("22379519.fafreplay");
Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/replay/load/22379519.fafreplay")), fafReplayFile);

Path scfaReplayFile = temporaryFolder.resolve("22379519.scfareplay");
Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/replay/load/22379519.scfareplay")), scfaReplayFile);

ReplayContainer fafReplayContainer = LoadReplay.loadFAFReplayFromDisk(fafReplayFile);
ReplayContainer scfaReplayContainer = LoadReplay.loadReplayFromDisk(scfaReplayFile);

assertEquals(scfaReplayContainer.replay().bytes().length, fafReplayContainer.replay().bytes().length);
assertEquals(Arrays.hashCode(scfaReplayContainer.replay().bytes()), Arrays.hashCode(fafReplayContainer.replay().bytes()));
assertArrayEquals( scfaReplayContainer.replay().bytes(), fafReplayContainer.replay().bytes());

}

@Test
public void compareBinary03() throws CompressorException, IOException {
Path fafReplayFile = temporaryFolder.resolve("2213263.fafreplay");
Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/replay/load/22379519.fafreplay")), fafReplayFile);

Path scfaReplayFile = temporaryFolder.resolve("2213263.scfareplay");
Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/replay/load/22379519.scfareplay")), scfaReplayFile);

ReplayContainer fafReplayContainer = LoadReplay.loadFAFReplayFromDisk(fafReplayFile);
ReplayContainer scfaReplayContainer = LoadReplay.loadReplayFromDisk(scfaReplayFile);

assertEquals(scfaReplayContainer.replay().bytes().length, fafReplayContainer.replay().bytes().length);
assertEquals(Arrays.hashCode(scfaReplayContainer.replay().bytes()), Arrays.hashCode(fafReplayContainer.replay().bytes()));
assertArrayEquals( scfaReplayContainer.replay().bytes(), fafReplayContainer.replay().bytes());

}
}
2 changes: 0 additions & 2 deletions faf-commons-data/src/test/resources/replay/22213263.fafreplay

This file was deleted.

2 changes: 0 additions & 2 deletions faf-commons-data/src/test/resources/replay/22425616.fafreplay

This file was deleted.

Binary file not shown.
Loading

0 comments on commit 61d4105

Please sign in to comment.