diff --git a/c64testprgs/joytest.prg b/c64testprgs/joytest.prg new file mode 100644 index 0000000..aa1a273 Binary files /dev/null and b/c64testprgs/joytest.prg differ diff --git a/free-c64-roms/basic b/free-c64-roms/basic new file mode 100644 index 0000000..7828da6 Binary files /dev/null and b/free-c64-roms/basic differ diff --git a/free-c64-roms/chargen b/free-c64-roms/chargen new file mode 100644 index 0000000..11cbd8c Binary files /dev/null and b/free-c64-roms/chargen differ diff --git a/free-c64-roms/kernal b/free-c64-roms/kernal new file mode 100644 index 0000000..0df4af2 Binary files /dev/null and b/free-c64-roms/kernal differ diff --git a/free-c64-roms/readme.txt b/free-c64-roms/readme.txt new file mode 100644 index 0000000..031c2fa --- /dev/null +++ b/free-c64-roms/readme.txt @@ -0,0 +1,24 @@ +Free/open source roms for the C64 + +See https://github.com/MEGA65/open-roms + +The following copyright notices apply to the entirety of this package, +including each source file, unless otherwise noted in each file or directory. + + Copyright Paul Gardner-Stephen, 2019. + Copyright Roman Standzikowski (FeralChil64), 2019-2020. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program. If not, see . + + diff --git a/src/main/kotlin/razorvine/c64emu/Cia.kt b/src/main/kotlin/razorvine/c64emu/Cia.kt index a948315..6eca49d 100644 --- a/src/main/kotlin/razorvine/c64emu/Cia.kt +++ b/src/main/kotlin/razorvine/c64emu/Cia.kt @@ -9,12 +9,20 @@ import java.awt.event.KeyEvent /** * Minimal simulation of the MOS 6526 CIA chip. * Depending on what CIA it is (1 or 2), some registers do different things on the C64. - * This implementation provides a working keyboard matrix, TOD clock, and the essentials of the timer A and B. + * This implementation provides a working keyboard matrix, joystick in port#2 (cia 1), + * time of day clock, and the essentials of the timer A and B. */ class Cia(val number: Int, startAddress: Address, endAddress: Address, val cpu: Cpu6502) : MemMappedComponent(startAddress, endAddress) { private var ramBuffer = Array(endAddress-startAddress+1) { 0 } private var regPRA = 0xff + // joystick in port 2 configuration (only works on cia#1) + private var joy2up = false + private var joy2down = false + private var joy2left = false + private var joy2right = false + private var joy2fire = false + class TimeOfDay { private var updatedAt = 0L private var startedAt = 0L @@ -125,6 +133,11 @@ class Cia(val number: Int, startAddress: Address, endAddress: Address, val cpu: timerAset = 0 timerBactual = 0 timerBset = 0 + joy2up = false + joy2down = false + joy2left = false + joy2right = false + joy2fire = false } override operator fun get(offset: Int): UByte { @@ -139,71 +152,85 @@ class Cia(val number: Int, startAddress: Address, endAddress: Address, val cpu: } val register = offset and 15 - if (number == 1 && register == 0x01) { - // register 1 on CIA#1 is the keyboard data port - // if bit is cleared in PRA, contains keys pressed in that column of the matrix - return when (regPRA) { - 0b00000000 -> { - // check if any keys are pressed at all (by checking all columns at once) - if (hostKeyPresses.isEmpty()) 0xff.toShort() else 0x00.toShort() - } - 0b11111110 -> { - // read column 0 - scanColumn(HostKeyPress(KeyEvent.VK_DOWN), HostKeyPress(KeyEvent.VK_F5), HostKeyPress(KeyEvent.VK_F3), - HostKeyPress(KeyEvent.VK_F1), HostKeyPress(KeyEvent.VK_F7), HostKeyPress(KeyEvent.VK_RIGHT), - HostKeyPress(KeyEvent.VK_ENTER), HostKeyPress(KeyEvent.VK_BACK_SPACE)) - } - 0b11111101 -> { - // read column 1 - scanColumn(HostKeyPress(KeyEvent.VK_SHIFT), // left shift - HostKeyPress(KeyEvent.VK_E), HostKeyPress(KeyEvent.VK_S), HostKeyPress(KeyEvent.VK_Z), - HostKeyPress(KeyEvent.VK_4), HostKeyPress(KeyEvent.VK_A), HostKeyPress(KeyEvent.VK_W), - HostKeyPress(KeyEvent.VK_3)) - } - 0b11111011 -> { - // read column 2 - scanColumn(HostKeyPress(KeyEvent.VK_X), HostKeyPress(KeyEvent.VK_T), HostKeyPress(KeyEvent.VK_F), - HostKeyPress(KeyEvent.VK_C), HostKeyPress(KeyEvent.VK_6), HostKeyPress(KeyEvent.VK_D), - HostKeyPress(KeyEvent.VK_R), HostKeyPress(KeyEvent.VK_5)) - } - 0b11110111 -> { - // read column 3 - scanColumn(HostKeyPress(KeyEvent.VK_V), HostKeyPress(KeyEvent.VK_U), HostKeyPress(KeyEvent.VK_H), - HostKeyPress(KeyEvent.VK_B), HostKeyPress(KeyEvent.VK_8), HostKeyPress(KeyEvent.VK_G), - HostKeyPress(KeyEvent.VK_Y), HostKeyPress(KeyEvent.VK_7)) - } - 0b11101111 -> { - // read column 4 - scanColumn(HostKeyPress(KeyEvent.VK_N), HostKeyPress(KeyEvent.VK_O), HostKeyPress(KeyEvent.VK_K), - HostKeyPress(KeyEvent.VK_M), HostKeyPress(KeyEvent.VK_0), HostKeyPress(KeyEvent.VK_J), - HostKeyPress(KeyEvent.VK_I), HostKeyPress(KeyEvent.VK_9)) - } - 0b11011111 -> { - // read column 5 - scanColumn(HostKeyPress(KeyEvent.VK_COMMA), HostKeyPress(KeyEvent.VK_AT), HostKeyPress(KeyEvent.VK_COLON), - HostKeyPress(KeyEvent.VK_PERIOD), HostKeyPress(KeyEvent.VK_MINUS), HostKeyPress(KeyEvent.VK_L), - HostKeyPress(KeyEvent.VK_P), HostKeyPress(KeyEvent.VK_PLUS)) - } - 0b10111111 -> { - // read column 6 - scanColumn(HostKeyPress(KeyEvent.VK_SLASH), HostKeyPress(KeyEvent.VK_CIRCUMFLEX), HostKeyPress(KeyEvent.VK_EQUALS), - HostKeyPress(KeyEvent.VK_SHIFT, rightSide = true), // right shift - HostKeyPress(KeyEvent.VK_HOME), HostKeyPress(KeyEvent.VK_SEMICOLON), HostKeyPress(KeyEvent.VK_ASTERISK), - HostKeyPress(KeyEvent.VK_DEAD_TILDE) // pound sign - ) - } - 0b01111111 -> { - // read column 7 - scanColumn(HostKeyPress(KeyEvent.VK_ESCAPE), HostKeyPress(KeyEvent.VK_Q), HostKeyPress(KeyEvent.VK_ALT), - HostKeyPress(KeyEvent.VK_SPACE), HostKeyPress(KeyEvent.VK_2), HostKeyPress(KeyEvent.VK_CONTROL), - HostKeyPress(KeyEvent.VK_BACK_QUOTE), HostKeyPress(KeyEvent.VK_1)) - } - else -> { - // invalid column selection - 0xff + + if(number==1) { + // first CIA has keyboard matrix + if(register==0x00) { + // reading $dc00 is joystick in port #2 + return (0b01100000 + or (if(joy2up) 0 else 0b00000001) + or (if(joy2down) 0 else 0b00000010) + or (if(joy2left) 0 else 0b00000100) + or (if(joy2right) 0 else 0b00001000) + or (if(joy2fire) 0 else 0b00010000)).toShort() + } else if(register==0x01) { + // register 1 on CIA#1 is the keyboard data port (and joystick #1...) + // if bit is cleared in PRA, contains keys pressed in that column of the matrix + // NOTE: we do not emulate a joystick in port #1 as this conflicts with the keyboard. + // just use the joystick in port #2 for now... + return when (regPRA) { + 0b00000000 -> { + // check if any keys are pressed at all (by checking all columns at once) + if (hostKeyPresses.isEmpty()) 0xff.toShort() else 0x00.toShort() + } + 0b11111110 -> { + // read column 0 + scanColumn(HostKeyPress(KeyEvent.VK_DOWN), HostKeyPress(KeyEvent.VK_F5), HostKeyPress(KeyEvent.VK_F3), + HostKeyPress(KeyEvent.VK_F1), HostKeyPress(KeyEvent.VK_F7), HostKeyPress(KeyEvent.VK_RIGHT), + HostKeyPress(KeyEvent.VK_ENTER), HostKeyPress(KeyEvent.VK_BACK_SPACE)) + } + 0b11111101 -> { + // read column 1 + scanColumn(HostKeyPress(KeyEvent.VK_SHIFT), // left shift + HostKeyPress(KeyEvent.VK_E), HostKeyPress(KeyEvent.VK_S), HostKeyPress(KeyEvent.VK_Z), + HostKeyPress(KeyEvent.VK_4), HostKeyPress(KeyEvent.VK_A), HostKeyPress(KeyEvent.VK_W), + HostKeyPress(KeyEvent.VK_3)) + } + 0b11111011 -> { + // read column 2 + scanColumn(HostKeyPress(KeyEvent.VK_X), HostKeyPress(KeyEvent.VK_T), HostKeyPress(KeyEvent.VK_F), + HostKeyPress(KeyEvent.VK_C), HostKeyPress(KeyEvent.VK_6), HostKeyPress(KeyEvent.VK_D), + HostKeyPress(KeyEvent.VK_R), HostKeyPress(KeyEvent.VK_5)) + } + 0b11110111 -> { + // read column 3 + scanColumn(HostKeyPress(KeyEvent.VK_V), HostKeyPress(KeyEvent.VK_U), HostKeyPress(KeyEvent.VK_H), + HostKeyPress(KeyEvent.VK_B), HostKeyPress(KeyEvent.VK_8), HostKeyPress(KeyEvent.VK_G), + HostKeyPress(KeyEvent.VK_Y), HostKeyPress(KeyEvent.VK_7)) + } + 0b11101111 -> { + // read column 4 + scanColumn(HostKeyPress(KeyEvent.VK_N), HostKeyPress(KeyEvent.VK_O), HostKeyPress(KeyEvent.VK_K), + HostKeyPress(KeyEvent.VK_M), HostKeyPress(KeyEvent.VK_0), HostKeyPress(KeyEvent.VK_J), + HostKeyPress(KeyEvent.VK_I), HostKeyPress(KeyEvent.VK_9)) + } + 0b11011111 -> { + // read column 5 + scanColumn(HostKeyPress(KeyEvent.VK_COMMA), HostKeyPress(KeyEvent.VK_AT), HostKeyPress(KeyEvent.VK_COLON), + HostKeyPress(KeyEvent.VK_PERIOD), HostKeyPress(KeyEvent.VK_MINUS), HostKeyPress(KeyEvent.VK_L), + HostKeyPress(KeyEvent.VK_P), HostKeyPress(KeyEvent.VK_PLUS)) + } + 0b10111111 -> { + // read column 6 + scanColumn(HostKeyPress(KeyEvent.VK_SLASH), HostKeyPress(KeyEvent.VK_CIRCUMFLEX), HostKeyPress(KeyEvent.VK_EQUALS), + HostKeyPress(KeyEvent.VK_SHIFT, rightSide = true), // right shift + HostKeyPress(KeyEvent.VK_HOME), HostKeyPress(KeyEvent.VK_SEMICOLON), HostKeyPress(KeyEvent.VK_ASTERISK), + HostKeyPress(KeyEvent.VK_DEAD_TILDE) // pound sign + ) + } + 0b01111111 -> { + // read column 7 + scanColumn(HostKeyPress(KeyEvent.VK_ESCAPE), HostKeyPress(KeyEvent.VK_Q), HostKeyPress(KeyEvent.VK_ALT), + HostKeyPress(KeyEvent.VK_SPACE), HostKeyPress(KeyEvent.VK_2), HostKeyPress(KeyEvent.VK_CONTROL), + HostKeyPress(KeyEvent.VK_BACK_QUOTE), HostKeyPress(KeyEvent.VK_1)) + } + else -> { + // invalid column selection + 0xff + } } } - } else ramBuffer[register] + } return when (register) { 0x04 -> (timerAactual and 0xff).toShort() @@ -224,19 +251,6 @@ class Cia(val number: Int, startAddress: Address, endAddress: Address, val cpu: } } - private fun toBCD(data: Int): UByte { - val tens = data/10 - val ones = data-tens*10 - return ((tens shl 4) or ones).toShort() - } - - private fun fromBCD(bcd: UByte): Int { - val ibcd = bcd.toInt() - val tens = ibcd ushr 4 - val ones = ibcd and 0x0f - return tens*10+ones - } - override operator fun set(offset: Int, data: UByte) { val register = offset and 15 if (number == 1 && register == 0x00) { @@ -296,6 +310,19 @@ class Cia(val number: Int, startAddress: Address, endAddress: Address, val cpu: } } + private fun toBCD(data: Int): UByte { + val tens = data/10 + val ones = data-tens*10 + return ((tens shl 4) or ones).toShort() + } + + private fun fromBCD(bcd: UByte): Int { + val ibcd = bcd.toInt() + val tens = ibcd ushr 4 + val ones = ibcd and 0x0f + return tens*10+ones + } + fun hostKeyPressed(event: KeyEvent) { val rightSide = event.keyLocation == KeyEvent.KEY_LOCATION_RIGHT val numpad = event.keyLocation == KeyEvent.KEY_LOCATION_NUMPAD @@ -374,4 +401,15 @@ class Cia(val number: Int, startAddress: Address, endAddress: Address, val cpu: } } } + + fun setJoystick2(up: Boolean, down: Boolean, left: Boolean, right: Boolean, fire: Boolean) { + if(number!=1) + throw NotImplementedError("joystick port 2 is connected to cia#1") + + joy2up = up + joy2down = down + joy2left = left + joy2right = right + joy2fire = fire + } } diff --git a/src/main/kotlin/razorvine/c64emu/GUI.kt b/src/main/kotlin/razorvine/c64emu/GUI.kt index f43583b..cfaccdf 100644 --- a/src/main/kotlin/razorvine/c64emu/GUI.kt +++ b/src/main/kotlin/razorvine/c64emu/GUI.kt @@ -82,16 +82,50 @@ class MainC64Window(title: String, chargen: Rom, val ram: MemoryComponent, val c // keyboard events: override fun keyTyped(event: KeyEvent) {} + private var joy2up = false + private var joy2down = false + private var joy2left = false + private var joy2right = false + private var joy2fire = false + override fun keyPressed(event: KeyEvent) { // '\' is mapped as RESTORE, this causes a NMI on the cpu if (event.keyChar == '\\') { cpu.nmiAsserted = true } else { - keypressCia.hostKeyPressed(event) + if(event.keyLocation==KeyEvent.KEY_LOCATION_NUMPAD) { + // numpad is joystick #2 + if (event.keyChar in "789") joy2up = true + if (event.keyChar in "123") joy2down = true + if (event.keyChar in "741") joy2left = true + if (event.keyChar in "963") joy2right = true + if (event.keyChar in "05\n") joy2fire = true + keypressCia.setJoystick2(joy2up, joy2down, joy2left, joy2right, joy2fire) + } else { + keypressCia.hostKeyPressed(event) + } } } override fun keyReleased(event: KeyEvent) { - keypressCia.hostKeyPressed(event) + if(event.keyLocation==KeyEvent.KEY_LOCATION_NUMPAD) { + // numpad is joystick #2 + if (event.keyChar in "789") joy2up = false + if (event.keyChar in "123") joy2down = false + if (event.keyChar in "741") joy2left = false + if (event.keyChar in "963") joy2right = false + if (event.keyChar in "05\n") joy2fire = false + keypressCia.setJoystick2(joy2up, joy2down, joy2left, joy2right, joy2fire) + } else { + keypressCia.hostKeyPressed(event) + } + } + + fun reset() { + joy2up = false + joy2down = false + joy2left = false + joy2right = false + joy2fire = false } } diff --git a/src/main/kotlin/razorvine/c64emu/c64Main.kt b/src/main/kotlin/razorvine/c64emu/c64Main.kt index 8dbb402..f1f78cc 100644 --- a/src/main/kotlin/razorvine/c64emu/c64Main.kt +++ b/src/main/kotlin/razorvine/c64emu/c64Main.kt @@ -216,6 +216,11 @@ class C64Machine(title: String) : IVirtualMachine { while (cpu.instrCycles > 0) bus.clock() } + override fun reset() { + bus.reset() + hostDisplay.reset() + } + override fun executeMonitorCommand(command: String) = monitor.command(command) fun start() { diff --git a/src/main/kotlin/razorvine/examplemachines/DebugWindow.kt b/src/main/kotlin/razorvine/examplemachines/DebugWindow.kt index c50f293..43ab6d1 100644 --- a/src/main/kotlin/razorvine/examplemachines/DebugWindow.kt +++ b/src/main/kotlin/razorvine/examplemachines/DebugWindow.kt @@ -182,6 +182,7 @@ class DebugWindow(private val vm: IVirtualMachine) : JFrame("Debugger - ksim65 v } "reset" -> { + vm.reset() vm.bus.reset() updateCpu(vm.cpu, vm.bus) } diff --git a/src/main/kotlin/razorvine/examplemachines/GUI.kt b/src/main/kotlin/razorvine/examplemachines/GUI.kt index f845107..9c07468 100644 --- a/src/main/kotlin/razorvine/examplemachines/GUI.kt +++ b/src/main/kotlin/razorvine/examplemachines/GUI.kt @@ -242,4 +242,8 @@ class MainWindow(title: String) : JFrame(title), KeyListener, MouseInputListener else keyboardBuffer.pop() } + fun reset() { + // when the reset button is pressed + } + } diff --git a/src/main/kotlin/razorvine/examplemachines/machineMain.kt b/src/main/kotlin/razorvine/examplemachines/machineMain.kt index 84de2cc..a4f204e 100644 --- a/src/main/kotlin/razorvine/examplemachines/machineMain.kt +++ b/src/main/kotlin/razorvine/examplemachines/machineMain.kt @@ -65,6 +65,11 @@ class VirtualMachine(title: String) : IVirtualMachine { while (cpu.instrCycles > 0) bus.clock() } + override fun reset() { + bus.reset() + hostDisplay.reset() + } + override fun executeMonitorCommand(command: String) = monitor.command(command) fun start() { diff --git a/src/main/kotlin/razorvine/ksim65/IVirtualMachine.kt b/src/main/kotlin/razorvine/ksim65/IVirtualMachine.kt index 10177c0..4f71347 100644 --- a/src/main/kotlin/razorvine/ksim65/IVirtualMachine.kt +++ b/src/main/kotlin/razorvine/ksim65/IVirtualMachine.kt @@ -7,6 +7,7 @@ import java.io.File interface IVirtualMachine { fun step() fun pause(paused: Boolean) + fun reset() fun getZeroAndStackPages(): Array fun loadFileInRam(file: File, loadAddress: Address?) diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties index d01ef93..af5f173 100644 --- a/src/main/resources/version.properties +++ b/src/main/resources/version.properties @@ -1 +1 @@ -version=1.8 +version=1.9-dev