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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +40 -0
- data/exe/badline +55 -0
- data/lib/badline/address_bus.rb +206 -0
- data/lib/badline/addressable.rb +61 -0
- data/lib/badline/cartridge/magic_desk.rb +23 -0
- data/lib/badline/cartridge/ocean.rb +33 -0
- data/lib/badline/cartridge/standard.rb +31 -0
- data/lib/badline/cartridge.rb +75 -0
- data/lib/badline/chrout_trap.rb +39 -0
- data/lib/badline/cia/timer.rb +122 -0
- data/lib/badline/cia.rb +189 -0
- data/lib/badline/color_memory.rb +16 -0
- data/lib/badline/computer.rb +100 -0
- data/lib/badline/control_ports.rb +24 -0
- data/lib/badline/cpu.rb +239 -0
- data/lib/badline/cycleable.rb +35 -0
- data/lib/badline/gui/application.rb +94 -0
- data/lib/badline/gui/joy_map.rb +19 -0
- data/lib/badline/gui/key_map.rb +34 -0
- data/lib/badline/gui/palette.rb +35 -0
- data/lib/badline/gui/pane.rb +35 -0
- data/lib/badline/gui/screen_pane.rb +50 -0
- data/lib/badline/gui/window.rb +46 -0
- data/lib/badline/gui.rb +11 -0
- data/lib/badline/instruction.rb +334 -0
- data/lib/badline/instruction_set/arithmetic.rb +119 -0
- data/lib/badline/instruction_set/bitwise.rb +131 -0
- data/lib/badline/instruction_set/branch.rb +78 -0
- data/lib/badline/instruction_set/flag.rb +63 -0
- data/lib/badline/instruction_set/illegal.rb +278 -0
- data/lib/badline/instruction_set/inc_dec.rb +71 -0
- data/lib/badline/instruction_set/stack.rb +104 -0
- data/lib/badline/instruction_set/transfer.rb +137 -0
- data/lib/badline/instruction_set.rb +77 -0
- data/lib/badline/integer_helper.rb +39 -0
- data/lib/badline/joystick.rb +25 -0
- data/lib/badline/kernal_trap/file.rb +54 -0
- data/lib/badline/kernal_trap/load.rb +63 -0
- data/lib/badline/kernal_trap/save.rb +42 -0
- data/lib/badline/kernal_trap.rb +5 -0
- data/lib/badline/keyboard.rb +58 -0
- data/lib/badline/keyboard_buffer.rb +33 -0
- data/lib/badline/media.rb +59 -0
- data/lib/badline/memory.rb +43 -0
- data/lib/badline/rom.rb +23 -0
- data/lib/badline/roms/README +18 -0
- data/lib/badline/roms/basic.rom +0 -0
- data/lib/badline/roms/character.rom +0 -0
- data/lib/badline/roms/kernal.rom +0 -0
- data/lib/badline/sid.rb +25 -0
- data/lib/badline/status.rb +56 -0
- data/lib/badline/storage/crt_file.rb +53 -0
- data/lib/badline/storage/d64_image.rb +21 -0
- data/lib/badline/storage/d71_image.rb +13 -0
- data/lib/badline/storage/d81_image.rb +14 -0
- data/lib/badline/storage/disk_image.rb +71 -0
- data/lib/badline/storage/host_directory.rb +49 -0
- data/lib/badline/storage/p00.rb +24 -0
- data/lib/badline/storage.rb +28 -0
- data/lib/badline/time_of_day.rb +101 -0
- data/lib/badline/traps.rb +15 -0
- data/lib/badline/version.rb +5 -0
- data/lib/badline/vic/bank.rb +65 -0
- data/lib/badline/vic/display_state.rb +78 -0
- data/lib/badline/vic/graphics_mode.rb +139 -0
- data/lib/badline/vic/registers.rb +170 -0
- data/lib/badline/vic/sequencer.rb +237 -0
- data/lib/badline/vic/sprite.rb +121 -0
- data/lib/badline/vic/sprites.rb +112 -0
- data/lib/badline/vic.rb +192 -0
- data/lib/badline.rb +29 -0
- 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,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
|