mirror of
https://github.com/felipecsl/6502Android.git
synced 2024-09-28 08:54:56 +00:00
Merge pull request #2 from felipecsl/felipe/mapper
Initial implementation for Mapper 4
This commit is contained in:
commit
3950fea47c
@ -14,6 +14,7 @@ android {
|
|||||||
}
|
}
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main.java.srcDirs += 'src/main/kotlin'
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
test.java.srcDirs += 'src/test/kotlin'
|
||||||
}
|
}
|
||||||
testOptions {
|
testOptions {
|
||||||
unitTests.returnDefaultValues = true
|
unitTests.returnDefaultValues = true
|
||||||
|
@ -287,4 +287,8 @@ class CPU(val memory: Memory) : Display.Callbacks {
|
|||||||
override fun onDraw() {
|
override fun onDraw() {
|
||||||
executionLock?.countDown()
|
executionLock?.countDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun triggerIRQ() {
|
||||||
|
throw NotImplementedError()
|
||||||
|
}
|
||||||
}
|
}
|
5
app/src/main/kotlin/android/emu6502/nes/APU.kt
Normal file
5
app/src/main/kotlin/android/emu6502/nes/APU.kt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package android.emu6502.nes
|
||||||
|
|
||||||
|
class APU {
|
||||||
|
|
||||||
|
}
|
@ -4,14 +4,14 @@ import java.util.*
|
|||||||
|
|
||||||
data class Cartridge(
|
data class Cartridge(
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
val pgr: ByteArray, // PRG-ROM banks
|
val pgr: IntArray, // PRG-ROM banks
|
||||||
val chr: ByteArray, // CHR-ROM banks
|
val chr: IntArray, // CHR-ROM banks
|
||||||
val mapper: Byte, // mapper type
|
val mapper: Byte, // mapper type
|
||||||
val mirror: Byte, // mirroring mode
|
var mirror: Int, // mirroring mode
|
||||||
val battery: Byte // battery present
|
val battery: Byte, // battery present
|
||||||
|
val sram: IntArray = IntArray(0x2000) // Save RAM
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
) {
|
) {
|
||||||
private val sram: ByteArray = ByteArray(0x2000) // Save RAM
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
|
29
app/src/main/kotlin/android/emu6502/nes/Console.kt
Normal file
29
app/src/main/kotlin/android/emu6502/nes/Console.kt
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package android.emu6502.nes
|
||||||
|
|
||||||
|
import android.emu6502.CPU
|
||||||
|
import android.emu6502.Display
|
||||||
|
import android.emu6502.Memory
|
||||||
|
import android.emu6502.nes.mappers.Mapper
|
||||||
|
|
||||||
|
class Console(
|
||||||
|
val cpu: CPU,
|
||||||
|
val apu: APU,
|
||||||
|
val ppu: PPU,
|
||||||
|
val cartridge: Cartridge,
|
||||||
|
val controller1: Controller,
|
||||||
|
val controller2: Controller,
|
||||||
|
val mapper: Mapper,
|
||||||
|
val ram: ByteArray = ByteArray(2048)
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun newConsole(cartridge: Cartridge, display: Display): Console {
|
||||||
|
val ppu = PPU()
|
||||||
|
val memory = Memory(display)
|
||||||
|
val cpu = CPU(memory)
|
||||||
|
val mapper = Mapper.newMapper(cartridge, ppu, cpu)
|
||||||
|
val apu = APU()
|
||||||
|
return Console(cpu, apu, ppu, cartridge, Controller(), Controller(), mapper)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
5
app/src/main/kotlin/android/emu6502/nes/Controller.kt
Normal file
5
app/src/main/kotlin/android/emu6502/nes/Controller.kt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package android.emu6502.nes
|
||||||
|
|
||||||
|
class Controller {
|
||||||
|
|
||||||
|
}
|
@ -27,13 +27,13 @@ internal class INESFileParser {
|
|||||||
val inesFileHeader = parseFileHeader(stream)
|
val inesFileHeader = parseFileHeader(stream)
|
||||||
// mapper type
|
// mapper type
|
||||||
val control1 = inesFileHeader.control1.toInt()
|
val control1 = inesFileHeader.control1.toInt()
|
||||||
val mapper1 = control1.shr(4)
|
val mapper1 = control1 shr 4
|
||||||
val mapper2 = inesFileHeader.control2.toInt().shr(4)
|
val mapper2 = inesFileHeader.control2.toInt() shr 4
|
||||||
val mapper = if (mapper1 != 0) mapper1 else mapper2
|
val mapper = mapper1 or (mapper2 shl 4)
|
||||||
// mirroring type
|
// mirroring type
|
||||||
val mirror1 = control1.and(1)
|
val mirror1 = control1 and 1
|
||||||
val mirror2 = control1.shr(3).and(1)
|
val mirror2 = (control1 shr 3) and 1
|
||||||
val mirror = if (mirror1 != 0) mirror1 else mirror2.shl(1)
|
val mirror = mirror1 or (mirror2 shl 1)
|
||||||
// battery-backed RAM
|
// battery-backed RAM
|
||||||
val battery = control1.shr(1).and(1).toByte()
|
val battery = control1.shr(1).and(1).toByte()
|
||||||
// read prg-rom bank(s)
|
// read prg-rom bank(s)
|
||||||
@ -42,7 +42,8 @@ internal class INESFileParser {
|
|||||||
// read chr-rom bank(s)
|
// read chr-rom bank(s)
|
||||||
val chr = ByteArray(inesFileHeader.numCHR.toInt() * 8192)
|
val chr = ByteArray(inesFileHeader.numCHR.toInt() * 8192)
|
||||||
stream.read(chr)
|
stream.read(chr)
|
||||||
return Cartridge(pgr, chr, mapper.toByte(), mirror.toByte(), battery)
|
return Cartridge(pgr.map(Byte::toInt).toIntArray(), chr.map(Byte::toInt).toIntArray(),
|
||||||
|
mapper.toByte(), mirror, battery)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
47
app/src/main/kotlin/android/emu6502/nes/PPU.kt
Normal file
47
app/src/main/kotlin/android/emu6502/nes/PPU.kt
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package android.emu6502.nes
|
||||||
|
|
||||||
|
class PPU(
|
||||||
|
// @formatter:off
|
||||||
|
|
||||||
|
val cycle: Int = 0, // 0-340
|
||||||
|
val scanLine: Int = 0, // 0-261, 0-239=visible, 240=post, 241-260=vblank, 261=pre
|
||||||
|
val frame: Int = 0, // frame counter
|
||||||
|
|
||||||
|
// PPU registers
|
||||||
|
val v: Int = 0, // current vram address (15 bit)
|
||||||
|
val t: Int = 0, // temporary vram address (15 bit)
|
||||||
|
val x: Byte = 0, // fine x scroll (3 bit)
|
||||||
|
val w: Byte = 0, // write toggle (1 bit)
|
||||||
|
val f: Byte = 0, // even/odd frame flag (1 bit)
|
||||||
|
val register: Byte = 0,
|
||||||
|
|
||||||
|
// $2000 PPUCTRL
|
||||||
|
val flagNameTable: Boolean = false, // 0: $2000; 1: $2400; 2: $2800; 3: $2C00
|
||||||
|
val flagIncrement: Boolean = false, // 0: add 1; 1: add 32
|
||||||
|
val flagSpriteTable: Boolean = false, // 0: $0000; 1: $1000; ignored in 8x16 mode
|
||||||
|
val flagBackgroundTable: Boolean = false, // 0: $0000; 1: $1000
|
||||||
|
val flagSpriteSize: Boolean = false, // 0: 8x8; 1: 8x16
|
||||||
|
val flagMasterSlave: Boolean = false, // 0: read EXT; 1: write EXT
|
||||||
|
|
||||||
|
// $2001 PPUMASK
|
||||||
|
val flagGrayscale: Boolean = false, // 0: color; 1: grayscale
|
||||||
|
val flagShowLeftBackground: Boolean = false, // 0: hide; 1: show
|
||||||
|
val flagShowLeftSprites: Boolean = false, // 0: hide; 1: show
|
||||||
|
val flagShowBackground: Boolean = false, // 0: hide; 1: show
|
||||||
|
val flagShowSprites: Boolean = false, // 0: hide; 1: show
|
||||||
|
val flagRedTint: Boolean = false, // 0: normal; 1: emphasized
|
||||||
|
val flagGreenTint: Boolean = false, // 0: normal; 1: emphasized
|
||||||
|
val flagBlueTint: Boolean = false, // 0: normal; 1: emphasized
|
||||||
|
|
||||||
|
// $2002 PPUSTATUS
|
||||||
|
val flagSpriteZeroHit: Boolean = false,
|
||||||
|
val flagSpriteOverflow: Boolean = false,
|
||||||
|
|
||||||
|
// $2003 OAMADDR
|
||||||
|
val oamAddress: Byte = 0,
|
||||||
|
|
||||||
|
// $2007 PPUDATA
|
||||||
|
val bufferedData: Byte = 0 // for buffered reads
|
||||||
|
|
||||||
|
// @formatter:on
|
||||||
|
)
|
13
app/src/main/kotlin/android/emu6502/nes/mappers/AOROM.kt
Normal file
13
app/src/main/kotlin/android/emu6502/nes/mappers/AOROM.kt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package android.emu6502.nes.mappers
|
||||||
|
|
||||||
|
class AOROM : Mapper {
|
||||||
|
override fun write(address: Int, value: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun step() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(address: Int): Int {
|
||||||
|
throw NotImplementedError()
|
||||||
|
}
|
||||||
|
}
|
13
app/src/main/kotlin/android/emu6502/nes/mappers/CNROM.kt
Normal file
13
app/src/main/kotlin/android/emu6502/nes/mappers/CNROM.kt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package android.emu6502.nes.mappers
|
||||||
|
|
||||||
|
class CNROM : Mapper {
|
||||||
|
override fun write(address: Int, value: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun step() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(address: Int): Int {
|
||||||
|
throw NotImplementedError()
|
||||||
|
}
|
||||||
|
}
|
13
app/src/main/kotlin/android/emu6502/nes/mappers/MMC1.kt
Normal file
13
app/src/main/kotlin/android/emu6502/nes/mappers/MMC1.kt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package android.emu6502.nes.mappers
|
||||||
|
|
||||||
|
class MMC1 : Mapper {
|
||||||
|
override fun write(address: Int, value: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun step() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(address: Int): Int {
|
||||||
|
throw NotImplementedError()
|
||||||
|
}
|
||||||
|
}
|
209
app/src/main/kotlin/android/emu6502/nes/mappers/MMC3.kt
Normal file
209
app/src/main/kotlin/android/emu6502/nes/mappers/MMC3.kt
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
package android.emu6502.nes.mappers
|
||||||
|
|
||||||
|
import android.emu6502.CPU
|
||||||
|
import android.emu6502.nes.Cartridge
|
||||||
|
import android.emu6502.nes.PPU
|
||||||
|
import android.emu6502.toHexString
|
||||||
|
|
||||||
|
// http://wiki.nesdev.com/w/index.php/MMC3
|
||||||
|
class MMC3(
|
||||||
|
private val cartridge: Cartridge,
|
||||||
|
private val ppu: PPU,
|
||||||
|
private val cpu: CPU
|
||||||
|
) : Mapper {
|
||||||
|
private var register: Int = 0
|
||||||
|
private var registers = IntArray(8)
|
||||||
|
private var prgMode: Int = 0
|
||||||
|
private var chrMode: Int = 0
|
||||||
|
private var prgOffsets = IntArray(4)
|
||||||
|
private var chrOffsets = IntArray(8)
|
||||||
|
private var reload: Int = 0
|
||||||
|
private var counter: Int = 0
|
||||||
|
private var irqEnable: Boolean = false
|
||||||
|
|
||||||
|
override fun read(address: Int): Int {
|
||||||
|
if (address < 0x2000) {
|
||||||
|
val bank = address / 0x0400
|
||||||
|
val offset = address % 0x0400
|
||||||
|
return cartridge.chr[chrOffsets[bank] + offset]
|
||||||
|
}
|
||||||
|
if (address >= 0x8000) {
|
||||||
|
val addr = address - 0x8000
|
||||||
|
val bank = addr / 0x2000
|
||||||
|
val offset = addr % 0x2000
|
||||||
|
return cartridge.pgr[prgOffsets[bank] + offset]
|
||||||
|
}
|
||||||
|
if (address >= 0x6000) {
|
||||||
|
return cartridge.sram[address - 0x6000]
|
||||||
|
}
|
||||||
|
throw RuntimeException("unhandled mapper4 read at address: ${address.toHexString()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(address: Int, value: Int) {
|
||||||
|
if (address < 0x2000) {
|
||||||
|
val bank = address / 0x0400
|
||||||
|
val offset = address % 0x0400
|
||||||
|
cartridge.chr[chrOffsets[bank] + offset] = value
|
||||||
|
} else if (address >= 0x8000) {
|
||||||
|
writeRegister(address, value)
|
||||||
|
} else if (address >= 0x6000) {
|
||||||
|
cartridge.sram[address - 0x6000] = value
|
||||||
|
} else {
|
||||||
|
throw RuntimeException("unhandled mapper4 write at address ${address.toHexString()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeRegister(address: Int, value: Int) {
|
||||||
|
if (address <= 0x9FFF && address % 2 == 0)
|
||||||
|
writeBankSelect(value)
|
||||||
|
else if (address <= 0x9FFF && address % 2 == 1)
|
||||||
|
writeBankData(value)
|
||||||
|
else if (address <= 0xBFFF && address % 2 == 0)
|
||||||
|
writeMirror(value)
|
||||||
|
else if (address <= 0xBFFF && address % 2 == 1)
|
||||||
|
writeProtect()
|
||||||
|
else if (address <= 0xDFFF && address % 2 == 0)
|
||||||
|
writeIRQLatch(value)
|
||||||
|
else if (address <= 0xDFFF && address % 2 == 1)
|
||||||
|
writeIRQReload()
|
||||||
|
else if (address <= 0xFFFF && address % 2 == 0)
|
||||||
|
writeIRQDisable()
|
||||||
|
else if (address <= 0xFFFF && address % 2 == 1)
|
||||||
|
writeIRQEnable()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeBankSelect(value: Int) {
|
||||||
|
prgMode = (value shr 6) and 1
|
||||||
|
chrMode = (value shr 7) and 1
|
||||||
|
register = value and 7
|
||||||
|
updateOffsets()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateOffsets() {
|
||||||
|
when (prgMode) {
|
||||||
|
0 -> {
|
||||||
|
prgOffsets[0] = prgBankOffset(registers[6])
|
||||||
|
prgOffsets[1] = prgBankOffset(registers[7])
|
||||||
|
prgOffsets[2] = prgBankOffset(-2)
|
||||||
|
prgOffsets[3] = prgBankOffset(-1)
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
prgOffsets[0] = prgBankOffset(-2)
|
||||||
|
prgOffsets[1] = prgBankOffset(registers[7])
|
||||||
|
prgOffsets[2] = prgBankOffset(registers[6])
|
||||||
|
prgOffsets[3] = prgBankOffset(-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when (chrMode) {
|
||||||
|
0 -> {
|
||||||
|
chrOffsets[0] = chrBankOffset(registers[0] and 0xFE)
|
||||||
|
chrOffsets[1] = chrBankOffset(registers[0] or 0x01)
|
||||||
|
chrOffsets[2] = chrBankOffset(registers[1] and 0xFE)
|
||||||
|
chrOffsets[3] = chrBankOffset(registers[1] or 0x01)
|
||||||
|
chrOffsets[4] = chrBankOffset(registers[2])
|
||||||
|
chrOffsets[5] = chrBankOffset(registers[3])
|
||||||
|
chrOffsets[6] = chrBankOffset(registers[4])
|
||||||
|
chrOffsets[7] = chrBankOffset(registers[5])
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
chrOffsets[0] = chrBankOffset(registers[2])
|
||||||
|
chrOffsets[1] = chrBankOffset(registers[3])
|
||||||
|
chrOffsets[2] = chrBankOffset(registers[4])
|
||||||
|
chrOffsets[3] = chrBankOffset(registers[5])
|
||||||
|
chrOffsets[4] = chrBankOffset(registers[0] and 0xFE)
|
||||||
|
chrOffsets[5] = chrBankOffset(registers[0] or 0x01)
|
||||||
|
chrOffsets[6] = chrBankOffset(registers[1] and 0xFE)
|
||||||
|
chrOffsets[7] = chrBankOffset(registers[1] or 0x01)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun chrBankOffset(index: Int): Int {
|
||||||
|
var idx = index
|
||||||
|
if (idx >= 0x80) {
|
||||||
|
idx -= 0x100
|
||||||
|
}
|
||||||
|
idx %= cartridge.chr.size / 0x0400
|
||||||
|
var offset = idx * 0x0400
|
||||||
|
if (offset < 0) {
|
||||||
|
offset += cartridge.chr.size
|
||||||
|
}
|
||||||
|
return offset
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prgBankOffset(index: Int): Int {
|
||||||
|
var idx = index
|
||||||
|
if (idx >= 0x80) {
|
||||||
|
idx -= 0x100
|
||||||
|
}
|
||||||
|
idx %= cartridge.chr.size / 0x2000
|
||||||
|
var offset = idx * 0x2000
|
||||||
|
if (offset < 0) {
|
||||||
|
offset += cartridge.chr.size
|
||||||
|
}
|
||||||
|
return offset
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeBankData(value: Int) {
|
||||||
|
registers[register] = value
|
||||||
|
updateOffsets()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeMirror(value: Int) {
|
||||||
|
when (value and 1) {
|
||||||
|
0 -> cartridge.mirror = MirrorVertical
|
||||||
|
1 -> cartridge.mirror = MirrorHorizontal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeProtect() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeIRQLatch(value: Int) {
|
||||||
|
reload = value
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeIRQReload() {
|
||||||
|
counter = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeIRQDisable() {
|
||||||
|
irqEnable = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeIRQEnable() {
|
||||||
|
irqEnable = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun step() {
|
||||||
|
if (ppu.cycle != 280) { // TODO: this *should* be 260
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (ppu.scanLine in 240..260) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!ppu.flagShowBackground && !ppu.flagShowSprites) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handleScanLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleScanLine() {
|
||||||
|
if (counter == 0) {
|
||||||
|
counter = reload
|
||||||
|
} else {
|
||||||
|
counter--
|
||||||
|
if (counter == 0 && irqEnable) {
|
||||||
|
cpu.triggerIRQ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val MirrorHorizontal = 0
|
||||||
|
val MirrorVertical = 1
|
||||||
|
val MirrorSingle0 = 2
|
||||||
|
val MirrorSingle1 = 3
|
||||||
|
val MirrorFour = 4
|
||||||
|
}
|
||||||
|
}
|
19
app/src/main/kotlin/android/emu6502/nes/mappers/Mapper.kt
Normal file
19
app/src/main/kotlin/android/emu6502/nes/mappers/Mapper.kt
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package android.emu6502.nes.mappers
|
||||||
|
|
||||||
|
import android.emu6502.CPU
|
||||||
|
import android.emu6502.nes.Cartridge
|
||||||
|
import android.emu6502.nes.PPU
|
||||||
|
|
||||||
|
interface Mapper {
|
||||||
|
fun read(address: Int): Int
|
||||||
|
fun write(address: Int, value: Int)
|
||||||
|
fun step()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newMapper(cartridge: Cartridge, ppu: PPU, cpu: CPU): Mapper =
|
||||||
|
when (cartridge.mapper.toInt()) {
|
||||||
|
4 -> MMC3(cartridge, ppu, cpu)
|
||||||
|
else -> throw NotImplementedError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
app/src/main/kotlin/android/emu6502/nes/mappers/UNROM.kt
Normal file
13
app/src/main/kotlin/android/emu6502/nes/mappers/UNROM.kt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package android.emu6502.nes.mappers
|
||||||
|
|
||||||
|
class UNROM : Mapper {
|
||||||
|
override fun write(address: Int, value: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun step() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(address: Int): Int {
|
||||||
|
throw NotImplementedError()
|
||||||
|
}
|
||||||
|
}
|
@ -28,4 +28,11 @@ class INESFileParserTest {
|
|||||||
INESFileParser.INES_FILE_MAGIC, 0x10, 0x10, 0x40, 0x0, 0x0, INESFileParser.PADDING))
|
INESFileParser.INES_FILE_MAGIC, 0x10, 0x10, 0x40, 0x0, 0x0, INESFileParser.PADDING))
|
||||||
assertThat(header.isValid()).isTrue()
|
assertThat(header.isValid()).isTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test fun testMapper() {
|
||||||
|
val testRom = javaClass.classLoader.getResource("roms/testrom.nes").toURI()
|
||||||
|
val cartridge = INESFileParser.parseCartridge(File(testRom))
|
||||||
|
// super mario bros 3 is Mapper 4 (MMC3)
|
||||||
|
assertThat(cartridge.mapper).isEqualTo(4)
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user