Remove NES code
This commit is contained in:
parent
143bd655c6
commit
b928d84c49
|
@ -26,9 +26,8 @@ dependencies {
|
||||||
implementation 'com.google.android.material:material:1.0.0-alpha1'
|
implementation 'com.google.android.material:material:1.0.0-alpha1'
|
||||||
implementation 'androidx.appcompat:appcompat:1.0.0-alpha1'
|
implementation 'androidx.appcompat:appcompat:1.0.0-alpha1'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0-alpha1'
|
implementation 'androidx.cardview:cardview:1.0.0-alpha1'
|
||||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.2.60'
|
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.2.61'
|
||||||
implementation "org.jetbrains.kotlin:kotlin-reflect:1.2.60"
|
implementation "org.jetbrains.kotlin:kotlin-reflect:1.2.61"
|
||||||
implementation 'com.facebook.stetho:stetho:1.5.0'
|
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
testImplementation 'com.google.truth:truth:0.42'
|
testImplementation 'com.google.truth:truth:0.42'
|
||||||
|
|
|
@ -1,14 +1,7 @@
|
||||||
package android.emu6502
|
package android.emu6502
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import com.facebook.stetho.Stetho
|
|
||||||
|
|
||||||
class Emu6502Application : Application() {
|
class Emu6502Application : Application() {
|
||||||
override fun onCreate() {
|
|
||||||
super.onCreate()
|
|
||||||
Stetho.initialize(Stetho.newInitializerBuilder(this)
|
|
||||||
.enableWebKitInspector(Stetho.defaultInspectorModulesProvider(this))
|
|
||||||
.build())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
package android.emu6502.nes
|
|
||||||
|
|
||||||
class APU {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
package android.emu6502.nes
|
|
||||||
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
data class Cartridge(
|
|
||||||
// @formatter:off
|
|
||||||
val pgr: IntArray, // PRG-ROM banks
|
|
||||||
val chr: IntArray, // CHR-ROM banks
|
|
||||||
val mapper: Byte, // mapper type
|
|
||||||
var mirror: Int, // mirroring mode
|
|
||||||
val battery: Byte, // battery present
|
|
||||||
val sram: IntArray = IntArray(0x2000) // Save RAM
|
|
||||||
// @formatter:on
|
|
||||||
) {
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (other?.javaClass != javaClass) return false
|
|
||||||
|
|
||||||
other as Cartridge
|
|
||||||
|
|
||||||
if (!Arrays.equals(pgr, other.pgr)) return false
|
|
||||||
if (!Arrays.equals(chr, other.chr)) return false
|
|
||||||
if (!Arrays.equals(sram, other.sram)) return false
|
|
||||||
if (mapper != other.mapper) return false
|
|
||||||
if (mirror != other.mirror) return false
|
|
||||||
if (battery != other.battery) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = Arrays.hashCode(pgr)
|
|
||||||
result = 31 * result + Arrays.hashCode(chr)
|
|
||||||
result = 31 * result + Arrays.hashCode(sram)
|
|
||||||
result = 31 * result + mapper
|
|
||||||
result = 31 * result + mirror
|
|
||||||
result = 31 * result + battery
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
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)
|
|
||||||
) {
|
|
||||||
fun step() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
package android.emu6502.nes
|
|
||||||
|
|
||||||
class Controller {
|
|
||||||
var buttons: Array<Boolean> = Array(8) { false }
|
|
||||||
var index: Byte = 0
|
|
||||||
var strobe: Byte = 0
|
|
||||||
|
|
||||||
fun read() {
|
|
||||||
}
|
|
||||||
|
|
||||||
fun write() {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
package android.emu6502.nes
|
|
||||||
|
|
||||||
import android.emu6502.nes.INESFileParser.Companion.INES_FILE_MAGIC
|
|
||||||
import android.emu6502.nes.INESFileParser.Companion.PADDING
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
// http://wiki.nesdev.com/w/index.php/INES
|
|
||||||
// Sample header:
|
|
||||||
// 4e 45 53 1a 10 10 40 00 00 00 00 00 00 00 00 00
|
|
||||||
data class INESFileHeader(
|
|
||||||
val magic: ByteArray, // Constant $4E $45 $53 $1A ("NES" followed by MS-DOS end-of-file)
|
|
||||||
val numPRG: Byte, // Size of PRG ROM in 16 KB units
|
|
||||||
val numCHR: Byte, // Size of CHR ROM in 8 KB units (Value 0 means the board uses CHR RAM)
|
|
||||||
val control1: Byte, // Flags 6
|
|
||||||
val control2: Byte, // Flags 7
|
|
||||||
val numRAM: Byte, // Size of PRG RAM in 8 KB units
|
|
||||||
val padding: ByteArray // 7 bytes, unused
|
|
||||||
) {
|
|
||||||
fun isValid() =
|
|
||||||
Arrays.equals(magic, INES_FILE_MAGIC) && Arrays.equals(padding, PADDING)
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (this === other) return true
|
|
||||||
if (other?.javaClass != javaClass) return false
|
|
||||||
|
|
||||||
other as INESFileHeader
|
|
||||||
|
|
||||||
if (!Arrays.equals(magic, other.magic)) return false
|
|
||||||
if (numPRG != other.numPRG) return false
|
|
||||||
if (numCHR != other.numCHR) return false
|
|
||||||
if (control1 != other.control1) return false
|
|
||||||
if (control2 != other.control2) return false
|
|
||||||
if (numRAM != other.numRAM) return false
|
|
||||||
if (!Arrays.equals(padding, other.padding)) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
var result = Arrays.hashCode(magic)
|
|
||||||
result = 31 * result + numPRG
|
|
||||||
result = 31 * result + numCHR
|
|
||||||
result = 31 * result + control1
|
|
||||||
result = 31 * result + control2
|
|
||||||
result = 31 * result + numRAM
|
|
||||||
result = 31 * result + Arrays.hashCode(padding)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
package android.emu6502.nes
|
|
||||||
|
|
||||||
import java.io.DataInputStream
|
|
||||||
import java.io.File
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
internal class INESFileParser {
|
|
||||||
companion object {
|
|
||||||
internal val INES_FILE_MAGIC = byteArrayOf(0x4e, 0x45, 0x53, 0x1a)/*0x4e45531a*/
|
|
||||||
internal val PADDING = byteArrayOf(0, 0, 0, 0, 0, 0, 0)
|
|
||||||
|
|
||||||
internal fun parseFileHeader(stream: InputStream): INESFileHeader {
|
|
||||||
val dataStream = DataInputStream(stream.buffered())
|
|
||||||
return INESFileHeader(
|
|
||||||
(0..3).map { dataStream.readByte() }.toByteArray(),
|
|
||||||
dataStream.readByte(),
|
|
||||||
dataStream.readByte(),
|
|
||||||
dataStream.readByte(),
|
|
||||||
dataStream.readByte(),
|
|
||||||
dataStream.readByte(),
|
|
||||||
(0..6).map { dataStream.readByte() }.toByteArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun parseCartridge(file: File): Cartridge =
|
|
||||||
file.inputStream().use {
|
|
||||||
val inesFileHeader = parseFileHeader(it)
|
|
||||||
// mapper type
|
|
||||||
val control1 = inesFileHeader.control1.toInt()
|
|
||||||
val mapper1 = control1 shr 4
|
|
||||||
val mapper2 = inesFileHeader.control2.toInt() shr 4
|
|
||||||
val mapper = mapper1 or (mapper2 shl 4)
|
|
||||||
// mirroring type
|
|
||||||
val mirror1 = control1 and 1
|
|
||||||
val mirror2 = (control1 shr 3) and 1
|
|
||||||
val mirror = mirror1 or (mirror2 shl 1)
|
|
||||||
// battery-backed RAM
|
|
||||||
val battery = (control1 shr 1).and(1).toByte()
|
|
||||||
// read prg-rom bank(s)
|
|
||||||
val prg = ByteArray(inesFileHeader.numPRG.toInt() * 16384)
|
|
||||||
it.read(prg)
|
|
||||||
// read chr-rom bank(s)
|
|
||||||
val chr = ByteArray(inesFileHeader.numCHR.toInt() * 8192)
|
|
||||||
it.read(chr)
|
|
||||||
return Cartridge(
|
|
||||||
prg.map(Byte::toInt).toIntArray(),
|
|
||||||
chr.map(Byte::toInt).toIntArray(),
|
|
||||||
mapper.toByte(), mirror, battery
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,211 +0,0 @@
|
||||||
package android.emu6502.nes
|
|
||||||
|
|
||||||
import android.emu6502.shl
|
|
||||||
import android.emu6502.shr
|
|
||||||
import android.media.Image
|
|
||||||
import android.system.Os.read
|
|
||||||
import kotlin.experimental.and
|
|
||||||
import kotlin.experimental.xor
|
|
||||||
|
|
||||||
class PPU(
|
|
||||||
val console: Console,
|
|
||||||
// @formatter:off
|
|
||||||
|
|
||||||
var cycle: Int = 0, // 0-340
|
|
||||||
var scanLine: Int = 0, // 0-261, 0-239=visible, 240=post, 241-260=vblank, 261=pre
|
|
||||||
var frame: Int = 0, // frame counter
|
|
||||||
|
|
||||||
// storage variables
|
|
||||||
var paletteData: ByteArray = ByteArray(32),
|
|
||||||
var nameTableData: ByteArray = ByteArray(2048),
|
|
||||||
var oamData: ByteArray = ByteArray(256),
|
|
||||||
var front: Image,
|
|
||||||
var back: Image,
|
|
||||||
|
|
||||||
// PPU registers
|
|
||||||
var v: Int = 0, // current vram address (15 bit)
|
|
||||||
var t: Int = 0, // temporary vram address (15 bit)
|
|
||||||
var x: Byte = 0, // fine x scroll (3 bit)
|
|
||||||
var w: Byte = 0, // write toggle (1 bit)
|
|
||||||
var f: Byte = 0, // even/odd frame flag (1 bit)
|
|
||||||
var register: Byte = 0,
|
|
||||||
|
|
||||||
var nmiOccurred: Boolean = false,
|
|
||||||
var nmiOutput: Boolean = false,
|
|
||||||
var nmiPrevious: Boolean = false,
|
|
||||||
var nmiDelay: Byte = 0,
|
|
||||||
|
|
||||||
// background temporary variables
|
|
||||||
var nameTableByte: Byte = 0,
|
|
||||||
var attributeTableByte: Byte = 0,
|
|
||||||
var lowTileByte: Byte = 0,
|
|
||||||
var highTileByte: Byte = 0,
|
|
||||||
var tileData: Int = 0,
|
|
||||||
|
|
||||||
// $2000 PPUCTRL
|
|
||||||
var flagNameTable: Byte = 0, // 0: $2000; 1: $2400; 2: $2800; 3: $2C00
|
|
||||||
var flagIncrement: Byte = 0, // 0: add 1; 1: add 32
|
|
||||||
var flagSpriteTable: Byte = 0, // 0: $0000; 1: $1000; ignored in 8x16 mode
|
|
||||||
var flagBackgroundTable: Byte = 0, // 0: $0000; 1: $1000
|
|
||||||
var flagSpriteSize: Byte = 0, // 0: 8x8; 1: 8x16
|
|
||||||
var flagMasterSlave: Byte = 0, // 0: read EXT; 1: write EXT
|
|
||||||
|
|
||||||
// $2001 PPUMASK
|
|
||||||
var flagGrayscale: Byte = 0, // 0: color; 1: grayscale
|
|
||||||
var flagShowLeftBackground: Byte = 0, // 0: hide; 1: show
|
|
||||||
var flagShowLeftSprites: Byte = 0, // 0: hide; 1: show
|
|
||||||
var flagShowBackground: Byte = 0, // 0: hide; 1: show
|
|
||||||
var flagShowSprites: Byte = 0, // 0: hide; 1: show
|
|
||||||
var flagRedTint: Byte = 0, // 0: normal; 1: emphasized
|
|
||||||
var flagGreenTint: Byte = 0, // 0: normal; 1: emphasized
|
|
||||||
var flagBlueTint: Byte = 0, // 0: normal; 1: emphasized
|
|
||||||
|
|
||||||
// $2002 PPUSTATUS
|
|
||||||
val flagSpriteZeroHit: Boolean = false,
|
|
||||||
val flagSpriteOverflow: Boolean = false,
|
|
||||||
|
|
||||||
// $2003 OAMADDR
|
|
||||||
var oamAddress: Byte = 0,
|
|
||||||
|
|
||||||
// $2007 PPUDATA
|
|
||||||
val bufferedData: Byte = 0 // for buffered reads
|
|
||||||
|
|
||||||
// @formatter:on
|
|
||||||
) {
|
|
||||||
fun writeRegister(address: Int, value: Byte) {
|
|
||||||
register = value
|
|
||||||
when (address) {
|
|
||||||
0x2000 -> writeControl(value)
|
|
||||||
0x2001 -> writeMask(value)
|
|
||||||
0x2003 -> writeOAMAddress(value)
|
|
||||||
0x2004 -> writeOAMData(value)
|
|
||||||
0x2005 -> writeScroll(value)
|
|
||||||
0x2006 -> writeAddress(value)
|
|
||||||
0x2007 -> writeData(value)
|
|
||||||
0x4014 -> writeDMA(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeDMA(value: Byte) {
|
|
||||||
TODO()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun step() {
|
|
||||||
tick()
|
|
||||||
val renderingEnabled = flagShowBackground != 0.toByte() || flagShowSprites != 0.toByte()
|
|
||||||
val preLine = scanLine == 261
|
|
||||||
val visibleLine = scanLine < 240
|
|
||||||
// postLine = scanLine == 240
|
|
||||||
val renderLine = preLine || visibleLine
|
|
||||||
val preFetchCycle = cycle in 321..336
|
|
||||||
val visibleCycle = cycle in 1..256
|
|
||||||
val fetchCycle = preFetchCycle || visibleCycle
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun tick() {
|
|
||||||
if (nmiDelay > 0) {
|
|
||||||
nmiDelay--
|
|
||||||
if (nmiDelay == 0.toByte() && nmiOutput && nmiOccurred) {
|
|
||||||
console.cpu.triggerNMI()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flagShowBackground != 0.toByte() || flagShowSprites != 0.toByte()) {
|
|
||||||
if (f == 1.toByte() && scanLine == 261 && cycle == 339) {
|
|
||||||
cycle = 0
|
|
||||||
scanLine = 0
|
|
||||||
frame++
|
|
||||||
f = f xor 1
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cycle++
|
|
||||||
if (cycle > 340) {
|
|
||||||
cycle = 0
|
|
||||||
scanLine++
|
|
||||||
if (scanLine > 261) {
|
|
||||||
scanLine = 0
|
|
||||||
frame++
|
|
||||||
f = f xor 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeData(value: Byte) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// $2006: PPUADDR
|
|
||||||
private fun writeAddress(value: Byte) {
|
|
||||||
if (w == 0.toByte()) {
|
|
||||||
// t: ..FEDCBA ........ = d: ..FEDCBA
|
|
||||||
// t: .X...... ........ = 0
|
|
||||||
// w: = 1
|
|
||||||
t = (t and 0x80FF) or ((value and 0x3F) shl 8).toInt()
|
|
||||||
w = 1
|
|
||||||
} else {
|
|
||||||
// t: ........ HGFEDCBA = d: HGFEDCBA
|
|
||||||
// v = t
|
|
||||||
// w: = 0
|
|
||||||
t = (t and 0xFF00) or value.toInt()
|
|
||||||
v = t
|
|
||||||
w = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// $2005: PPUSCROLL
|
|
||||||
private fun writeScroll(value: Byte) {
|
|
||||||
TODO("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// $2004: OAMDATA (write)
|
|
||||||
private fun writeOAMData(value: Byte) {
|
|
||||||
oamData[oamAddress.toInt()] = value
|
|
||||||
oamAddress++
|
|
||||||
}
|
|
||||||
|
|
||||||
// $2003: OAMADDR
|
|
||||||
private fun writeOAMAddress(value: Byte) {
|
|
||||||
oamAddress = value
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeMask(value: Byte) {
|
|
||||||
flagGrayscale = (value shr 0) and 1
|
|
||||||
flagShowLeftBackground = (value shr 1) and 1
|
|
||||||
flagShowLeftSprites = (value shr 2) and 1
|
|
||||||
flagShowBackground = (value shr 3) and 1
|
|
||||||
flagShowSprites = (value shr 4) and 1
|
|
||||||
flagRedTint = (value shr 5) and 1
|
|
||||||
flagGreenTint = (value shr 6) and 1
|
|
||||||
flagBlueTint = (value shr 7) and 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// $2000: PPUCTRL
|
|
||||||
private fun writeControl(value: Byte) {
|
|
||||||
flagNameTable = (value shr 0) and 3
|
|
||||||
flagIncrement = (value shr 2) and 1
|
|
||||||
flagSpriteTable = (value shr 3) and 1
|
|
||||||
flagBackgroundTable = (value shr 4) and 1
|
|
||||||
flagSpriteSize = (value shr 5) and 1
|
|
||||||
flagMasterSlave = (value shr 6) and 1
|
|
||||||
nmiOutput = (value shr 7) and 1 == 1.toByte()
|
|
||||||
nmiChange()
|
|
||||||
// t: ....BA.. ........ = d: ......BA
|
|
||||||
t = (t and 0xF3FF) or ((value and 0x03) shl 10).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun nmiChange() {
|
|
||||||
val nmi = nmiOutput && nmiOccurred
|
|
||||||
if (nmi && !nmiPrevious) {
|
|
||||||
// TODO: this fixes some games but the delay shouldn't have to be so
|
|
||||||
// long, so the timings are off somewhere
|
|
||||||
nmiDelay = 15
|
|
||||||
}
|
|
||||||
nmiPrevious = nmi
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fetchNameTableByte() {
|
|
||||||
val address = 0x2000 or (v and 0x0FFF)
|
|
||||||
nameTableByte = read(address)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,214 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
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("Mapper ${cartridge.mapper.toInt()} not implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,8 +6,8 @@ buildscript {
|
||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.1.4'
|
classpath 'com.android.tools.build:gradle:3.3.0-alpha08'
|
||||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.60'
|
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.61'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
|
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip
|
||||||
|
|
Loading…
Reference in New Issue