| 1 | package com.rox.cartridge; | |
| 2 | ||
| 3 | import com.rox.mem.RAM; | |
| 4 | ||
| 5 | import java.util.LinkedHashMap; | |
| 6 | import java.util.Map; | |
| 7 | ||
| 8 | import static com.rox.ByteUtil.BYTE_MASK; | |
| 9 | ||
| 10 | /** | |
| 11 | * iNES mapper 4 (MMC3). Four register pairs at {@code $8000-$FFFF}, even address selects the "low" | |
| 12 | * register of the pair, odd selects the "high" one: | |
| 13 | * <ul> | |
| 14 | * <li>{@code $8000}/{@code $8001} - bank select / bank data: {@code $8000} picks which of 8 | |
| 15 | * internal bank registers (R0-R7) the next {@code $8001} write latches into, plus the PRG bank | |
| 16 | * mode (bit 6) and CHR A12 inversion (bit 7) bits.</li> | |
| 17 | * <li>{@code $A000}/{@code $A001} - nametable mirroring / PRG-RAM write-protect. The protect bits | |
| 18 | * are deliberately not modeled - real MMC3 boards use them to write-protect save RAM during | |
| 19 | * power-off, not something a running emulator needs, and modeling them risks the same MMC6 | |
| 20 | * register-meaning incompatibility real emulators avoid by skipping this too.</li> | |
| 21 | * <li>{@code $C000}/{@code $C001} - IRQ latch / IRQ reload.</li> | |
| 22 | * <li>{@code $E000}/{@code $E001} - IRQ disable(+acknowledge) / IRQ enable.</li> | |
| 23 | * </ul> | |
| 24 | * | |
| 25 | * PRG-ROM: four 8KB windows ({@code $8000-$9FFF}, {@code $A000-$BFFF}, {@code $C000-$DFFF}, | |
| 26 | * {@code $E000-$FFFF}). R7 always drives {@code $A000-$BFFF} and the last bank is always fixed at | |
| 27 | * {@code $E000-$FFFF}; the bank-mode bit swaps which of {@code $8000-$9FFF}/{@code $C000-$DFFF} is | |
| 28 | * R6-switchable and which is fixed to the second-last bank. 8KB PRG-RAM at {@code $6000-$7FFF}, | |
| 29 | * always readable/writable - this codebase doesn't model the PRG-RAM chip-enable bit (see above), | |
| 30 | * matching {@link Mmc1Mapper}'s own equivalent simplification. | |
| 31 | * | |
| 32 | * CHR: two 2KB windows (R0, R1) and four 1KB windows (R2-R5) tiling {@code $0000-$1FFF}; the CHR A12 | |
| 33 | * inversion bit swaps which half ({@code $0000-$0FFF} or {@code $1000-$1FFF}) holds the 2KB windows | |
| 34 | * versus the 1KB ones - verified against nesdev.org's own bulleted "PPU $0000-$07FF (or $1000-$17FF)" | |
| 35 | * listing (not its separately-rendered summary table, which garbles this exact mapping - re-checked | |
| 36 | * directly against the page's raw HTML after that discrepancy surfaced). CHR-RAM (a single fixed 8KB | |
| 37 | * bank, banking registers simply ignored) on boards with no CHR-ROM in the file, same convention as | |
| 38 | * {@link NromMapper}/{@link Mmc1Mapper}. | |
| 39 | * | |
| 40 | * IRQ: a scanline counter clocked by a rising edge on the PPU address bus's A12 line (bit 12 going | |
| 41 | * 0→1) - on real hardware, filtered to require the line was low for a few PPU cycles first, to reject | |
| 42 | * spurious mid-fetch toggles. That filter is deliberately not modeled here: this codebase's PPU | |
| 43 | * ({@code PPU.readMemory}) drives every pattern-table fetch - background and sprite alike - through | |
| 44 | * {@link #readChr}, at the same real per-dot cadence real hardware uses, so a plain, unfiltered | |
| 45 | * 0→1 edge on {@link #readChr}'s own address already reproduces the intended "once per scanline" | |
| 46 | * signal for the standard (and by far most common) configuration nesdev itself documents as required | |
| 47 | * for the counter to work at all: background and sprites drawn from different pattern-table halves. | |
| 48 | * Counter semantics (verified against nesdev.org's "Counter operation"/"Important points" sections): | |
| 49 | * clocked on that edge, the counter reloads from the IRQ latch if it's currently zero or a reload was | |
| 50 | * requested (via {@code $C001}), otherwise it decrements; an IRQ is asserted whenever the | |
| 51 | * (post-clock) counter is zero and IRQs are enabled. The counter itself is never gated by the | |
| 52 | * enable/disable registers - only whether reaching zero actually asserts the IRQ line is. | |
| 53 | * | |
| 54 | * <p><b>Known limitation</b> (flagged by CodeRabbit review on PR #37, not yet fixed): the "no filter | |
| 55 | * needed" reasoning above only holds when every sprite fetched on a scanline reads from the same | |
| 56 | * pattern-table half - true for the standard background-at-{@code $0000}/sprites-at-{@code $1000} | |
| 57 | * (or vice versa) configuration nesdev documents as required for the counter to work at all, but not | |
| 58 | * for 8x16 sprites, where each sprite's own tile index (not a single shared PPUCTRL bit) picks its | |
| 59 | * pattern-table half. A frame mixing even- and odd-indexed 8x16 sprites can toggle A12 more than once | |
| 60 | * within one scanline's sprite-fetch phase, which this unfiltered edge detector would (incorrectly) | |
| 61 | * clock as multiple rises. Compounding this, {@code PPU.fetchSpritesForNextScanline()} still fetches | |
| 62 | * all 8 sprite slots (real and dummy) in one collapsed step at dot 257 rather than real hardware's | |
| 63 | * spread across dots 257-320, so even a correctly-filtered edge could land at the wrong simulated | |
| 64 | * instant relative to the scanline. Properly fixing this needs two coordinated changes neither made | |
| 65 | * here: PPU-cycle-aware low-time qualification in this class (real hardware requires A12 low for a | |
| 66 | * few PPU cycles before counting a rise), and un-collapsing the PPU's sprite fetch back into its own | |
| 67 | * per-slot dots so that timing information actually exists to qualify against - a bigger change than | |
| 68 | * this simplification was worth making speculatively, since no ROM this codebase currently runs | |
| 69 | * exercises 8x16-sprite MMC3 IRQ timing (Shadowgate's own mapper-4 conversion doesn't use the IRQ | |
| 70 | * mechanism at all, verified by instrumenting every {@code $8000-$FFFF} register write across full | |
| 71 | * playthroughs). Revisit if/when a ROM actually depends on this. | |
| 72 | */ | |
| 73 | public final class Mmc3Mapper implements Mapper { | |
| 74 | private static final int PRG_RAM_SIZE = 0x2000; | |
| 75 | private static final int PRG_ROM_START_ADDRESS = 0x8000; | |
| 76 | private static final int PRG_BANK_SIZE = 0x2000; | |
| 77 | private static final int CHR_WINDOW_SIZE = 0x2000; | |
| 78 | private static final int CHR_BANK_1KB = 0x400; | |
| 79 | private static final int CHR_A12_BIT = 0x1000; | |
| 80 | ||
| 81 | private static final int BANK_SELECT_REGISTER_MASK = 0x07; | |
| 82 | private static final int PRG_MODE_BIT = 0x40; | |
| 83 | private static final int CHR_INVERSION_BIT = 0x80; | |
| 84 | private static final int PRG_BANK_NUMBER_MASK = 0x3F; //MMC3 has only 6 PRG ROM address lines | |
| 85 | private static final int MIRRORING_REGISTER_START = 0xA000; | |
| 86 | private static final int IRQ_LATCH_REGISTER_START = 0xC000; | |
| 87 | private static final int IRQ_ENABLE_REGISTER_START = 0xE000; | |
| 88 | ||
| 89 | private final byte[] prgRom; | |
| 90 | private final RAM prgRam = new RAM(PRG_RAM_SIZE, "PRG-RAM"); | |
| 91 | ||
| 92 | private final ChrMemory chr; | |
| 93 | ||
| 94 | /** R0-R7 - unspecified at power-on, same as real hardware */ | |
| 95 | private final int[] bankRegister = new int[8]; | |
| 96 | /** last value written to $8000: register-select bits, PRG mode bit, CHR inversion bit */ | |
| 97 | private int bankSelect; | |
| 98 | private boolean horizontalMirroring; | |
| 99 | ||
| 100 | private int irqLatch; | |
| 101 | private int irqCounter; | |
| 102 | private boolean irqReloadRequested; | |
| 103 | private boolean irqEnabled; | |
| 104 | private boolean irqPending; | |
| 105 | private boolean lastChrAddressA12High; | |
| 106 | ||
| 107 | public Mmc3Mapper(final INesRom rom){ | |
| 108 | this.prgRom = rom.prgRom(); | |
| 109 |
2
1. <init> : negated conditional → KILLED 2. <init> : changed conditional boundary → KILLED |
if (prgRom.length < PRG_BANK_SIZE * 2){ |
| 110 | throw new IllegalArgumentException("MMC3 requires at least 16KB of PRG-ROM, got " + prgRom.length + " bytes"); | |
| 111 | } | |
| 112 | ||
| 113 | this.chr = ChrMemory.of(rom, CHR_WINDOW_SIZE); | |
| 114 | ||
| 115 | //a reasonable initial hint, immediately overridden by the game's own $A000 write - real MMC3 | |
| 116 | //hardware has no defined power-on mirroring state of its own (see class doc's "$8000... is | |
| 117 | //unspecified at power on" quote, equally true here) | |
| 118 |
1
1. <init> : negated conditional → KILLED |
this.horizontalMirroring = !rom.isVerticalMirroring(); |
| 119 | } | |
| 120 | ||
| 121 | /** Contract: only ever called with {@code address} in {@code $6000-$FFFF} (see {@link Mapper}'s class doc). */ | |
| 122 | @Override | |
| 123 | public int read(final int address){ | |
| 124 |
2
1. read : changed conditional boundary → KILLED 2. read : negated conditional → KILLED |
if (address >= PRG_ROM_START_ADDRESS){ |
| 125 |
2
1. read : replaced int return with 0 for com/rox/cartridge/Mmc3Mapper::read → KILLED 2. read : Replaced bitwise AND with OR → KILLED |
return prgRom[prgRomOffset(address)] & BYTE_MASK; |
| 126 | } | |
| 127 |
1
1. read : replaced int return with 0 for com/rox/cartridge/Mmc3Mapper::read → KILLED |
return prgRam.read(address); |
| 128 | } | |
| 129 | ||
| 130 | /** Contract: only ever called with {@code address} in {@code $6000-$FFFF} (see {@link Mapper}'s class doc). */ | |
| 131 | @Override | |
| 132 | public void write(final int address, final int value){ | |
| 133 |
2
1. write : changed conditional boundary → KILLED 2. write : negated conditional → KILLED |
if (address < PRG_ROM_START_ADDRESS){ |
| 134 |
1
1. write : removed call to com/rox/mem/RAM::write → KILLED |
prgRam.write(address, value); |
| 135 | return; | |
| 136 | } | |
| 137 |
2
1. write : Replaced bitwise AND with OR → KILLED 2. write : negated conditional → KILLED |
final boolean even = (address & 1) == 0; |
| 138 |
2
1. write : changed conditional boundary → KILLED 2. write : negated conditional → KILLED |
if (address < MIRRORING_REGISTER_START){ |
| 139 |
1
1. write : negated conditional → KILLED |
if (even){ |
| 140 |
1
1. write : Replaced bitwise AND with OR → KILLED |
bankSelect = value & BYTE_MASK; |
| 141 | } else { | |
| 142 |
1
1. write : removed call to com/rox/cartridge/Mmc3Mapper::writeBankData → KILLED |
writeBankData(value); |
| 143 | } | |
| 144 |
2
1. write : changed conditional boundary → KILLED 2. write : negated conditional → KILLED |
} else if (address < IRQ_LATCH_REGISTER_START){ |
| 145 |
1
1. write : negated conditional → KILLED |
if (even){ |
| 146 |
2
1. write : Replaced bitwise AND with OR → KILLED 2. write : negated conditional → KILLED |
horizontalMirroring = (value & 1) == 0; |
| 147 | } | |
| 148 | //odd ($A001): PRG-RAM write-protect - deliberately not modeled, see class doc | |
| 149 |
2
1. write : negated conditional → KILLED 2. write : changed conditional boundary → KILLED |
} else if (address < IRQ_ENABLE_REGISTER_START){ |
| 150 |
1
1. write : negated conditional → KILLED |
if (even){ |
| 151 |
1
1. write : Replaced bitwise AND with OR → KILLED |
irqLatch = value & BYTE_MASK; |
| 152 | } else { | |
| 153 | //"clears the MMC3 IRQ counter immediately, and then reloads it at the NEXT rising edge" | |
| 154 | irqCounter = 0; | |
| 155 | irqReloadRequested = true; | |
| 156 | } | |
| 157 | } else { | |
| 158 |
1
1. write : negated conditional → KILLED |
if (even){ |
| 159 | irqEnabled = false; | |
| 160 | irqPending = false; //"acknowledge any pending interrupts" | |
| 161 | } else { | |
| 162 | irqEnabled = true; //"the counter remains unaffected" - only future IRQ assertion is gated | |
| 163 | } | |
| 164 | } | |
| 165 | } | |
| 166 | ||
| 167 | private void writeBankData(final int value){ | |
| 168 |
1
1. writeBankData : Replaced bitwise AND with OR → KILLED |
final int register = bankSelect & BANK_SELECT_REGISTER_MASK; |
| 169 | final int masked = switch (register){ | |
| 170 |
1
1. writeBankData : Replaced bitwise AND with OR → KILLED |
case 0, 1 -> value & ~1; //R0/R1: 2KB CHR banks - bottom bit ignored, always an even bank number |
| 171 |
1
1. writeBankData : Replaced bitwise AND with OR → KILLED |
case 6, 7 -> value & PRG_BANK_NUMBER_MASK; |
| 172 |
1
1. writeBankData : Replaced bitwise AND with OR → KILLED |
default -> value & BYTE_MASK; |
| 173 | }; | |
| 174 | bankRegister[register] = masked; | |
| 175 | } | |
| 176 | ||
| 177 | private int prgRomOffset(final int address){ | |
| 178 |
1
1. prgRomOffset : Replaced integer subtraction with addition → KILLED |
final int windowOffset = address - PRG_ROM_START_ADDRESS; //0..0x7FFF |
| 179 |
1
1. prgRomOffset : Replaced integer division with multiplication → KILLED |
final int window = windowOffset / PRG_BANK_SIZE; //0-3 |
| 180 |
1
1. prgRomOffset : Replaced integer division with multiplication → KILLED |
final int totalBanks = prgRom.length / PRG_BANK_SIZE; |
| 181 |
2
1. prgRomOffset : Replaced bitwise AND with OR → KILLED 2. prgRomOffset : negated conditional → KILLED |
final boolean prgMode1 = (bankSelect & PRG_MODE_BIT) != 0; |
| 182 | final int bank = switch (window){ | |
| 183 |
3
1. prgRomOffset : Replaced integer modulus with multiplication → KILLED 2. prgRomOffset : Replaced integer subtraction with addition → KILLED 3. prgRomOffset : negated conditional → KILLED |
case 0 -> prgMode1 ? totalBanks - 2 : bankRegister[6] % totalBanks; |
| 184 |
1
1. prgRomOffset : Replaced integer modulus with multiplication → KILLED |
case 1 -> bankRegister[7] % totalBanks; |
| 185 |
3
1. prgRomOffset : negated conditional → KILLED 2. prgRomOffset : Replaced integer subtraction with addition → KILLED 3. prgRomOffset : Replaced integer modulus with multiplication → KILLED |
case 2 -> prgMode1 ? bankRegister[6] % totalBanks : totalBanks - 2; |
| 186 |
1
1. prgRomOffset : Replaced integer subtraction with addition → KILLED |
default -> totalBanks - 1; //$E000-$FFFF: always the last bank |
| 187 | }; | |
| 188 |
4
1. prgRomOffset : Replaced integer multiplication with division → KILLED 2. prgRomOffset : replaced int return with 0 for com/rox/cartridge/Mmc3Mapper::prgRomOffset → KILLED 3. prgRomOffset : Replaced integer addition with subtraction → KILLED 4. prgRomOffset : Replaced bitwise AND with OR → KILLED |
return bank * PRG_BANK_SIZE + (windowOffset & (PRG_BANK_SIZE - 1)); |
| 189 | } | |
| 190 | ||
| 191 | @Override | |
| 192 | public int readChr(final int address){ | |
| 193 |
1
1. readChr : removed call to com/rox/cartridge/Mmc3Mapper::clockScanlineCounterOnA12RisingEdge → KILLED |
clockScanlineCounterOnA12RisingEdge(address); |
| 194 |
1
1. readChr : replaced int return with 0 for com/rox/cartridge/Mmc3Mapper::readChr → KILLED |
return chr.read(chrOffset(address)); |
| 195 | } | |
| 196 | ||
| 197 | /** No-op if Chr is ROM/Read-Only */ | |
| 198 | @Override | |
| 199 | public void writeChr(final int address, final int value){ | |
| 200 |
1
1. writeChr : removed call to com/rox/cartridge/ChrMemory::write → KILLED |
chr.write(chrOffset(address), value); |
| 201 | } | |
| 202 | ||
| 203 | private void clockScanlineCounterOnA12RisingEdge(final int address){ | |
| 204 |
2
1. clockScanlineCounterOnA12RisingEdge : negated conditional → KILLED 2. clockScanlineCounterOnA12RisingEdge : Replaced bitwise AND with OR → KILLED |
final boolean a12High = (address & CHR_A12_BIT) != 0; |
| 205 |
2
1. clockScanlineCounterOnA12RisingEdge : negated conditional → KILLED 2. clockScanlineCounterOnA12RisingEdge : negated conditional → KILLED |
if (a12High && !lastChrAddressA12High){ |
| 206 |
2
1. clockScanlineCounterOnA12RisingEdge : negated conditional → KILLED 2. clockScanlineCounterOnA12RisingEdge : negated conditional → KILLED |
if (irqCounter == 0 || irqReloadRequested){ |
| 207 | irqCounter = irqLatch; | |
| 208 | } else { | |
| 209 |
1
1. clockScanlineCounterOnA12RisingEdge : Replaced integer subtraction with addition → KILLED |
irqCounter--; |
| 210 | } | |
| 211 | irqReloadRequested = false; | |
| 212 |
2
1. clockScanlineCounterOnA12RisingEdge : negated conditional → KILLED 2. clockScanlineCounterOnA12RisingEdge : negated conditional → KILLED |
if (irqCounter == 0 && irqEnabled){ |
| 213 | irqPending = true; | |
| 214 | } | |
| 215 | } | |
| 216 | lastChrAddressA12High = a12High; | |
| 217 | } | |
| 218 | ||
| 219 | private int chrOffset(final int address){ | |
| 220 |
1
1. chrOffset : Replaced bitwise AND with OR → KILLED |
final int a = address & (CHR_WINDOW_SIZE - 1); |
| 221 |
2
1. chrOffset : negated conditional → KILLED 2. chrOffset : Replaced bitwise AND with OR → KILLED |
final boolean inverted = (bankSelect & CHR_INVERSION_BIT) != 0; |
| 222 |
2
1. chrOffset : negated conditional → KILLED 2. chrOffset : Replaced XOR with AND → KILLED |
final int effective = inverted ? a ^ CHR_A12_BIT : a; |
| 223 |
1
1. chrOffset : Replaced integer division with multiplication → KILLED |
final int region = effective / CHR_BANK_1KB; //0-7 |
| 224 | final int bank1k = switch (region){ | |
| 225 | case 0 -> bankRegister[0]; | |
| 226 |
1
1. chrOffset : Replaced integer addition with subtraction → KILLED |
case 1 -> bankRegister[0] + 1; |
| 227 | case 2 -> bankRegister[1]; | |
| 228 |
1
1. chrOffset : Replaced integer addition with subtraction → KILLED |
case 3 -> bankRegister[1] + 1; |
| 229 | case 4 -> bankRegister[2]; | |
| 230 | case 5 -> bankRegister[3]; | |
| 231 | case 6 -> bankRegister[4]; | |
| 232 | default -> bankRegister[5]; //region 7 | |
| 233 | }; | |
| 234 | //no separate "bank1k % totalKbBanks" wraparound step needed here: chrSize is always an exact | |
| 235 | //multiple of CHR_BANK_1KB, so the trailing "% chrSize" below already produces the identical | |
| 236 | //result on its own - (bank1k % n)*k % (n*k) == bank1k*k % (n*k) for any non-negative bank1k | |
| 237 |
1
1. chrOffset : Replaced bitwise AND with OR → KILLED |
final int offsetWithinBank = effective & (CHR_BANK_1KB - 1); |
| 238 |
4
1. chrOffset : replaced int return with 0 for com/rox/cartridge/Mmc3Mapper::chrOffset → KILLED 2. chrOffset : Replaced integer modulus with multiplication → KILLED 3. chrOffset : Replaced integer addition with subtraction → KILLED 4. chrOffset : Replaced integer multiplication with division → KILLED |
return (bank1k * CHR_BANK_1KB + offsetWithinBank) % chr.size(); |
| 239 | } | |
| 240 | ||
| 241 | @Override | |
| 242 | public Mirroring nametableMirroring(){ | |
| 243 |
2
1. nametableMirroring : replaced return value with null for com/rox/cartridge/Mmc3Mapper::nametableMirroring → KILLED 2. nametableMirroring : negated conditional → KILLED |
return horizontalMirroring ? Mirroring.HORIZONTAL : Mirroring.VERTICAL; |
| 244 | } | |
| 245 | ||
| 246 | @Override | |
| 247 | public boolean isIrqAsserted(){ | |
| 248 |
2
1. isIrqAsserted : replaced boolean return with false for com/rox/cartridge/Mmc3Mapper::isIrqAsserted → KILLED 2. isIrqAsserted : replaced boolean return with true for com/rox/cartridge/Mmc3Mapper::isIrqAsserted → KILLED |
return irqPending; |
| 249 | } | |
| 250 | ||
| 251 | @Override | |
| 252 | public int[] prgRam(){ | |
| 253 |
1
1. prgRam : replaced return value with null for com/rox/cartridge/Mmc3Mapper::prgRam → KILLED |
return prgRam.snapshot(); |
| 254 | } | |
| 255 | ||
| 256 | @Override | |
| 257 | public void restorePrgRam(final int[] prgRam){ | |
| 258 |
1
1. restorePrgRam : removed call to com/rox/mem/RAM::restore → KILLED |
this.prgRam.restore(prgRam); |
| 259 | } | |
| 260 | ||
| 261 | int bankRegister(final int index){ | |
| 262 |
1
1. bankRegister : replaced int return with 0 for com/rox/cartridge/Mmc3Mapper::bankRegister → KILLED |
return bankRegister[index]; |
| 263 | } | |
| 264 | ||
| 265 | int bankSelect(){ | |
| 266 |
1
1. bankSelect : replaced int return with 0 for com/rox/cartridge/Mmc3Mapper::bankSelect → KILLED |
return bankSelect; |
| 267 | } | |
| 268 | ||
| 269 | int irqCounter(){ | |
| 270 |
1
1. irqCounter : replaced int return with 0 for com/rox/cartridge/Mmc3Mapper::irqCounter → KILLED |
return irqCounter; |
| 271 | } | |
| 272 | ||
| 273 | int irqLatch(){ | |
| 274 |
1
1. irqLatch : replaced int return with 0 for com/rox/cartridge/Mmc3Mapper::irqLatch → KILLED |
return irqLatch; |
| 275 | } | |
| 276 | ||
| 277 | boolean irqEnabled(){ | |
| 278 |
2
1. irqEnabled : replaced boolean return with false for com/rox/cartridge/Mmc3Mapper::irqEnabled → KILLED 2. irqEnabled : replaced boolean return with true for com/rox/cartridge/Mmc3Mapper::irqEnabled → KILLED |
return irqEnabled; |
| 279 | } | |
| 280 | ||
| 281 | @Override | |
| 282 | public Map<String, String> debugState(){ | |
| 283 | final Map<String, String> state = new LinkedHashMap<>(); | |
| 284 | state.put("Bank select", String.format("$%02X", bankSelect)); | |
| 285 |
2
1. debugState : negated conditional → KILLED 2. debugState : changed conditional boundary → KILLED |
for (int i = 0; i < bankRegister.length; i++){ |
| 286 | state.put("R" + i, String.format("$%02X", bankRegister[i])); | |
| 287 | } | |
| 288 | state.put("IRQ latch", String.format("$%02X", irqLatch)); | |
| 289 | state.put("IRQ counter", String.format("$%02X", irqCounter)); | |
| 290 | state.put("IRQ enabled", String.valueOf(irqEnabled)); | |
| 291 |
1
1. debugState : replaced return value with Collections.emptyMap for com/rox/cartridge/Mmc3Mapper::debugState → KILLED |
return state; |
| 292 | } | |
| 293 | ||
| 294 | @Override | |
| 295 | public MapperSnapshot snapshot(){ | |
| 296 |
1
1. snapshot : replaced return value with null for com/rox/cartridge/Mmc3Mapper::snapshot → KILLED |
return new Mmc3MapperSnapshot(prgRam.snapshot(), chr.snapshot(), bankRegister.clone(), bankSelect, |
| 297 | horizontalMirroring, irqLatch, irqCounter, irqReloadRequested, irqEnabled, irqPending, | |
| 298 | lastChrAddressA12High); | |
| 299 | } | |
| 300 | ||
| 301 | @Override | |
| 302 | public void checkRestorable(final MapperSnapshot snapshot){ | |
| 303 | final Mmc3MapperSnapshot mmc3 = SnapshotArrays.expect(snapshot, Mmc3MapperSnapshot.class); | |
| 304 |
1
1. checkRestorable : removed call to com/rox/mem/RAM::checkRestorable → KILLED |
prgRam.checkRestorable(mmc3.prgRam()); |
| 305 |
1
1. checkRestorable : removed call to com/rox/cartridge/ChrMemory::checkRestorable → KILLED |
chr.checkRestorable(mmc3.chrRam()); |
| 306 |
1
1. checkRestorable : removed call to com/rox/cartridge/SnapshotArrays::requireSameSize → KILLED |
SnapshotArrays.requireSameSize(mmc3.bankRegisters(), bankRegister, "bank registers"); |
| 307 | } | |
| 308 | ||
| 309 | @Override | |
| 310 | public void restore(final MapperSnapshot snapshot){ | |
| 311 |
1
1. restore : removed call to com/rox/cartridge/Mmc3Mapper::checkRestorable → KILLED |
checkRestorable(snapshot); |
| 312 | final Mmc3MapperSnapshot mmc3 = (Mmc3MapperSnapshot) snapshot; | |
| 313 |
1
1. restore : removed call to com/rox/mem/RAM::restore → KILLED |
prgRam.restore(mmc3.prgRam()); |
| 314 |
1
1. restore : removed call to com/rox/cartridge/ChrMemory::restore → KILLED |
chr.restore(mmc3.chrRam()); |
| 315 |
1
1. restore : removed call to com/rox/cartridge/SnapshotArrays::copy → KILLED |
SnapshotArrays.copy(mmc3.bankRegisters(), bankRegister); |
| 316 | bankSelect = mmc3.bankSelect(); | |
| 317 | horizontalMirroring = mmc3.horizontalMirroring(); | |
| 318 | irqLatch = mmc3.irqLatch(); | |
| 319 | irqCounter = mmc3.irqCounter(); | |
| 320 | irqReloadRequested = mmc3.irqReloadRequested(); | |
| 321 | irqEnabled = mmc3.irqEnabled(); | |
| 322 | irqPending = mmc3.irqPending(); | |
| 323 | lastChrAddressA12High = mmc3.lastChrAddressA12High(); | |
| 324 | } | |
| 325 | } | |
Mutations | ||
| 109 |
1.1 2.2 |
|
| 118 |
1.1 |
|
| 124 |
1.1 2.2 |
|
| 125 |
1.1 2.2 |
|
| 127 |
1.1 |
|
| 133 |
1.1 2.2 |
|
| 134 |
1.1 |
|
| 137 |
1.1 2.2 |
|
| 138 |
1.1 2.2 |
|
| 139 |
1.1 |
|
| 140 |
1.1 |
|
| 142 |
1.1 |
|
| 144 |
1.1 2.2 |
|
| 145 |
1.1 |
|
| 146 |
1.1 2.2 |
|
| 149 |
1.1 2.2 |
|
| 150 |
1.1 |
|
| 151 |
1.1 |
|
| 158 |
1.1 |
|
| 168 |
1.1 |
|
| 170 |
1.1 |
|
| 171 |
1.1 |
|
| 172 |
1.1 |
|
| 178 |
1.1 |
|
| 179 |
1.1 |
|
| 180 |
1.1 |
|
| 181 |
1.1 2.2 |
|
| 183 |
1.1 2.2 3.3 |
|
| 184 |
1.1 |
|
| 185 |
1.1 2.2 3.3 |
|
| 186 |
1.1 |
|
| 188 |
1.1 2.2 3.3 4.4 |
|
| 193 |
1.1 |
|
| 194 |
1.1 |
|
| 200 |
1.1 |
|
| 204 |
1.1 2.2 |
|
| 205 |
1.1 2.2 |
|
| 206 |
1.1 2.2 |
|
| 209 |
1.1 |
|
| 212 |
1.1 2.2 |
|
| 220 |
1.1 |
|
| 221 |
1.1 2.2 |
|
| 222 |
1.1 2.2 |
|
| 223 |
1.1 |
|
| 226 |
1.1 |
|
| 228 |
1.1 |
|
| 237 |
1.1 |
|
| 238 |
1.1 2.2 3.3 4.4 |
|
| 243 |
1.1 2.2 |
|
| 248 |
1.1 2.2 |
|
| 253 |
1.1 |
|
| 258 |
1.1 |
|
| 262 |
1.1 |
|
| 266 |
1.1 |
|
| 270 |
1.1 |
|
| 274 |
1.1 |
|
| 278 |
1.1 2.2 |
|
| 285 |
1.1 2.2 |
|
| 291 |
1.1 |
|
| 296 |
1.1 |
|
| 304 |
1.1 |
|
| 305 |
1.1 |
|
| 306 |
1.1 |
|
| 311 |
1.1 |
|
| 313 |
1.1 |
|
| 314 |
1.1 |
|
| 315 |
1.1 |