INesRom.java

1
package com.rox.cartridge;
2
3
import java.util.Arrays;
4
5
/**
6
 * A parsed iNES (".nes") ROM file: the 16-byte header plus the PRG-ROM/CHR-ROM bytes it describes.
7
 * Doesn't know anything about mapper banking behaviour - that's {@link Mapper}'s job, built from the
8
 * data here.
9
 */
10
public final class INesRom {
11
    private static final int HEADER_SIZE = 16;
12
    private static final int TRAINER_SIZE = 512;
13
    private static final int PRG_BANK_SIZE = 16384;
14
    private static final int CHR_BANK_SIZE = 8192;
15
16
    private static final int PRG_BANKS_OFFSET = 4;
17
    private static final int CHR_BANKS_OFFSET = 5;
18
    private static final int FLAGS_6_OFFSET = 6;
19
    private static final int FLAGS_7_OFFSET = 7;
20
21
    private static final int TRAINER_BIT = 0x04;
22
    private static final int BATTERY_BIT = 0x02;
23
    private static final int VERTICAL_MIRRORING_BIT = 0x01;
24
    private static final int MAPPER_LOW_NIBBLE_SHIFT = 4;
25
    private static final int MAPPER_HIGH_NIBBLE_MASK = 0xF0;
26
    private static final int NES2_IDENTIFICATION_MASK = 0x0C;
27
    private static final int NES2_IDENTIFICATION_VALUE = 0x08;
28
29
    private final int mapperNumber;
30
    private final boolean verticalMirroring;
31
    private final boolean hasBattery;
32
    private final boolean hasTrainer;
33
    private final byte[] prgRom;
34
    private final byte[] chrRom;
35
36
    private INesRom(final int mapperNumber, final boolean verticalMirroring, final boolean hasBattery,
37
                     final boolean hasTrainer, final byte[] prgRom, final byte[] chrRom){
38
        this.mapperNumber = mapperNumber;
39
        this.verticalMirroring = verticalMirroring;
40
        this.hasBattery = hasBattery;
41
        this.hasTrainer = hasTrainer;
42
        this.prgRom = prgRom;
43
        this.chrRom = chrRom;
44
    }
45
46
    /** Parse the raw bytes of a ".nes" file. */
47
    public static INesRom parse(final byte[] fileBytes){
48 3 1. parse : negated conditional → KILLED
2. parse : changed conditional boundary → KILLED
3. parse : negated conditional → KILLED
        if (fileBytes.length < HEADER_SIZE || !hasMagic(fileBytes)){
49
            throw new IllegalArgumentException("Not an iNES ROM (missing 'NES' + $1A header magic)");
50
        }
51
52 1 1. parse : Replaced bitwise AND with OR → KILLED
        final int flags7 = fileBytes[FLAGS_7_OFFSET] & 0xFF;
53 2 1. parse : Replaced bitwise AND with OR → KILLED
2. parse : negated conditional → KILLED
        if ((flags7 & NES2_IDENTIFICATION_MASK) == NES2_IDENTIFICATION_VALUE){
54
            throw new IllegalArgumentException("NES 2.0 ROMs are not supported yet - flags7 identifies this as NES 2.0");
55
        }
56
57 1 1. parse : Replaced bitwise AND with OR → KILLED
        final int prgBanks = fileBytes[PRG_BANKS_OFFSET] & 0xFF;
58 1 1. parse : Replaced bitwise AND with OR → KILLED
        final int chrBanks = fileBytes[CHR_BANKS_OFFSET] & 0xFF;
59 1 1. parse : Replaced bitwise AND with OR → KILLED
        final int flags6 = fileBytes[FLAGS_6_OFFSET] & 0xFF;
60
61 3 1. parse : Replaced Shift Right with Shift Left → KILLED
2. parse : Replaced bitwise OR with AND → KILLED
3. parse : Replaced bitwise AND with OR → KILLED
        final int mapperNumber = (flags7 & MAPPER_HIGH_NIBBLE_MASK) | (flags6 >> MAPPER_LOW_NIBBLE_SHIFT);
62 2 1. parse : negated conditional → KILLED
2. parse : Replaced bitwise AND with OR → KILLED
        final boolean verticalMirroring = (flags6 & VERTICAL_MIRRORING_BIT) != 0;
63 2 1. parse : negated conditional → KILLED
2. parse : Replaced bitwise AND with OR → KILLED
        final boolean hasBattery = (flags6 & BATTERY_BIT) != 0;
64 2 1. parse : Replaced bitwise AND with OR → KILLED
2. parse : negated conditional → KILLED
        final boolean hasTrainer = (flags6 & TRAINER_BIT) != 0;
65
66 2 1. parse : negated conditional → KILLED
2. parse : Replaced integer addition with subtraction → KILLED
        int offset = HEADER_SIZE + (hasTrainer ? TRAINER_SIZE : 0);
67
68 1 1. parse : Replaced integer multiplication with division → KILLED
        final int prgSize = prgBanks * PRG_BANK_SIZE;
69
        final byte[] prgRom = copyRange(fileBytes, offset, prgSize, "PRG-ROM");
70 1 1. parse : Replaced integer addition with subtraction → KILLED
        offset += prgSize;
71
72 1 1. parse : Replaced integer multiplication with division → KILLED
        final int chrSize = chrBanks * CHR_BANK_SIZE;
73
        final byte[] chrRom = copyRange(fileBytes, offset, chrSize, "CHR-ROM");
74
75 1 1. parse : replaced return value with null for com/rox/cartridge/INesRom::parse → KILLED
        return new INesRom(mapperNumber, verticalMirroring, hasBattery, hasTrainer, prgRom, chrRom);
76
    }
77
78
    private static boolean hasMagic(final byte[] fileBytes){
79 5 1. hasMagic : replaced boolean return with true for com/rox/cartridge/INesRom::hasMagic → KILLED
2. hasMagic : negated conditional → KILLED
3. hasMagic : negated conditional → KILLED
4. hasMagic : negated conditional → KILLED
5. hasMagic : negated conditional → KILLED
        return fileBytes[0] == 'N' && fileBytes[1] == 'E' && fileBytes[2] == 'S' && fileBytes[3] == 0x1A;
80
    }
81
82
    private static byte[] copyRange(final byte[] fileBytes, final int offset, final int length, final String label){
83 3 1. copyRange : changed conditional boundary → KILLED
2. copyRange : negated conditional → KILLED
3. copyRange : Replaced integer addition with subtraction → KILLED
        if (offset + length > fileBytes.length){
84
            throw new IllegalArgumentException("Truncated iNES ROM: expected " + length + " bytes of " + label
85
                    + " at offset " + offset + ", file is only " + fileBytes.length + " bytes");
86
        }
87 2 1. copyRange : replaced return value with null for com/rox/cartridge/INesRom::copyRange → KILLED
2. copyRange : Replaced integer addition with subtraction → KILLED
        return Arrays.copyOfRange(fileBytes, offset, offset + length);
88
    }
89
90
    public int mapperNumber(){
91 1 1. mapperNumber : replaced int return with 0 for com/rox/cartridge/INesRom::mapperNumber → KILLED
        return mapperNumber;
92
    }
93
94
    public boolean isVerticalMirroring(){
95 2 1. isVerticalMirroring : replaced boolean return with true for com/rox/cartridge/INesRom::isVerticalMirroring → KILLED
2. isVerticalMirroring : replaced boolean return with false for com/rox/cartridge/INesRom::isVerticalMirroring → KILLED
        return verticalMirroring;
96
    }
97
98
    /** Whether this cartridge's PRG-RAM ({@code $6000-$7FFF}) is battery-backed - flags6 bit 1 of the iNES header. */
99
    public boolean hasBattery(){
100 2 1. hasBattery : replaced boolean return with true for com/rox/cartridge/INesRom::hasBattery → KILLED
2. hasBattery : replaced boolean return with false for com/rox/cartridge/INesRom::hasBattery → KILLED
        return hasBattery;
101
    }
102
103
    /** Whether the file includes a 512-byte trainer before PRG-ROM - flags6 bit 2 of the iNES header. */
104
    public boolean hasTrainer(){
105 2 1. hasTrainer : replaced boolean return with true for com/rox/cartridge/INesRom::hasTrainer → KILLED
2. hasTrainer : replaced boolean return with false for com/rox/cartridge/INesRom::hasTrainer → KILLED
        return hasTrainer;
106
    }
107
108
    /** Defensive copy - callers must not be able to mutate ROM content behind PRG-ROM's back. */
109
    public byte[] prgRom(){
110 1 1. prgRom : replaced return value with null for com/rox/cartridge/INesRom::prgRom → KILLED
        return Arrays.copyOf(prgRom, prgRom.length);
111
    }
112
113
    /** Defensive copy - callers must not be able to mutate ROM content behind CHR-ROM's back. */
114
    public byte[] chrRom(){
115 1 1. chrRom : replaced return value with null for com/rox/cartridge/INesRom::chrRom → KILLED
        return Arrays.copyOf(chrRom, chrRom.length);
116
    }
117
}

Mutations

48

1.1
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[test-template:rejectsFileWithAnySingleMagicByteWrong(int)]/[test-template-invocation:#1]
negated conditional → KILLED

2.2
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:acceptsFileExactlyTheHeaderSizeWithNoPrgOrChrBanks()]
changed conditional boundary → KILLED

3.3
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:parsesMapperNumberFromLowNibbleOfFlags6()]
negated conditional → KILLED

52

1.1
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:rejectsNes2FormatHeaders()]
Replaced bitwise AND with OR → KILLED

53

1.1
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:rejectsNes2FormatHeaders()]
Replaced bitwise AND with OR → KILLED

2.2
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:rejectsNes2FormatHeaders()]
negated conditional → KILLED

57

1.1
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:parsesMapperNumberFromLowNibbleOfFlags6()]
Replaced bitwise AND with OR → KILLED

58

1.1
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:parsesMapperNumberFromLowNibbleOfFlags6()]
Replaced bitwise AND with OR → KILLED

59

1.1
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:parsesMapperNumberFromLowNibbleOfFlags6()]
Replaced bitwise AND with OR → KILLED

61

1.1
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:parsesMapperNumberFromLowNibbleOfFlags6()]
Replaced Shift Right with Shift Left → KILLED

2.2
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:parsesMapperNumberFromLowNibbleOfFlags6()]
Replaced bitwise OR with AND → KILLED

3.3
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:parsesMapperNumberFromLowNibbleOfFlags6()]
Replaced bitwise AND with OR → KILLED

62

1.1
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:verticalMirroringBitIsRead()]
negated conditional → KILLED

2.2
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:verticalMirroringBitIsRead()]
Replaced bitwise AND with OR → KILLED

63

1.1
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:batteryBitIsRead()]
negated conditional → KILLED

2.2
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:batteryBitIsRead()]
Replaced bitwise AND with OR → KILLED

64

1.1
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:parsesMapperNumberFromLowNibbleOfFlags6()]
Replaced bitwise AND with OR → KILLED

2.2
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:parsesMapperNumberFromLowNibbleOfFlags6()]
negated conditional → KILLED

66

1.1
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:parsesMapperNumberFromLowNibbleOfFlags6()]
negated conditional → KILLED

2.2
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:trainerBitIsRead()]
Replaced integer addition with subtraction → KILLED

68

1.1
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:rejectsPrgTruncationEvenWhenChrIsAlsoDeclared()]
Replaced integer multiplication with division → KILLED

70

1.1
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:parsesMapperNumberFromLowNibbleOfFlags6()]
Replaced integer addition with subtraction → KILLED

72

1.1
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:chrRomGetterReturnsADefensiveCopy()]
Replaced integer multiplication with division → KILLED

75

1.1
Location : parse
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:parsesMapperNumberFromLowNibbleOfFlags6()]
replaced return value with null for com/rox/cartridge/INesRom::parse → KILLED

79

1.1
Location : hasMagic
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[test-template:rejectsFileWithAnySingleMagicByteWrong(int)]/[test-template-invocation:#1]
replaced boolean return with true for com/rox/cartridge/INesRom::hasMagic → KILLED

2.2
Location : hasMagic
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[test-template:rejectsFileWithAnySingleMagicByteWrong(int)]/[test-template-invocation:#4]
negated conditional → KILLED

3.3
Location : hasMagic
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[test-template:rejectsFileWithAnySingleMagicByteWrong(int)]/[test-template-invocation:#2]
negated conditional → KILLED

4.4
Location : hasMagic
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[test-template:rejectsFileWithAnySingleMagicByteWrong(int)]/[test-template-invocation:#3]
negated conditional → KILLED

5.5
Location : hasMagic
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[test-template:rejectsFileWithAnySingleMagicByteWrong(int)]/[test-template-invocation:#1]
negated conditional → KILLED

83

1.1
Location : copyRange
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:parsesMapperNumberFromLowNibbleOfFlags6()]
changed conditional boundary → KILLED

2.2
Location : copyRange
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:rejectsPrgTruncationEvenWhenChrIsAlsoDeclared()]
negated conditional → KILLED

3.3
Location : copyRange
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:rejectsPrgTruncationEvenWhenChrIsAlsoDeclared()]
Replaced integer addition with subtraction → KILLED

87

1.1
Location : copyRange
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:acceptsFileExactlyTheHeaderSizeWithNoPrgOrChrBanks()]
replaced return value with null for com/rox/cartridge/INesRom::copyRange → KILLED

2.2
Location : copyRange
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:parsesMapperNumberFromLowNibbleOfFlags6()]
Replaced integer addition with subtraction → KILLED

91

1.1
Location : mapperNumber
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:parsesMapperNumberFromLowNibbleOfFlags6()]
replaced int return with 0 for com/rox/cartridge/INesRom::mapperNumber → KILLED

95

1.1
Location : isVerticalMirroring
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:verticalMirroringBitIsRead()]
replaced boolean return with true for com/rox/cartridge/INesRom::isVerticalMirroring → KILLED

2.2
Location : isVerticalMirroring
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:verticalMirroringBitIsRead()]
replaced boolean return with false for com/rox/cartridge/INesRom::isVerticalMirroring → KILLED

100

1.1
Location : hasBattery
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:batteryBitIsRead()]
replaced boolean return with true for com/rox/cartridge/INesRom::hasBattery → KILLED

2.2
Location : hasBattery
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:batteryBitIsRead()]
replaced boolean return with false for com/rox/cartridge/INesRom::hasBattery → KILLED

105

1.1
Location : hasTrainer
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:trainerBitIsRead()]
replaced boolean return with true for com/rox/cartridge/INesRom::hasTrainer → KILLED

2.2
Location : hasTrainer
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:trainerBitIsRead()]
replaced boolean return with false for com/rox/cartridge/INesRom::hasTrainer → KILLED

110

1.1
Location : prgRom
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:acceptsFileExactlyTheHeaderSizeWithNoPrgOrChrBanks()]
replaced return value with null for com/rox/cartridge/INesRom::prgRom → KILLED

115

1.1
Location : chrRom
Killed by : com.rox.cartridge.INesRomTest.[engine:junit-jupiter]/[class:com.rox.cartridge.INesRomTest]/[method:chrRomGetterReturnsADefensiveCopy()]
replaced return value with null for com/rox/cartridge/INesRom::chrRom → KILLED

Active mutators

Tests examined


Report generated by PIT 1.22.1