badline 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +40 -0
  4. data/exe/badline +55 -0
  5. data/lib/badline/address_bus.rb +206 -0
  6. data/lib/badline/addressable.rb +61 -0
  7. data/lib/badline/cartridge/magic_desk.rb +23 -0
  8. data/lib/badline/cartridge/ocean.rb +33 -0
  9. data/lib/badline/cartridge/standard.rb +31 -0
  10. data/lib/badline/cartridge.rb +75 -0
  11. data/lib/badline/chrout_trap.rb +39 -0
  12. data/lib/badline/cia/timer.rb +122 -0
  13. data/lib/badline/cia.rb +189 -0
  14. data/lib/badline/color_memory.rb +16 -0
  15. data/lib/badline/computer.rb +100 -0
  16. data/lib/badline/control_ports.rb +24 -0
  17. data/lib/badline/cpu.rb +239 -0
  18. data/lib/badline/cycleable.rb +35 -0
  19. data/lib/badline/gui/application.rb +94 -0
  20. data/lib/badline/gui/joy_map.rb +19 -0
  21. data/lib/badline/gui/key_map.rb +34 -0
  22. data/lib/badline/gui/palette.rb +35 -0
  23. data/lib/badline/gui/pane.rb +35 -0
  24. data/lib/badline/gui/screen_pane.rb +50 -0
  25. data/lib/badline/gui/window.rb +46 -0
  26. data/lib/badline/gui.rb +11 -0
  27. data/lib/badline/instruction.rb +334 -0
  28. data/lib/badline/instruction_set/arithmetic.rb +119 -0
  29. data/lib/badline/instruction_set/bitwise.rb +131 -0
  30. data/lib/badline/instruction_set/branch.rb +78 -0
  31. data/lib/badline/instruction_set/flag.rb +63 -0
  32. data/lib/badline/instruction_set/illegal.rb +278 -0
  33. data/lib/badline/instruction_set/inc_dec.rb +71 -0
  34. data/lib/badline/instruction_set/stack.rb +104 -0
  35. data/lib/badline/instruction_set/transfer.rb +137 -0
  36. data/lib/badline/instruction_set.rb +77 -0
  37. data/lib/badline/integer_helper.rb +39 -0
  38. data/lib/badline/joystick.rb +25 -0
  39. data/lib/badline/kernal_trap/file.rb +54 -0
  40. data/lib/badline/kernal_trap/load.rb +63 -0
  41. data/lib/badline/kernal_trap/save.rb +42 -0
  42. data/lib/badline/kernal_trap.rb +5 -0
  43. data/lib/badline/keyboard.rb +58 -0
  44. data/lib/badline/keyboard_buffer.rb +33 -0
  45. data/lib/badline/media.rb +59 -0
  46. data/lib/badline/memory.rb +43 -0
  47. data/lib/badline/rom.rb +23 -0
  48. data/lib/badline/roms/README +18 -0
  49. data/lib/badline/roms/basic.rom +0 -0
  50. data/lib/badline/roms/character.rom +0 -0
  51. data/lib/badline/roms/kernal.rom +0 -0
  52. data/lib/badline/sid.rb +25 -0
  53. data/lib/badline/status.rb +56 -0
  54. data/lib/badline/storage/crt_file.rb +53 -0
  55. data/lib/badline/storage/d64_image.rb +21 -0
  56. data/lib/badline/storage/d71_image.rb +13 -0
  57. data/lib/badline/storage/d81_image.rb +14 -0
  58. data/lib/badline/storage/disk_image.rb +71 -0
  59. data/lib/badline/storage/host_directory.rb +49 -0
  60. data/lib/badline/storage/p00.rb +24 -0
  61. data/lib/badline/storage.rb +28 -0
  62. data/lib/badline/time_of_day.rb +101 -0
  63. data/lib/badline/traps.rb +15 -0
  64. data/lib/badline/version.rb +5 -0
  65. data/lib/badline/vic/bank.rb +65 -0
  66. data/lib/badline/vic/display_state.rb +78 -0
  67. data/lib/badline/vic/graphics_mode.rb +139 -0
  68. data/lib/badline/vic/registers.rb +170 -0
  69. data/lib/badline/vic/sequencer.rb +237 -0
  70. data/lib/badline/vic/sprite.rb +121 -0
  71. data/lib/badline/vic/sprites.rb +112 -0
  72. data/lib/badline/vic.rb +192 -0
  73. data/lib/badline.rb +29 -0
  74. metadata +131 -0
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module InstructionSet
5
+ module IncDec
6
+ # Decrements memory location by one.
7
+ #
8
+ # Opcodes:
9
+ # $C6 - zeropage - 5 cycles
10
+ # $CE - absolute - 6 cycles
11
+ # $D6 - zeropage_x - 6 cycles
12
+ # $DE - absolute_x - 7 cycles
13
+ def dec(addr, value)
14
+ v = resolve(value)
15
+ result = (v - 1) & 0xff
16
+ write_modified(addr, v, result)
17
+ update_number_flags(result)
18
+ end
19
+
20
+ # Decrements X register by one.
21
+ #
22
+ # Opcodes:
23
+ # $CA - implied - 2 cycles
24
+ def dex(_addr, _value)
25
+ cycle { @x = (@x - 1) & 0xff }
26
+ update_number_flags(@x)
27
+ end
28
+
29
+ # Decrements Y register by one.
30
+ #
31
+ # Opcodes:
32
+ # $88 - implied - 2 cycles
33
+ def dey(_addr, _value)
34
+ cycle { @y = (@y - 1) & 0xff }
35
+ update_number_flags(@y)
36
+ end
37
+
38
+ # Increments memory location by one.
39
+ #
40
+ # Opcodes:
41
+ # $E6 - zeropage - 5 cycles
42
+ # $EE - absolute - 6 cycles
43
+ # $F6 - zeropage_x - 6 cycles
44
+ # $FE - absolute_x - 7 cycles
45
+ def inc(addr, value)
46
+ v = resolve(value)
47
+ result = (v + 1) & 0xff
48
+ write_modified(addr, v, result)
49
+ update_number_flags(result)
50
+ end
51
+
52
+ # Increments X register by one.
53
+ #
54
+ # Opcodes:
55
+ # $E8 - implied - 2 cycles
56
+ def inx(_addr, _value)
57
+ cycle { @x = (@x + 1) & 0xff }
58
+ update_number_flags(@x)
59
+ end
60
+
61
+ # Increments Y register by one.
62
+ #
63
+ # Opcodes:
64
+ # $C8 - implied - 2 cycles
65
+ def iny(_addr, _value)
66
+ cycle { @y = (@y + 1) & 0xff }
67
+ update_number_flags(@y)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module InstructionSet
5
+ module Stack
6
+ # Push accumulator onto the stack.
7
+ #
8
+ # Opcodes:
9
+ # $48 - implied - 3 cycles
10
+ def pha(_addr, _value)
11
+ stack_push(@a)
12
+ end
13
+
14
+ # Push processor status onto the stack.
15
+ #
16
+ # Opcodes:
17
+ # $08 - implied - 3 cycles
18
+ def php(_addr, _value)
19
+ stack_push(p | 0b00010000)
20
+ end
21
+
22
+ # Pull accumulator from stack.
23
+ #
24
+ # Opcodes:
25
+ # $68 - implied - 4 cycles
26
+ def pla(_addr, _value)
27
+ cycle { @a = stack_pull }
28
+ update_number_flags(@a)
29
+ end
30
+
31
+ # Pull processor status from stack.
32
+ #
33
+ # Opcodes:
34
+ # $28 - implied - 4 cycles
35
+ def plp(_addr, _value)
36
+ cycle { status.value = stack_pull & 0b11101111 }
37
+ end
38
+
39
+ # Jump to absolute address.
40
+ #
41
+ # Opcodes:
42
+ # $4C - absolute - 3 cycles
43
+ def jmp(addr, _value)
44
+ @program_counter = addr
45
+ end
46
+
47
+ # Jump to subroutine. The operand high byte is fetched only after the
48
+ # return address has been pushed, so a JSR executing inside the stack
49
+ # jumps via the value its own push just wrote.
50
+ #
51
+ # Opcodes:
52
+ # $20 - absolute - 6 cycles
53
+ def jsr(addr, _value)
54
+ return_addr = (program_counter - 1) & 0xffff
55
+ cycle { @memory.peek(stack_address) } # internal cycle: dummy stack read
56
+ write_byte(stack_address, high_byte(return_addr))
57
+ write_byte(stack_address(-1), low_byte(return_addr))
58
+ @stack_pointer = (@stack_pointer - 2) & 0xff
59
+ @program_counter = uint16(addr, read_byte(return_addr))
60
+ end
61
+
62
+ # Return from interrupt.
63
+ #
64
+ # Opcodes:
65
+ # $40 - implied - 6 cycles
66
+ def rti(_addr, _value)
67
+ cycle do
68
+ @stack_pointer = (@stack_pointer + 1) & 0xff
69
+ @status.value = memory[stack_address]
70
+ @status.break = false
71
+ end
72
+ @program_counter = uint16(stack_pull, stack_pull)
73
+ end
74
+
75
+ # Return from subroutine.
76
+ #
77
+ # Opcodes:
78
+ # $60 - implied - 6 cycles
79
+ def rts(_addr, _value)
80
+ cycle do
81
+ @program_counter = (uint16(stack_pull, stack_pull) + 1) & 0xffff
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def stack_pull
88
+ cycle { @stack_pointer = (@stack_pointer + 1) & 0xff }
89
+ read_byte(stack_address)
90
+ end
91
+
92
+ def stack_push(value)
93
+ write_byte(stack_address, value)
94
+ cycle { @stack_pointer = (@stack_pointer - 1) & 0xff }
95
+ end
96
+
97
+ def stack_push16(value)
98
+ write_byte(stack_address, high_byte(value))
99
+ write_byte(stack_address(-1), low_byte(value))
100
+ cycle { @stack_pointer = (@stack_pointer - 2) & 0xff }
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module InstructionSet
5
+ module Transfer
6
+ # +begin_src ruby
7
+ # Load accumulator with memory.
8
+ #
9
+ # Opcodes:
10
+ # $A1 - indirect_x - 6 cycles
11
+ # $A5 - zeropage - 3 cycles
12
+ # $A9 - immediate - 2 cycles
13
+ # $AD - absolute - 4 cycles
14
+ # $B1 - indirect_y - 5+ cycles
15
+ # $B5 - zeropage_x - 4 cycles
16
+ # $B9 - absolute_y - 4+ cycles
17
+ # $BD - absolute_x - 4+ cycles
18
+ def lda(_addr, value)
19
+ @a = resolve(value)
20
+ update_number_flags(@a)
21
+ end
22
+
23
+ # Load X register with memory.
24
+ #
25
+ # Opcodes:
26
+ # $A2 - immediate - 2 cycles
27
+ # $A6 - zeropage - 3 cycles
28
+ # $AE - absolute - 4 cycles
29
+ # $B6 - zeropage_y - 4 cycles
30
+ # $BE - absolute_y - 4+ cycles
31
+ def ldx(_addr, value)
32
+ @x = resolve(value)
33
+ update_number_flags(@x)
34
+ end
35
+
36
+ # Load Y register with memory.
37
+ #
38
+ # Opcodes:
39
+ # $A0 - immediate - 2 cycles
40
+ # $A4 - zeropage - 3 cycles
41
+ # $AC - absolute - 4 cycles
42
+ # $B4 - zeropage_x - 4 cycles
43
+ # $BC - absolute_x - 4+ cycles
44
+ def ldy(_addr, value)
45
+ @y = resolve(value)
46
+ update_number_flags(@y)
47
+ end
48
+
49
+ # Store accumulator in memory.
50
+ #
51
+ # Opcodes:
52
+ # $81 - indirect_x - 6 cycles
53
+ # $85 - zeropage - 3 cycles
54
+ # $8D - absolute - 4 cycles
55
+ # $91 - indirect_y - 6 cycles
56
+ # $95 - zeropage_x - 4 cycles
57
+ # $99 - absolute_y - 5 cycles
58
+ # $9D - absolute_x - 5 cycles
59
+ def sta(addr, _value)
60
+ write_byte(addr, @a)
61
+ end
62
+
63
+ # Store X register in memory.
64
+ #
65
+ # Opcodes:
66
+ # $86 - zeropage - 3 cycles
67
+ # $8E - absolute - 4 cycles
68
+ # $96 - zeropage_y - 4 cycles
69
+ def stx(addr, _value)
70
+ write_byte(addr, @x)
71
+ end
72
+
73
+ # Store Y register in memory.
74
+ #
75
+ # Opcodes:
76
+ # $84 - zeropage - 3 cycles
77
+ # $8C - absolute - 4 cycles
78
+ # $94 - zeropage_x - 4 cycles
79
+ def sty(addr, _value)
80
+ write_byte(addr, @y)
81
+ end
82
+
83
+ # Transfer accumulator to X register.
84
+ #
85
+ # Opcodes:
86
+ # $AA - implied - 2 cycles
87
+ def tax(_addr, _value)
88
+ cycle { @x = a }
89
+ update_number_flags(@x)
90
+ end
91
+
92
+ # Transfer accumulator to Y register.
93
+ #
94
+ # Opcodes:
95
+ # $A8 - implied - 2 cycles
96
+ def tay(_addr, _value)
97
+ cycle { @y = a }
98
+ update_number_flags(@y)
99
+ end
100
+
101
+ # Transfer stack pointer to X register.
102
+ #
103
+ # Opcodes:
104
+ # $BA - implied - 2 cycles
105
+ def tsx(_addr, _value)
106
+ cycle { @x = stack_pointer }
107
+ update_number_flags(@x)
108
+ end
109
+
110
+ # Transfer X register to accumulator.
111
+ #
112
+ # Opcodes:
113
+ # $8A - implied - 2 cycles
114
+ def txa(_addr, _operand)
115
+ cycle { @a = x }
116
+ update_number_flags(@a)
117
+ end
118
+
119
+ # Transfer X register to stack pointer.
120
+ #
121
+ # Opcodes:
122
+ # $9A - implied - 2 cycles
123
+ def txs(_addr, _operand)
124
+ cycle { @stack_pointer = x }
125
+ end
126
+
127
+ # Transfer Y register to accumulator.
128
+ #
129
+ # Opcodes:
130
+ # $98 - implied - 2 cycles
131
+ def tya(_addr, _operand)
132
+ cycle { @a = y }
133
+ update_number_flags(@a)
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/instruction_set/arithmetic"
4
+ require "badline/instruction_set/bitwise"
5
+ require "badline/instruction_set/branch"
6
+ require "badline/instruction_set/inc_dec"
7
+ require "badline/instruction_set/flag"
8
+ require "badline/instruction_set/illegal"
9
+ require "badline/instruction_set/stack"
10
+ require "badline/instruction_set/transfer"
11
+
12
+ module Badline
13
+ # http://www.6502.org/tutorials/6502opcodes.html
14
+ # http://www.e-tradition.net/bytes/6502/6502_instruction_set.html
15
+ module InstructionSet
16
+ include InstructionSet::Arithmetic
17
+ include InstructionSet::Bitwise
18
+ include InstructionSet::Branch
19
+ include InstructionSet::IncDec
20
+ include InstructionSet::Flag
21
+ include InstructionSet::Illegal
22
+ include InstructionSet::Stack
23
+ include InstructionSet::Transfer
24
+
25
+ # Forces a software interrupt (break).
26
+ # Pushes PC+2 and status to stack, sets break flag, and jumps via IRQ vector.
27
+ #
28
+ # Opcodes:
29
+ # $00 - implied - 7 cycles
30
+ def brk(_addr, _value)
31
+ status.break = true
32
+ handle_interrupt(0xfffe, brk: true, pre_cycles: 1)
33
+ status.break = false
34
+ end
35
+
36
+ # No operation. Does nothing but consume a clock cycle.
37
+ #
38
+ # Opcodes:
39
+ # $EA - implied - 2 cycles
40
+ # $04, $44, $64 - zeropage - 3 cycles (illegal)
41
+ # $14, $34, $54, $74, $D4, $F4 - zeropage_x - 4 cycles (illegal)
42
+ # $0C - absolute - 4 cycles (illegal)
43
+ # $1C, $3C, $5C, $7C, $DC, $FC - absolute_x - 4+ cycles (illegal)
44
+ # $1A, $3A, $5A, $7A, $DA, $FA - implied - 2 cycles (illegal)
45
+ def nop(_addr, _value)
46
+ cycle
47
+ end
48
+
49
+ private
50
+
51
+ def resolve(value)
52
+ if value == :lazy
53
+ realize_value(@instruction, @operand, @address)
54
+ elsif value.is_a?(Proc)
55
+ value.call
56
+ else
57
+ value
58
+ end
59
+ end
60
+
61
+ def update_number_flags(value)
62
+ status.zero = value.zero?
63
+ status.negative = value.anybits?(0x80)
64
+ value
65
+ end
66
+
67
+ # Read-modify-write instructions put the unmodified value back on the bus before writing the result.
68
+ def write_modified(addr, original, result)
69
+ if addr == :accumulator
70
+ cycle { @a = result }
71
+ else
72
+ write_byte(addr, original)
73
+ write_byte(addr, result)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module IntegerHelper
5
+ def bcd(number)
6
+ high, low = number.divmod(10)
7
+ (high << 4) + low
8
+ end
9
+
10
+ def bcd_to_i(number)
11
+ (((number & 0xf0) >> 4) * 10) + (number & 0x0f)
12
+ end
13
+
14
+ def format8(number)
15
+ "0x#{number.to_s(16).rjust(2, '0')}"
16
+ end
17
+
18
+ def format16(number)
19
+ "0x#{number.to_s(16).rjust(4, '0')}"
20
+ end
21
+
22
+ def high_byte(number)
23
+ (number >> 8) & 0xff
24
+ end
25
+
26
+ def low_byte(number)
27
+ number & 0xff
28
+ end
29
+
30
+ def signed_int8(number)
31
+ uint = number & 0xff
32
+ uint > 127 ? uint - 256 : uint
33
+ end
34
+
35
+ def uint16(low, high)
36
+ (high << 8) + low
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Joystick
5
+ DIRECTIONS = { up: 0, down: 1, left: 2, right: 3, fire: 4 }.freeze
6
+
7
+ def initialize
8
+ @pressed = {}
9
+ end
10
+
11
+ def press(direction)
12
+ @pressed[direction] = true if DIRECTIONS.key?(direction)
13
+ end
14
+
15
+ def release(direction)
16
+ @pressed.delete(direction)
17
+ end
18
+
19
+ def port_bits
20
+ bits = 0xff
21
+ DIRECTIONS.each { |dir, bit| bits &= ~(1 << bit) if @pressed[dir] }
22
+ bits
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module KernalTrap
5
+ class File
6
+ include IntegerHelper
7
+
8
+ DEVICE = 8
9
+
10
+ MISSING_FILENAME = 0x08
11
+
12
+ def initialize(cpu:, bus:, storage:)
13
+ @cpu = cpu
14
+ @bus = bus
15
+ @storage = storage
16
+ end
17
+
18
+ private
19
+
20
+ def active?
21
+ @bus.io_port.kernal? && @bus.peek(0xba) == DEVICE
22
+ end
23
+
24
+ # Filename pointer at $BB/$BC, length at $B7
25
+ def filename
26
+ pointer = uint16(@bus.peek(0xbb), @bus.peek(0xbc))
27
+ bytes = Array.new(@bus.peek(0xb7)) do |i|
28
+ @bus.peek((pointer + i) & 0xffff)
29
+ end
30
+ strip_drive_prefix(Storage.ascii(bytes))
31
+ end
32
+
33
+ # CBM DOS drive prefix — "0:NAME" selects a drive, "@0:NAME" is
34
+ # save-with-replace
35
+ def strip_drive_prefix(name)
36
+ name.sub(/\A@?\d*:/, "")
37
+ end
38
+
39
+ def error(code)
40
+ @cpu.a = code
41
+ @cpu.status.carry = true
42
+ end
43
+
44
+ def return_to_caller
45
+ @cpu.program_counter = (uint16(pull_byte, pull_byte) + 1) & 0xffff
46
+ end
47
+
48
+ def pull_byte
49
+ @cpu.stack_pointer = (@cpu.stack_pointer + 1) & 0xff
50
+ @bus.peek(0x0100 + @cpu.stack_pointer)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module KernalTrap
5
+ # PC trap on the KERNAL serial LOAD routine ($F4A5, the default ILOAD
6
+ # vector target). Serves device 8 requests from a storage backend and
7
+ # returns to the caller with the routine's register/zeropage contract;
8
+ # other devices fall through to the ROM.
9
+ class Load < File
10
+ ADDRESS = 0xf4a5
11
+
12
+ FILE_NOT_FOUND = 0x04
13
+
14
+ def call
15
+ return unless active?
16
+
17
+ name = filename
18
+ if name.empty?
19
+ error(MISSING_FILENAME)
20
+ elsif (data = @storage.read_file(name))
21
+ deliver(data)
22
+ else
23
+ error(FILE_NOT_FOUND)
24
+ end
25
+ return_to_caller
26
+ end
27
+
28
+ private
29
+
30
+ # A=0 is LOAD, A=1 is VERIFY
31
+ def load?
32
+ @cpu.a.zero?
33
+ end
34
+
35
+ def deliver(data)
36
+ addr = load_address(data)
37
+ payload = data[2..] || []
38
+ payload = payload[0, 0x10000 - addr] if addr + payload.length > 0x10000
39
+ @bus.ram.write(addr, payload) if load?
40
+ finish((addr + payload.length) & 0xffff)
41
+ end
42
+
43
+ # Secondary address $B9 zero relocates to the caller-supplied address
44
+ # stashed at $C3/$C4 (MEMUSS)
45
+ def load_address(data)
46
+ if @bus.peek(0xb9).zero?
47
+ uint16(@bus.peek(0xc3), @bus.peek(0xc4))
48
+ else
49
+ uint16(data[0], data[1])
50
+ end
51
+ end
52
+
53
+ def finish(end_addr)
54
+ @bus.poke(0x90, 0x40)
55
+ @bus.poke(0xae, low_byte(end_addr))
56
+ @bus.poke(0xaf, high_byte(end_addr))
57
+ @cpu.x = low_byte(end_addr)
58
+ @cpu.y = high_byte(end_addr)
59
+ @cpu.status.carry = false
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module KernalTrap
5
+ # PC trap on the KERNAL serial SAVE routine ($F5ED, the default ISAVE
6
+ # vector target). Writes device 8 saves to a storage backend as a PRG
7
+ # (load address followed by the memory range); other devices fall
8
+ # through to the ROM.
9
+ class Save < File
10
+ ADDRESS = 0xf5ed
11
+
12
+ def call
13
+ return unless active?
14
+
15
+ name = filename
16
+ if name.empty?
17
+ error(MISSING_FILENAME)
18
+ else
19
+ @storage.write_file(name, payload)
20
+ finish
21
+ end
22
+ return_to_caller
23
+ end
24
+
25
+ private
26
+
27
+ # Start address at $C1/$C2 (STAL), end address (exclusive) at
28
+ # $AE/$AF (EAL)
29
+ def payload
30
+ start = uint16(@bus.peek(0xc1), @bus.peek(0xc2))
31
+ length = (uint16(@bus.peek(0xae), @bus.peek(0xaf)) - start) & 0xffff
32
+ [low_byte(start), high_byte(start)] +
33
+ Array.new(length) { |i| @bus.peek((start + i) & 0xffff) }
34
+ end
35
+
36
+ def finish
37
+ @bus.poke(0x90, 0x00)
38
+ @cpu.status.carry = false
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/kernal_trap/file"
4
+ require "badline/kernal_trap/load"
5
+ require "badline/kernal_trap/save"
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class Keyboard
5
+ include IntegerHelper
6
+
7
+ attr_reader :keys, :matrix
8
+
9
+ def initialize
10
+ @keys = []
11
+
12
+ @matrix = [
13
+ %i[delete return cursor_h f7 f1 f3 cursor_v],
14
+ %i[3 w a 4 z s e lshift],
15
+ %i[5 r d 6 c f t x],
16
+ %i[7 y g 8 b h u v],
17
+ %i[9 i j 0 m k o n],
18
+ %i[+ p l - . : @ ,],
19
+ %i[£ * ; clr_home rshift = up /],
20
+ %i[1 left control 2 space cbm q run_stop]
21
+ ]
22
+ end
23
+
24
+ def press(key)
25
+ @keys << key if valid_key?(key)
26
+ end
27
+
28
+ def read_a(_port_a, _port_b)
29
+ 0xff
30
+ end
31
+
32
+ def read_b(port_a, _port_b)
33
+ return 0xff unless keys.any?
34
+
35
+ output = 0xff
36
+
37
+ matrix.each_with_index do |row, a|
38
+ next unless port_a[a].zero?
39
+
40
+ row.each_with_index do |key, b|
41
+ output -= (1 << b) if keys.include?(key)
42
+ end
43
+ end
44
+
45
+ output
46
+ end
47
+
48
+ def release(key)
49
+ @keys.reject! { |k| k == key }
50
+ end
51
+
52
+ private
53
+
54
+ def valid_key?(key)
55
+ matrix.flatten.include?(key)
56
+ end
57
+ end
58
+ end