badline 0.1.0 → 0.2.1
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 +4 -4
- data/CHANGELOG.md +185 -0
- data/CLAUDE.md +217 -0
- data/CODE_OF_CONDUCT.md +92 -0
- data/CONTRIBUTING.md +51 -0
- data/README.md +224 -31
- data/doc/pinned-behaviour.md +1117 -0
- data/exe/badline +22 -2
- data/exe/badline-sid +34 -0
- data/lib/badline/address_bus.rb +96 -25
- data/lib/badline/audio/aiff.rb +36 -0
- data/lib/badline/audio/bare_player.rb +129 -0
- data/lib/badline/audio/cli.rb +133 -0
- data/lib/badline/audio/console.rb +75 -0
- data/lib/badline/audio/jukebox.rb +82 -0
- data/lib/badline/audio/machine_player.rb +57 -0
- data/lib/badline/audio/options.rb +137 -0
- data/lib/badline/audio/pcm_writer.rb +57 -0
- data/lib/badline/audio/playback.rb +108 -0
- data/lib/badline/audio/renderer.rb +85 -0
- data/lib/badline/audio/sdl_sink.rb +102 -0
- data/lib/badline/audio/wav.rb +23 -0
- data/lib/badline/audio.rb +14 -0
- data/lib/badline/cartridge/action_replay.rb +111 -0
- data/lib/badline/cartridge/atomic_power.rb +24 -0
- data/lib/badline/cartridge/bank.rb +81 -0
- data/lib/badline/cartridge/comal80.rb +48 -0
- data/lib/badline/cartridge/dinamic.rb +34 -0
- data/lib/badline/cartridge/easy_flash.rb +122 -0
- data/lib/badline/cartridge/epyx_fastload.rb +71 -0
- data/lib/badline/cartridge/final_cartridge3.rb +77 -0
- data/lib/badline/cartridge/flash.rb +265 -0
- data/lib/badline/cartridge/freezer.rb +31 -0
- data/lib/badline/cartridge/fun_play.rb +38 -0
- data/lib/badline/cartridge/g_mod2.rb +62 -0
- data/lib/badline/cartridge/game_system.rb +40 -0
- data/lib/badline/cartridge/kcs_power.rb +72 -0
- data/lib/badline/cartridge/mach5.rb +34 -0
- data/lib/badline/cartridge/magic_desk.rb +6 -3
- data/lib/badline/cartridge/ocean.rb +11 -12
- data/lib/badline/cartridge/pagefox.rb +56 -0
- data/lib/badline/cartridge/retro_replay.rb +276 -0
- data/lib/badline/cartridge/rex_utility.rb +33 -0
- data/lib/badline/cartridge/rgcd.rb +48 -0
- data/lib/badline/cartridge/simons_basic.rb +40 -0
- data/lib/badline/cartridge/standard.rb +3 -19
- data/lib/badline/cartridge/super_games.rb +36 -0
- data/lib/badline/cartridge/westermann.rb +34 -0
- data/lib/badline/cartridge/zaxxon.rb +50 -0
- data/lib/badline/cartridge.rb +132 -16
- data/lib/badline/cia/interrupt_register.rb +102 -0
- data/lib/badline/cia/serial.rb +219 -0
- data/lib/badline/cia/timer.rb +43 -31
- data/lib/badline/cia.rb +138 -88
- data/lib/badline/color_memory.rb +14 -5
- data/lib/badline/computer.rb +97 -12
- data/lib/badline/control_ports.rb +54 -9
- data/lib/badline/cpu/addressing.rb +131 -0
- data/lib/badline/cpu/microcode.rb +169 -0
- data/lib/badline/cpu/operations.rb +88 -0
- data/lib/badline/cpu/stack_operations.rb +82 -0
- data/lib/badline/cpu.rb +109 -168
- data/lib/badline/cycleable.rb +3 -0
- data/lib/badline/datasette.rb +81 -0
- data/lib/badline/debug_register.rb +28 -0
- data/lib/badline/gui/application.rb +79 -18
- data/lib/badline/gui/gamepads.rb +97 -0
- data/lib/badline/gui/joy_map.rb +15 -7
- data/lib/badline/gui.rb +1 -0
- data/lib/badline/input/mouse1351.rb +41 -0
- data/lib/badline/input/paddles.rb +36 -0
- data/lib/badline/input.rb +4 -0
- data/lib/badline/instruction_set/arithmetic.rb +16 -21
- data/lib/badline/instruction_set/bitwise.rb +17 -22
- data/lib/badline/instruction_set/branch.rb +20 -19
- data/lib/badline/instruction_set/flag.rb +7 -7
- data/lib/badline/instruction_set/illegal.rb +14 -22
- data/lib/badline/instruction_set/inc_dec.rb +8 -10
- data/lib/badline/instruction_set/stack.rb +5 -57
- data/lib/badline/instruction_set/transfer.rb +9 -9
- data/lib/badline/instruction_set.rb +9 -31
- data/lib/badline/interrupts.rb +104 -0
- data/lib/badline/joystick.rb +9 -9
- data/lib/badline/kernal_trap/channel.rb +51 -0
- data/lib/badline/kernal_trap/drive/status.rb +49 -0
- data/lib/badline/kernal_trap/drive.rb +277 -0
- data/lib/badline/kernal_trap/file.rb +5 -25
- data/lib/badline/kernal_trap/load.rb +86 -24
- data/lib/badline/kernal_trap/routine.rb +51 -0
- data/lib/badline/kernal_trap/save.rb +46 -14
- data/lib/badline/kernal_trap/serial.rb +171 -0
- data/lib/badline/kernal_trap.rb +4 -0
- data/lib/badline/keyboard.rb +40 -16
- data/lib/badline/media.rb +48 -10
- data/lib/badline/memory.rb +5 -0
- data/lib/badline/roms/eapi/LICENSE.md +15 -0
- data/lib/badline/roms/eapi/README +41 -0
- data/lib/badline/roms/eapi/eapi-am29f040-14 +0 -0
- data/lib/badline/sid/decimator.rb +46 -0
- data/lib/badline/sid/envelope.rb +232 -0
- data/lib/badline/sid/filter.rb +213 -0
- data/lib/badline/sid/voice.rb +61 -0
- data/lib/badline/sid/waveform/combined.rb +126 -0
- data/lib/badline/sid/waveform/fast_forward.rb +105 -0
- data/lib/badline/sid/waveform/noise_writeback.rb +82 -0
- data/lib/badline/sid/waveform.rb +289 -0
- data/lib/badline/sid.rb +274 -7
- data/lib/badline/status.rb +251 -23
- data/lib/badline/storage/crt_file.rb +18 -2
- data/lib/badline/storage/d64_image.rb +6 -0
- data/lib/badline/storage/d71_image.rb +4 -0
- data/lib/badline/storage/d81_image.rb +4 -0
- data/lib/badline/storage/disk_image.rb +99 -13
- data/lib/badline/storage/host_directory.rb +30 -4
- data/lib/badline/storage/sid_file.rb +274 -0
- data/lib/badline/storage/song_lengths.rb +65 -0
- data/lib/badline/storage/t64.rb +67 -0
- data/lib/badline/storage/tap.rb +68 -0
- data/lib/badline/storage.rb +19 -0
- data/lib/badline/time_of_day.rb +105 -52
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic/bank.rb +14 -6
- data/lib/badline/vic/border_mask.rb +76 -0
- data/lib/badline/vic/collisions.rb +104 -0
- data/lib/badline/vic/color_patches.rb +33 -0
- data/lib/badline/vic/display_state.rb +92 -29
- data/lib/badline/vic/graphics_mode.rb +78 -53
- data/lib/badline/vic/graphics_shifter.rb +140 -0
- data/lib/badline/vic/register_log.rb +63 -0
- data/lib/badline/vic/registers.rb +3 -0
- data/lib/badline/vic/sequencer.rb +163 -137
- data/lib/badline/vic/sequencer_output.rb +116 -0
- data/lib/badline/vic/sprite/internal_bus.rb +49 -0
- data/lib/badline/vic/sprite/shifter.rb +129 -0
- data/lib/badline/vic/sprite.rb +275 -65
- data/lib/badline/vic/sprites.rb +220 -58
- data/lib/badline/vic.rb +389 -55
- data/lib/badline.rb +5 -0
- data/vendor/.gitkeep +0 -0
- metadata +104 -4
|
@@ -11,9 +11,8 @@ module Badline
|
|
|
11
11
|
# $D6 - zeropage_x - 6 cycles
|
|
12
12
|
# $DE - absolute_x - 7 cycles
|
|
13
13
|
def dec(addr, value)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
write_modified(addr, v, result)
|
|
14
|
+
result = (value - 1) & 0xff
|
|
15
|
+
write_modified(addr, result)
|
|
17
16
|
update_number_flags(result)
|
|
18
17
|
end
|
|
19
18
|
|
|
@@ -22,7 +21,7 @@ module Badline
|
|
|
22
21
|
# Opcodes:
|
|
23
22
|
# $CA - implied - 2 cycles
|
|
24
23
|
def dex(_addr, _value)
|
|
25
|
-
|
|
24
|
+
@x = (@x - 1) & 0xff
|
|
26
25
|
update_number_flags(@x)
|
|
27
26
|
end
|
|
28
27
|
|
|
@@ -31,7 +30,7 @@ module Badline
|
|
|
31
30
|
# Opcodes:
|
|
32
31
|
# $88 - implied - 2 cycles
|
|
33
32
|
def dey(_addr, _value)
|
|
34
|
-
|
|
33
|
+
@y = (@y - 1) & 0xff
|
|
35
34
|
update_number_flags(@y)
|
|
36
35
|
end
|
|
37
36
|
|
|
@@ -43,9 +42,8 @@ module Badline
|
|
|
43
42
|
# $F6 - zeropage_x - 6 cycles
|
|
44
43
|
# $FE - absolute_x - 7 cycles
|
|
45
44
|
def inc(addr, value)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
write_modified(addr, v, result)
|
|
45
|
+
result = (value + 1) & 0xff
|
|
46
|
+
write_modified(addr, result)
|
|
49
47
|
update_number_flags(result)
|
|
50
48
|
end
|
|
51
49
|
|
|
@@ -54,7 +52,7 @@ module Badline
|
|
|
54
52
|
# Opcodes:
|
|
55
53
|
# $E8 - implied - 2 cycles
|
|
56
54
|
def inx(_addr, _value)
|
|
57
|
-
|
|
55
|
+
@x = (@x + 1) & 0xff
|
|
58
56
|
update_number_flags(@x)
|
|
59
57
|
end
|
|
60
58
|
|
|
@@ -63,7 +61,7 @@ module Badline
|
|
|
63
61
|
# Opcodes:
|
|
64
62
|
# $C8 - implied - 2 cycles
|
|
65
63
|
def iny(_addr, _value)
|
|
66
|
-
|
|
64
|
+
@y = (@y + 1) & 0xff
|
|
67
65
|
update_number_flags(@y)
|
|
68
66
|
end
|
|
69
67
|
end
|
|
@@ -24,7 +24,7 @@ module Badline
|
|
|
24
24
|
# Opcodes:
|
|
25
25
|
# $68 - implied - 4 cycles
|
|
26
26
|
def pla(_addr, _value)
|
|
27
|
-
|
|
27
|
+
@a = stack_pull
|
|
28
28
|
update_number_flags(@a)
|
|
29
29
|
end
|
|
30
30
|
|
|
@@ -33,71 +33,19 @@ module Badline
|
|
|
33
33
|
# Opcodes:
|
|
34
34
|
# $28 - implied - 4 cycles
|
|
35
35
|
def plp(_addr, _value)
|
|
36
|
-
|
|
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
|
|
36
|
+
status.value = stack_pull & 0b11101111
|
|
83
37
|
end
|
|
84
38
|
|
|
85
39
|
private
|
|
86
40
|
|
|
87
41
|
def stack_pull
|
|
88
|
-
|
|
89
|
-
|
|
42
|
+
@stack_pointer = (@stack_pointer + 1) & 0xff
|
|
43
|
+
@memory.peek(stack_address)
|
|
90
44
|
end
|
|
91
45
|
|
|
92
46
|
def stack_push(value)
|
|
93
47
|
write_byte(stack_address, value)
|
|
94
|
-
|
|
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 }
|
|
48
|
+
@stack_pointer = (@stack_pointer - 1) & 0xff
|
|
101
49
|
end
|
|
102
50
|
end
|
|
103
51
|
end
|
|
@@ -16,7 +16,7 @@ module Badline
|
|
|
16
16
|
# $B9 - absolute_y - 4+ cycles
|
|
17
17
|
# $BD - absolute_x - 4+ cycles
|
|
18
18
|
def lda(_addr, value)
|
|
19
|
-
@a =
|
|
19
|
+
@a = value
|
|
20
20
|
update_number_flags(@a)
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -29,7 +29,7 @@ module Badline
|
|
|
29
29
|
# $B6 - zeropage_y - 4 cycles
|
|
30
30
|
# $BE - absolute_y - 4+ cycles
|
|
31
31
|
def ldx(_addr, value)
|
|
32
|
-
@x =
|
|
32
|
+
@x = value
|
|
33
33
|
update_number_flags(@x)
|
|
34
34
|
end
|
|
35
35
|
|
|
@@ -42,7 +42,7 @@ module Badline
|
|
|
42
42
|
# $B4 - zeropage_x - 4 cycles
|
|
43
43
|
# $BC - absolute_x - 4+ cycles
|
|
44
44
|
def ldy(_addr, value)
|
|
45
|
-
@y =
|
|
45
|
+
@y = value
|
|
46
46
|
update_number_flags(@y)
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -85,7 +85,7 @@ module Badline
|
|
|
85
85
|
# Opcodes:
|
|
86
86
|
# $AA - implied - 2 cycles
|
|
87
87
|
def tax(_addr, _value)
|
|
88
|
-
|
|
88
|
+
@x = a
|
|
89
89
|
update_number_flags(@x)
|
|
90
90
|
end
|
|
91
91
|
|
|
@@ -94,7 +94,7 @@ module Badline
|
|
|
94
94
|
# Opcodes:
|
|
95
95
|
# $A8 - implied - 2 cycles
|
|
96
96
|
def tay(_addr, _value)
|
|
97
|
-
|
|
97
|
+
@y = a
|
|
98
98
|
update_number_flags(@y)
|
|
99
99
|
end
|
|
100
100
|
|
|
@@ -103,7 +103,7 @@ module Badline
|
|
|
103
103
|
# Opcodes:
|
|
104
104
|
# $BA - implied - 2 cycles
|
|
105
105
|
def tsx(_addr, _value)
|
|
106
|
-
|
|
106
|
+
@x = stack_pointer
|
|
107
107
|
update_number_flags(@x)
|
|
108
108
|
end
|
|
109
109
|
|
|
@@ -112,7 +112,7 @@ module Badline
|
|
|
112
112
|
# Opcodes:
|
|
113
113
|
# $8A - implied - 2 cycles
|
|
114
114
|
def txa(_addr, _operand)
|
|
115
|
-
|
|
115
|
+
@a = x
|
|
116
116
|
update_number_flags(@a)
|
|
117
117
|
end
|
|
118
118
|
|
|
@@ -121,7 +121,7 @@ module Badline
|
|
|
121
121
|
# Opcodes:
|
|
122
122
|
# $9A - implied - 2 cycles
|
|
123
123
|
def txs(_addr, _operand)
|
|
124
|
-
|
|
124
|
+
@stack_pointer = x
|
|
125
125
|
end
|
|
126
126
|
|
|
127
127
|
# Transfer Y register to accumulator.
|
|
@@ -129,7 +129,7 @@ module Badline
|
|
|
129
129
|
# Opcodes:
|
|
130
130
|
# $98 - implied - 2 cycles
|
|
131
131
|
def tya(_addr, _operand)
|
|
132
|
-
|
|
132
|
+
@a = y
|
|
133
133
|
update_number_flags(@a)
|
|
134
134
|
end
|
|
135
135
|
end
|
|
@@ -22,18 +22,8 @@ module Badline
|
|
|
22
22
|
include InstructionSet::Stack
|
|
23
23
|
include InstructionSet::Transfer
|
|
24
24
|
|
|
25
|
-
#
|
|
26
|
-
#
|
|
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.
|
|
25
|
+
# No operation. The illegal forms with an operand still read it, in
|
|
26
|
+
# the steps before this is called.
|
|
37
27
|
#
|
|
38
28
|
# Opcodes:
|
|
39
29
|
# $EA - implied - 2 cycles
|
|
@@ -42,35 +32,23 @@ module Badline
|
|
|
42
32
|
# $0C - absolute - 4 cycles (illegal)
|
|
43
33
|
# $1C, $3C, $5C, $7C, $DC, $FC - absolute_x - 4+ cycles (illegal)
|
|
44
34
|
# $1A, $3A, $5A, $7A, $DA, $FA - implied - 2 cycles (illegal)
|
|
45
|
-
def nop(_addr, _value)
|
|
46
|
-
cycle
|
|
47
|
-
end
|
|
35
|
+
def nop(_addr, _value); end
|
|
48
36
|
|
|
49
37
|
private
|
|
50
38
|
|
|
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
39
|
def update_number_flags(value)
|
|
62
40
|
status.zero = value.zero?
|
|
63
|
-
status.negative = value
|
|
41
|
+
status.negative = value & 0x80 != 0
|
|
64
42
|
value
|
|
65
43
|
end
|
|
66
44
|
|
|
67
|
-
#
|
|
68
|
-
|
|
45
|
+
# Stores a read-modify-write result. For memory, the write happens in
|
|
46
|
+
# the steps that follow (see Operations#op_rmw_read).
|
|
47
|
+
def write_modified(addr, result)
|
|
69
48
|
if addr == :accumulator
|
|
70
|
-
|
|
49
|
+
@a = result
|
|
71
50
|
else
|
|
72
|
-
|
|
73
|
-
write_byte(addr, result)
|
|
51
|
+
@rmw_result = result
|
|
74
52
|
end
|
|
75
53
|
end
|
|
76
54
|
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Interrupts
|
|
5
|
+
BREAK_MASK = 0x10
|
|
6
|
+
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
# Samples the IRQ line and the NMI latch once per cycle. An instruction
|
|
10
|
+
# boundary commits the sample taken on its second-to-last cycle.
|
|
11
|
+
def poll
|
|
12
|
+
if @skip_poll
|
|
13
|
+
@skip_poll = false
|
|
14
|
+
return
|
|
15
|
+
end
|
|
16
|
+
@irq_pending = @irq_sample
|
|
17
|
+
@nmi_pending = @nmi_sample
|
|
18
|
+
@irq_sample = @irq && !@status.interrupt?
|
|
19
|
+
@nmi_sample = @nmi
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# A cycle the VIC stalls through BA keeps sampling the lines, OR-ed with
|
|
23
|
+
# the sample it already holds, but the boundary pipeline doesn't advance
|
|
24
|
+
# and a skipped poll stays owed to the next real cycle.
|
|
25
|
+
def sample_while_stalled
|
|
26
|
+
@irq_sample ||= @irq && !interrupt_disabled_after_stall?
|
|
27
|
+
@nmi_sample = true if @nmi
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# A stalled CLI or SEI execute cycle masks with the I it is about to
|
|
31
|
+
# set. Every other step masks with the current I, PLP included: its
|
|
32
|
+
# pulled I only arrives with the stalled stack read.
|
|
33
|
+
def interrupt_disabled_after_stall?
|
|
34
|
+
return @status.interrupt? unless @plan.equal?(CPU::IMPLIED_PLAN)
|
|
35
|
+
|
|
36
|
+
case @operation
|
|
37
|
+
when :cli then false
|
|
38
|
+
when :sei then true
|
|
39
|
+
else @status.interrupt?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Runs in the opcode fetch cycle in place of the fetch, with no bus
|
|
44
|
+
# access, and switches to the interrupt plan.
|
|
45
|
+
def start_interrupt
|
|
46
|
+
@interrupt = @boundary_nmi ? 0xfffa : 0xfffe
|
|
47
|
+
@brk = false
|
|
48
|
+
@plan = CPU::INTERRUPT_PLAN
|
|
49
|
+
@writes = CPU::INTERRUPT_WRITES
|
|
50
|
+
@index = 1
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# BRK reads the byte after its opcode and ignores it, and selects the
|
|
54
|
+
# IRQ vector.
|
|
55
|
+
def op_brk_dummy
|
|
56
|
+
@memory.peek(@program_counter)
|
|
57
|
+
@interrupt = 0xfffe
|
|
58
|
+
@brk = true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def op_int_push_pch
|
|
62
|
+
@address = @brk ? (@program_counter + 1) & 0xffff : @program_counter
|
|
63
|
+
@memory.poke(stack_address, high_byte(@address))
|
|
64
|
+
@stack_pointer = (@stack_pointer - 1) & 0xff
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def op_int_push_pcl
|
|
68
|
+
@memory.poke(stack_address, low_byte(@address))
|
|
69
|
+
@stack_pointer = (@stack_pointer - 1) & 0xff
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def op_int_push_status
|
|
73
|
+
value = @brk ? @status.value | BREAK_MASK : @status.value & ~BREAK_MASK
|
|
74
|
+
@memory.poke(stack_address, value)
|
|
75
|
+
@stack_pointer = (@stack_pointer - 1) & 0xff
|
|
76
|
+
@status.interrupt = true
|
|
77
|
+
# An NMI that arrives before cycle 4 of a BRK or IRQ takes it over:
|
|
78
|
+
# the sequence finishes with the NMI vector.
|
|
79
|
+
@interrupt = 0xfffa if @interrupt == 0xfffe && @nmi_pending
|
|
80
|
+
@nmi = false if @interrupt == 0xfffa
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def op_int_vector_low
|
|
84
|
+
@address = @memory.peek(@interrupt)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Clears any interrupt that is pending, so the handler's first
|
|
88
|
+
# instruction always runs before the next interrupt. This applies to
|
|
89
|
+
# BRK too.
|
|
90
|
+
def op_int_vector_high
|
|
91
|
+
high = @memory.peek((@interrupt + 1) & 0xffff)
|
|
92
|
+
@program_counter = @address | (high << 8)
|
|
93
|
+
@irq_sample = @irq_pending = false
|
|
94
|
+
@nmi_sample = @nmi_pending = false
|
|
95
|
+
@interrupt = nil
|
|
96
|
+
if @brk
|
|
97
|
+
@status.break = false
|
|
98
|
+
end_instruction
|
|
99
|
+
else
|
|
100
|
+
end_sequence
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
data/lib/badline/joystick.rb
CHANGED
|
@@ -4,22 +4,22 @@ module Badline
|
|
|
4
4
|
class Joystick
|
|
5
5
|
DIRECTIONS = { up: 0, down: 1, left: 2, right: 3, fire: 4 }.freeze
|
|
6
6
|
|
|
7
|
+
# Kept up to date as switches move, since the CIA samples the fire line
|
|
8
|
+
# every cycle to drive the light pen input.
|
|
9
|
+
attr_reader :port_bits
|
|
10
|
+
|
|
7
11
|
def initialize
|
|
8
|
-
@
|
|
12
|
+
@port_bits = 0xff
|
|
9
13
|
end
|
|
10
14
|
|
|
11
15
|
def press(direction)
|
|
12
|
-
|
|
16
|
+
bit = DIRECTIONS[direction]
|
|
17
|
+
@port_bits &= ~(1 << bit) if bit
|
|
13
18
|
end
|
|
14
19
|
|
|
15
20
|
def release(direction)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def port_bits
|
|
20
|
-
bits = 0xff
|
|
21
|
-
DIRECTIONS.each { |dir, bit| bits &= ~(1 << bit) if @pressed[dir] }
|
|
22
|
-
bits
|
|
21
|
+
bit = DIRECTIONS[direction]
|
|
22
|
+
@port_bits |= 1 << bit if bit
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module KernalTrap
|
|
5
|
+
# One open channel on the virtual drive, holding a file, a block buffer
|
|
6
|
+
# or the command channel's status message. It gives out one byte at a
|
|
7
|
+
# time and flags the last one, so the bus can raise EOI with it. A
|
|
8
|
+
# channel cut short by a read error never flags one, and holds the
|
|
9
|
+
# error for the drive to report once its bytes run out.
|
|
10
|
+
class Channel
|
|
11
|
+
attr_accessor :pointer
|
|
12
|
+
attr_reader :error
|
|
13
|
+
|
|
14
|
+
def initialize(bytes = [], error: nil)
|
|
15
|
+
replace(bytes)
|
|
16
|
+
@error = error
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# A file's bytes, up to the first block its chain can't read. The
|
|
20
|
+
# drive reads a block ahead before sending the last byte of the one
|
|
21
|
+
# before, so a bad block holds that byte back too, and a bad first
|
|
22
|
+
# block or a missing file leaves nothing to send.
|
|
23
|
+
def self.for_file(storage, name, type)
|
|
24
|
+
data = storage.read_file(name, type:)
|
|
25
|
+
return new([], error: [Drive::FILE_NOT_FOUND]) unless data
|
|
26
|
+
|
|
27
|
+
failure = storage.respond_to?(:read_error) && storage.read_error(name, type:)
|
|
28
|
+
return new(data) unless failure
|
|
29
|
+
|
|
30
|
+
new(data[0, failure[:offset] - 1] || [], error: failure.values_at(:error, :track, :sector))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def replace(bytes)
|
|
34
|
+
@bytes = bytes
|
|
35
|
+
@pointer = 0
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def exhausted?
|
|
39
|
+
@pointer >= @bytes.length
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def read
|
|
43
|
+
return if exhausted?
|
|
44
|
+
|
|
45
|
+
byte = @bytes[@pointer]
|
|
46
|
+
@pointer += 1
|
|
47
|
+
[byte, exhausted? && !@error]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module KernalTrap
|
|
5
|
+
class Drive
|
|
6
|
+
# The command channel's side of the drive: the last DOS error with the
|
|
7
|
+
# track and sector it happened at, or the bytes an M-R read. Once its
|
|
8
|
+
# last byte has been read, it holds OK again.
|
|
9
|
+
class Status
|
|
10
|
+
MESSAGES = [20, 21, 22, 23, 24, 27].to_h { |code| [code, "READ ERROR"] }.merge(
|
|
11
|
+
0 => " OK",
|
|
12
|
+
25 => "WRITE ERROR",
|
|
13
|
+
26 => "WRITE PROTECT ON",
|
|
14
|
+
28 => "WRITE ERROR",
|
|
15
|
+
29 => "DISK ID MISMATCH",
|
|
16
|
+
30 => "SYNTAX ERROR",
|
|
17
|
+
62 => "FILE NOT FOUND",
|
|
18
|
+
63 => "FILE EXISTS",
|
|
19
|
+
66 => "ILLEGAL TRACK OR SECTOR",
|
|
20
|
+
70 => "NO CHANNEL",
|
|
21
|
+
73 => "CBM DOS V2.6 1541",
|
|
22
|
+
74 => "DRIVE NOT READY"
|
|
23
|
+
).freeze
|
|
24
|
+
|
|
25
|
+
def initialize
|
|
26
|
+
@channel = Channel.new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def report(code, track = 0, sector = 0)
|
|
30
|
+
message = format("%<code>02d,%<message>s,%<track>02d,%<sector>02d\r",
|
|
31
|
+
code:, message: MESSAGES.fetch(code),
|
|
32
|
+
track: track.to_i, sector: sector.to_i)
|
|
33
|
+
@channel.replace(message.bytes)
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def replace(bytes)
|
|
38
|
+
@channel.replace(bytes)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def read
|
|
42
|
+
result = @channel.read
|
|
43
|
+
report(0) if result&.last
|
|
44
|
+
result
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|