214 lines
5.6 KiB
Kotlin
214 lines
5.6 KiB
Kotlin
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 {
|
|
return when {
|
|
address < 0x2000 -> {
|
|
val bank = address / 0x0400
|
|
val offset = address % 0x0400
|
|
cartridge.chr[chrOffsets[bank] + offset]
|
|
}
|
|
address >= 0x8000 -> {
|
|
val addr = address - 0x8000
|
|
val bank = addr / 0x2000
|
|
val offset = addr % 0x2000
|
|
cartridge.pgr[prgOffsets[bank] + offset]
|
|
}
|
|
address >= 0x6000 -> {
|
|
return cartridge.sram[address - 0x6000]
|
|
}
|
|
else -> throw RuntimeException("unhandled mapper4 read at address: ${address.toHexString()}")
|
|
}
|
|
}
|
|
|
|
override fun write(address: Int, value: Int) {
|
|
when {
|
|
address < 0x2000 -> {
|
|
val bank = address / 0x0400
|
|
val offset = address % 0x0400
|
|
cartridge.chr[chrOffsets[bank] + offset] = value
|
|
}
|
|
address >= 0x8000 -> {
|
|
writeRegister(address, value)
|
|
}
|
|
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
|
|
}
|
|
} |