| 1 | package com.rox.save; | |
| 2 | ||
| 3 | import com.rox.ByteUtil; | |
| 4 | ||
| 5 | import java.io.IOException; | |
| 6 | import java.nio.file.Files; | |
| 7 | import java.nio.file.Path; | |
| 8 | import java.util.Optional; | |
| 9 | ||
| 10 | /** | |
| 11 | * Public save-file codec. A missing file, bad magic/version, or a CRC mismatch all read back | |
| 12 | * identically as {@link Optional#empty()} - callers treat that exactly like "no save file exists", | |
| 13 | * never a crash. | |
| 14 | */ | |
| 15 | public final class SaveFileFormat { | |
| 16 | /** The board's PRG-RAM size on every currently-supported mapper - see {@code Mapper.prgRam()}. */ | |
| 17 | private static final int PRG_RAM_SIZE = 0x2000; | |
| 18 | ||
| 19 | private SaveFileFormat(){ | |
| 20 | } | |
| 21 | ||
| 22 | public static void writeBatterySave(final Path path, final SaveMetadata metadata, final int[] prgRam) throws IOException { | |
| 23 |
1
1. writeBatterySave : negated conditional → KILLED |
if (prgRam.length != PRG_RAM_SIZE){ |
| 24 | throw new IllegalArgumentException("Expected " + PRG_RAM_SIZE + " bytes of PRG-RAM, got " + prgRam.length); | |
| 25 | } | |
| 26 |
1
1. writeBatterySave : removed call to com/rox/save/SaveCodec::writeAtomically → KILLED |
SaveCodec.writeAtomically(path, SaveCodec.encode(SaveType.BATTERY_PRG_RAM, metadata, ByteUtil.toBytes(prgRam))); |
| 27 | } | |
| 28 | ||
| 29 | public static Optional<BatterySaveFile> readBatterySave(final Path path){ | |
| 30 |
1
1. readBatterySave : replaced return value with Optional.empty for com/rox/save/SaveFileFormat::readBatterySave → KILLED |
return readFile(path) |
| 31 | .flatMap(SaveCodec::decode) | |
| 32 |
2
1. lambda$readBatterySave$0 : negated conditional → KILLED 2. lambda$readBatterySave$0 : replaced boolean return with true for com/rox/save/SaveFileFormat::lambda$readBatterySave$0 → KILLED |
.filter(decoded -> decoded.type() == SaveType.BATTERY_PRG_RAM) |
| 33 | //reject here or leave to crash deep inside Mapper.restorePrgRam()'s own length check | |
| 34 |
2
1. lambda$readBatterySave$1 : replaced boolean return with true for com/rox/save/SaveFileFormat::lambda$readBatterySave$1 → KILLED 2. lambda$readBatterySave$1 : negated conditional → KILLED |
.filter(decoded -> decoded.payload().length == PRG_RAM_SIZE) |
| 35 |
1
1. lambda$readBatterySave$2 : replaced return value with null for com/rox/save/SaveFileFormat::lambda$readBatterySave$2 → KILLED |
.map(decoded -> new BatterySaveFile(decoded.metadata(), ByteUtil.toUnsigned(decoded.payload()))); |
| 36 | } | |
| 37 | ||
| 38 | public static void writeLiveSnapshot(final Path path, final SaveMetadata metadata, final SystemSnapshot snapshot) throws IOException { | |
| 39 |
1
1. writeLiveSnapshot : removed call to com/rox/save/SaveCodec::writeAtomically → KILLED |
SaveCodec.writeAtomically(path, SaveCodec.encode(SaveType.LIVE_SNAPSHOT, metadata, SnapshotCodec.encode(snapshot))); |
| 40 | } | |
| 41 | ||
| 42 | public static Optional<LiveSaveFile> readLiveSnapshot(final Path path){ | |
| 43 |
1
1. readLiveSnapshot : replaced return value with Optional.empty for com/rox/save/SaveFileFormat::readLiveSnapshot → KILLED |
return readFile(path) |
| 44 | .flatMap(SaveCodec::decode) | |
| 45 |
2
1. lambda$readLiveSnapshot$0 : replaced boolean return with true for com/rox/save/SaveFileFormat::lambda$readLiveSnapshot$0 → KILLED 2. lambda$readLiveSnapshot$0 : negated conditional → KILLED |
.filter(decoded -> decoded.type() == SaveType.LIVE_SNAPSHOT) |
| 46 |
1
1. lambda$readLiveSnapshot$1 : replaced return value with Optional.empty for com/rox/save/SaveFileFormat::lambda$readLiveSnapshot$1 → KILLED |
.flatMap(decoded -> SnapshotCodec.decode(decoded.payload(), SystemSnapshot.class) |
| 47 |
1
1. lambda$readLiveSnapshot$2 : replaced return value with null for com/rox/save/SaveFileFormat::lambda$readLiveSnapshot$2 → KILLED |
.map(snapshot -> new LiveSaveFile(decoded.metadata(), snapshot))); |
| 48 | } | |
| 49 | ||
| 50 | /** A missing or unreadable file is just "no save", never an error. */ | |
| 51 | private static Optional<byte[]> readFile(final Path path){ | |
| 52 |
1
1. readFile : negated conditional → KILLED |
if (!Files.isRegularFile(path)){ |
| 53 | return Optional.empty(); | |
| 54 | } | |
| 55 | try { | |
| 56 |
1
1. readFile : replaced return value with Optional.empty for com/rox/save/SaveFileFormat::readFile → KILLED |
return Optional.of(Files.readAllBytes(path)); |
| 57 | } catch (IOException e){ | |
| 58 | //Something changed between isRegularFile and here (TOCTOU) or permissions check fail -> No usable save | |
| 59 | return Optional.empty(); | |
| 60 | } | |
| 61 | } | |
| 62 | } | |
Mutations | ||
| 23 |
1.1 |
|
| 26 |
1.1 |
|
| 30 |
1.1 |
|
| 32 |
1.1 2.2 |
|
| 34 |
1.1 2.2 |
|
| 35 |
1.1 |
|
| 39 |
1.1 |
|
| 43 |
1.1 |
|
| 45 |
1.1 2.2 |
|
| 46 |
1.1 |
|
| 47 |
1.1 |
|
| 52 |
1.1 |
|
| 56 |
1.1 |