-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Separate out the reading and interpreting of the file
- Loading branch information
Showing
21 changed files
with
264 additions
and
17 deletions.
There are no files selected for viewing
82 changes: 82 additions & 0 deletions
82
faf-commons-data/src/main/java/com/faforever/commons/replay/LoadReplay.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
} | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
faf-commons-data/src/main/java/com/faforever/commons/replay/ReplayBinaryFormat.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {} | ||
|
||
} |
4 changes: 4 additions & 0 deletions
4
faf-commons-data/src/main/java/com/faforever/commons/replay/ReplayContainer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) { | ||
} |
128 changes: 128 additions & 0 deletions
128
faf-commons-data/src/test/java/com/faforever/commons/replay/LoadReplayTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
2
faf-commons-data/src/test/resources/replay/22213263.fafreplay
This file was deleted.
Oops, something went wrong.
2 changes: 0 additions & 2 deletions
2
faf-commons-data/src/test/resources/replay/22425616.fafreplay
This file was deleted.
Oops, something went wrong.
Binary file modified
BIN
-190 KB
(0.067%)
faf-commons-data/src/test/resources/replay/TestCommands01.fafreplay
Binary file not shown.
Oops, something went wrong.