badline 0.10.1 → 0.11.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.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/CLAUDE.md +25 -6
  4. data/CONTRIBUTING.md +8 -3
  5. data/README.md +63 -4
  6. data/doc/pinned-behaviour.md +75 -0
  7. data/doc/snapshots.md +15 -0
  8. data/lib/badline/address_bus/pla.rb +3 -16
  9. data/lib/badline/address_bus.rb +14 -0
  10. data/lib/badline/c128/banks.rb +35 -0
  11. data/lib/badline/c128/bus.rb +347 -0
  12. data/lib/badline/c128/bus_state.rb +53 -0
  13. data/lib/badline/c128/color_lines.rb +39 -0
  14. data/lib/badline/c128/cpu.rb +10 -0
  15. data/lib/badline/c128/drives.rb +95 -0
  16. data/lib/badline/c128/fast_serial.rb +62 -0
  17. data/lib/badline/c128/keys.rb +35 -0
  18. data/lib/badline/c128/mmu.rb +197 -0
  19. data/lib/badline/c128/mmu_pages.rb +105 -0
  20. data/lib/badline/c128/model.rb +39 -0
  21. data/lib/badline/c128/modes.rb +48 -0
  22. data/lib/badline/c128/saved_state.rb +121 -0
  23. data/lib/badline/c128/vdc/memory.rb +54 -0
  24. data/lib/badline/c128/vdc/painter.rb +143 -0
  25. data/lib/badline/c128/vdc/raster.rb +97 -0
  26. data/lib/badline/c128/vdc/window.rb +215 -0
  27. data/lib/badline/c128/vdc.rb +268 -0
  28. data/lib/badline/c128.rb +322 -0
  29. data/lib/badline/chrout_trap.rb +3 -0
  30. data/lib/badline/cia/fast_forward.rb +42 -0
  31. data/lib/badline/cia/serial.rb +12 -0
  32. data/lib/badline/cia/timer.rb +39 -0
  33. data/lib/badline/cia.rb +2 -0
  34. data/lib/badline/computer/attachments.rb +10 -13
  35. data/lib/badline/computer/kernal_traps.rb +29 -0
  36. data/lib/badline/computer.rb +5 -0
  37. data/lib/badline/drive/core.rb +172 -0
  38. data/lib/badline/{drive1541 → drive}/idle.rb +18 -19
  39. data/lib/badline/drive/idle_cpu.rb +28 -0
  40. data/lib/badline/{drive1541 → drive}/orbit.rb +2 -1
  41. data/lib/badline/drive/serial_lines.rb +73 -0
  42. data/lib/badline/{drive1541 → drive}/sleep.rb +4 -3
  43. data/lib/badline/drive/watch.rb +150 -0
  44. data/lib/badline/drive.rb +9 -0
  45. data/lib/badline/drive1541/bus.rb +5 -129
  46. data/lib/badline/drive1541/byte_ready.rb +48 -0
  47. data/lib/badline/drive1541/cpu.rb +1 -19
  48. data/lib/badline/drive1541/disk.rb +77 -20
  49. data/lib/badline/drive1541/disk_state.rb +7 -2
  50. data/lib/badline/drive1541/disk_via.rb +3 -0
  51. data/lib/badline/drive1541/mechanism.rb +48 -14
  52. data/lib/badline/drive1541/mechanism_state.rb +5 -4
  53. data/lib/badline/drive1541/rotation.rb +5 -5
  54. data/lib/badline/drive1541/serial_port.rb +4 -60
  55. data/lib/badline/drive1541.rb +13 -172
  56. data/lib/badline/drive1571/bus.rb +111 -0
  57. data/lib/badline/drive1571/cpu.rb +11 -0
  58. data/lib/badline/drive1571/serial_port.rb +37 -0
  59. data/lib/badline/drive1571/serial_via.rb +25 -0
  60. data/lib/badline/drive1571/wd1770.rb +34 -0
  61. data/lib/badline/drive1571.rb +225 -0
  62. data/lib/badline/frontend/app.rb +35 -22
  63. data/lib/badline/frontend/boot.rb +31 -1
  64. data/lib/badline/frontend/controls.rb +11 -0
  65. data/lib/badline/frontend/drive_led.rb +1 -1
  66. data/lib/badline/frontend/keys.rb +28 -1
  67. data/lib/badline/frontend/machine_window.rb +16 -13
  68. data/lib/badline/frontend/menu_media.rb +3 -3
  69. data/lib/badline/frontend/menu_pages.rb +8 -2
  70. data/lib/badline/frontend/menu_ports.rb +1 -1
  71. data/lib/badline/frontend/pause_menu.rb +1 -0
  72. data/lib/badline/frontend/screen.rb +33 -8
  73. data/lib/badline/frontend/screens.rb +90 -0
  74. data/lib/badline/frontend/timeline.rb +15 -4
  75. data/lib/badline/frontend.rb +1 -0
  76. data/lib/badline/iec_bus.rb +54 -4
  77. data/lib/badline/kernal_trap/file.rb +1 -1
  78. data/lib/badline/kernal_trap/layout.rb +58 -8
  79. data/lib/badline/kernal_trap/load.rb +1 -1
  80. data/lib/badline/kernal_trap/save.rb +1 -1
  81. data/lib/badline/kernal_trap/serial.rb +5 -0
  82. data/lib/badline/machine.rb +20 -9
  83. data/lib/badline/media/true_drive.rb +26 -23
  84. data/lib/badline/media.rb +8 -6
  85. data/lib/badline/options/event.rb +5 -1
  86. data/lib/badline/options/family.rb +52 -0
  87. data/lib/badline/options/help.rb +7 -2
  88. data/lib/badline/options/table.rb +8 -2
  89. data/lib/badline/options/validation.rb +14 -1
  90. data/lib/badline/options.rb +20 -19
  91. data/lib/badline/roms/README +36 -0
  92. data/lib/badline/roms/c128/basic.rom +0 -0
  93. data/lib/badline/roms/c128/character.rom +0 -0
  94. data/lib/badline/roms/c128/kernal.rom +0 -0
  95. data/lib/badline/roms/dos1571.rom +0 -0
  96. data/lib/badline/snapshot/container.rb +2 -0
  97. data/lib/badline/snapshot/image.rb +14 -0
  98. data/lib/badline/snapshot.rb +9 -2
  99. data/lib/badline/storage/d64_image.rb +3 -0
  100. data/lib/badline/storage/d71_image.rb +9 -4
  101. data/lib/badline/storage/g64_image.rb +7 -2
  102. data/lib/badline/time_of_day.rb +9 -0
  103. data/lib/badline/version.rb +1 -1
  104. data/lib/badline/via/fast_forward.rb +3 -3
  105. data/lib/badline/vic/display_state.rb +12 -0
  106. data/lib/badline/vic/registers.rb +3 -0
  107. data/lib/badline/vic.rb +108 -7
  108. data/lib/badline/vic20/attachments.rb +6 -0
  109. data/lib/badline/z80/arithmetic.rb +117 -0
  110. data/lib/badline/z80/bit_page.rb +55 -0
  111. data/lib/badline/z80/block_io.rb +58 -0
  112. data/lib/badline/z80/block_transfers.rb +59 -0
  113. data/lib/badline/z80/bus_cycles.rb +111 -0
  114. data/lib/badline/z80/core.rb +111 -0
  115. data/lib/badline/z80/exchanges.rb +50 -0
  116. data/lib/badline/z80/extended_page.rb +89 -0
  117. data/lib/badline/z80/flags.rb +37 -0
  118. data/lib/badline/z80/interrupt_acceptance.rb +46 -0
  119. data/lib/badline/z80/jumps.rb +82 -0
  120. data/lib/badline/z80/loads.rb +98 -0
  121. data/lib/badline/z80/main_page.rb +134 -0
  122. data/lib/badline/z80/registers.rb +120 -0
  123. data/lib/badline/z80/shifts.rb +64 -0
  124. data/lib/badline/z80.rb +11 -0
  125. data/lib/badline.rb +1 -0
  126. metadata +59 -4
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ # The 8722 MMU's registers, $D500-$D50B in C128 mode:
6
+ #
7
+ # $D500 CR: I/O, the ROMs and the RAM bank the CPU sees
8
+ # $D501-$D504 PCRA-PCRD: preset configurations
9
+ # $D505 MCR: the CPU (bit 0, 1 for the 8502), FSDIR, GAME and EXROM
10
+ # in, C64 mode (bit 6) and the 40/80 key
11
+ # $D506 RCR: common RAM and the VIC's RAM bank
12
+ # $D507-$D50A P0L, P0H, P1L, P1H: page 0 and page 1 relocation
13
+ # $D50B VR: the version, 2 banks of version 0
14
+ #
15
+ # The rest of the page reads $FF. $FF00 is CR again, and a write to
16
+ # $FF01-$FF04, the LCRs, copies PCRA-PCRD into CR. Those five answer in
17
+ # every configuration, whatever CR maps there.
18
+ #
19
+ # A write to P0H or P1H waits until the matching P0L or P1L write
20
+ # takes it.
21
+ #
22
+ # On the C128 the Z80 runs first and hands the bus to the 8502. Here the
23
+ # 8502 runs from reset, with MCR as the Z80 leaves it. A machine built
24
+ # for C64 mode resets into C64 mode, the state the C128 KERNAL leaves
25
+ # when C= is held at power-on. In C64 mode the MMU answers nowhere, but
26
+ # the CPU's RAM bank, common RAM and the VIC's bank it holds stay in
27
+ # force.
28
+ class MMU
29
+ CR = 0
30
+ MCR = 5
31
+ RCR = 6
32
+ P0L = 7
33
+ P0H = 8
34
+ P1L = 9
35
+ P1H = 10
36
+ VR = 11
37
+
38
+ MCR_8502 = 0x01
39
+ MCR_FSDIR = 0x08
40
+ MCR_C64_MODE = 0x40
41
+ VERSION = 0x20
42
+
43
+ # MCR's bits that read back what was written: the CPU, FSDIR and C64
44
+ # mode. Bits 1 and 2 have no pins and read 1.
45
+ MCR_WRITABLE = 0x49
46
+ MCR_UNUSED = 0x06
47
+ MCR_GAME = 0x10
48
+ MCR_EXROM = 0x20
49
+ MCR_DISPLAY_KEY = 0x80
50
+
51
+ # The common RAM sizes RCR bits 0-1 pick, in pages.
52
+ COMMON_PAGES = [4, 16, 32, 64].freeze
53
+
54
+ # The registers, CR first.
55
+ attr_reader :registers
56
+
57
+ # Whether the 40/80 DISPLAY key is locked down, which MCR bit 7 reads
58
+ # as 0 in C128 mode.
59
+ attr_accessor :display_key
60
+
61
+ # The cartridge's GAME and EXROM lines, which MCR bits 4 and 5 read.
62
+ attr_writer :game, :exrom
63
+
64
+ # +mode+ is the mode a reset leaves, :c128 or :c64.
65
+ def initialize(mode = :c64)
66
+ @reset_mode = mode
67
+ @registers = Array.new(12, 0)
68
+ @display_key = false
69
+ @game = 1
70
+ @exrom = 1
71
+ @on_change = nil
72
+ @on_fast_serial_change = nil
73
+ reset!
74
+ end
75
+
76
+ # Calls the block whenever the mapping the registers ask for changes.
77
+ def on_change(&block)
78
+ @on_change = block
79
+ end
80
+
81
+ # Calls the block whenever FSDIR turns the fast serial buffers round.
82
+ def on_fast_serial_change(&block)
83
+ @on_fast_serial_change = block
84
+ end
85
+
86
+ def reset!
87
+ fast_serial_out = fast_serial_out?
88
+ @registers.fill(0)
89
+ @registers[MCR] = @reset_mode == :c64 ? MCR_C64_MODE | MCR_8502 : MCR_8502
90
+ @registers[P1L] = 0x01
91
+ @registers[VR] = VERSION
92
+ @p0h_latch = 0
93
+ @p1h_latch = 0
94
+ @on_change&.call
95
+ @on_fast_serial_change&.call if fast_serial_out
96
+ end
97
+
98
+ def c64_mode? = @registers[MCR].anybits?(MCR_C64_MODE)
99
+
100
+ # Whether MCR bit 3, FSDIR, turns the fast serial buffers outwards, so
101
+ # CIA 1's CNT and SP drive SRQ and DATA.
102
+ def fast_serial_out? = @registers[MCR].anybits?(MCR_FSDIR)
103
+
104
+ # :c64 or :c128, the mode MCR bit 6 selects.
105
+ def mode = c64_mode? ? :c64 : :c128
106
+
107
+ # The 64K bank the VIC sees, RCR bit 6. Bit 7 picks banks a 256K
108
+ # machine has.
109
+ def vic_bank = (@registers[RCR] >> 6) & 0x01
110
+
111
+ # The RAM bank the CPU sees, CR bit 6.
112
+ def cpu_bank = (@registers[CR] >> 6) & 0x01
113
+
114
+ def cr = @registers[CR]
115
+
116
+ # The pages from $0000 up that common RAM keeps in bank 0, or 0.
117
+ def common_low_pages = @registers[RCR].anybits?(0x04) ? common_pages : 0
118
+
119
+ # The first page of the common RAM below $FFFF, or 256 for none.
120
+ def common_high_start = @registers[RCR].anybits?(0x08) ? 256 - common_pages : 256
121
+
122
+ # The page and the bank page 0 moves to.
123
+ def p0_page = @registers[P0L]
124
+ def p0_bank = @registers[P0H] & 0x01
125
+
126
+ # The page and the bank page 1 moves to.
127
+ def p1_page = @registers[P1L]
128
+ def p1_bank = @registers[P1H] & 0x01
129
+
130
+ # The registers at $D500-$D5FF.
131
+ def peek(addr)
132
+ offset = addr & 0xff
133
+ case offset
134
+ when MCR then mcr
135
+ when P0H, P1H then 0xf0 | @registers[offset]
136
+ when 0..VR then @registers[offset]
137
+ else 0xff
138
+ end
139
+ end
140
+
141
+ def poke(addr, value)
142
+ offset = addr & 0xff
143
+ case offset
144
+ when P0H then @p0h_latch = value & 0x0f
145
+ when P1H then @p1h_latch = value & 0x0f
146
+ when P0L then relocate(P0L, P0H, @p0h_latch, value)
147
+ when P1L then relocate(P1L, P1H, @p1h_latch, value)
148
+ when CR..RCR then write(offset, value)
149
+ end
150
+ end
151
+
152
+ # The registers and the P0H and P1H writes waiting for P0L and P1L.
153
+ # The bus maps the restored registers once its own state is back.
154
+ def save_state(out)
155
+ out.ints(@registers).int(@p0h_latch).int(@p1h_latch)
156
+ end
157
+
158
+ def load_state(input)
159
+ input.ints_into(@registers)
160
+ @p0h_latch = input.int
161
+ @p1h_latch = input.int
162
+ end
163
+
164
+ # CR and the LCRs at $FF00-$FF04: an LCR reads its PCR.
165
+ def peek_configuration(addr) = @registers[addr & 0x07]
166
+
167
+ def poke_configuration(addr, value)
168
+ offset = addr & 0x07
169
+ write(CR, offset.zero? ? value : @registers[offset])
170
+ end
171
+
172
+ private
173
+
174
+ def common_pages = COMMON_PAGES[@registers[RCR] & 0x03]
175
+
176
+ def mcr
177
+ value = (@registers[MCR] & MCR_WRITABLE) | MCR_UNUSED
178
+ value |= MCR_GAME if @game == 1
179
+ value |= MCR_EXROM if @exrom == 1
180
+ value |= MCR_DISPLAY_KEY unless @display_key
181
+ value
182
+ end
183
+
184
+ def relocate(low, high, latch, value)
185
+ @registers[high] = latch
186
+ write(low, value)
187
+ end
188
+
189
+ def write(offset, value)
190
+ fast_serial_out = fast_serial_out?
191
+ @registers[offset] = value
192
+ @on_change&.call
193
+ @on_fast_serial_change&.call if fast_serial_out? != fast_serial_out
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ class Bus
6
+ # What the MMU maps. For the CPU: the RAM bank, common RAM and the
7
+ # pages page 0 and page 1 move to in both modes, and CR's ROMs and I/O
8
+ # in C128 mode. C64 mode lays the PLA's ROMs and I/O over the same RAM
9
+ # (AddressBus::PLA).
10
+ module MMUPages
11
+ private
12
+
13
+ # CR's ROMs and I/O over the RAM, with the configuration registers on
14
+ # top at $FF00.
15
+ def map_c128_pages
16
+ cr = @mmu.cr
17
+ map_ram_pages
18
+ @read_pages.fill(@basic_low_rom, 0x40, 0x40) if cr.nobits?(0x02)
19
+ map_rom_block((cr >> 2) & 0x03, 0x80)
20
+ map_rom_block((cr >> 4) & 0x03, 0xc0)
21
+ map_io_pages if cr.nobits?(0x01)
22
+ @configuration_reads.below = @read_pages[0xff]
23
+ @configuration_writes.below = @write_pages[0xff]
24
+ @read_pages[0xff] = @configuration_reads
25
+ @write_pages[0xff] = @configuration_writes
26
+ end
27
+
28
+ # A 16K block's two CR bits pick the system ROM, the internal or the
29
+ # external function ROM, neither of which is fitted, or RAM.
30
+ def map_rom_block(select, first_page)
31
+ case select
32
+ when 0 then map_system_rom(first_page)
33
+ when 1, 2 then @read_pages.fill(@open_bus, first_page, 0x40)
34
+ end
35
+ end
36
+
37
+ # BASIC's high half at $8000, or the screen editor, the character ROM
38
+ # and the KERNAL at $C000.
39
+ def map_system_rom(first_page)
40
+ if first_page == 0x80
41
+ @read_pages.fill(@basic_high_rom, 0x80, 0x40)
42
+ else
43
+ @read_pages.fill(@editor_rom, 0xc0, 0x10)
44
+ @read_pages.fill(@c128_character_rom, 0xd0, 0x10)
45
+ @read_pages.fill(@c128_kernal_rom, 0xe0, 0x20)
46
+ end
47
+ end
48
+
49
+ # The bank CR picks, with common RAM in bank 0, then the pages page 0
50
+ # and page 1 move to.
51
+ def map_ram_pages
52
+ @io_mapped = false
53
+ bank_ram = @mmu.cpu_bank.zero? ? @ram : @bank1
54
+ @read_pages.fill(bank_ram)
55
+ @write_pages.fill(bank_ram)
56
+ map_common_ram
57
+ relocate_pages
58
+ end
59
+
60
+ def map_common_ram
61
+ low = @mmu.common_low_pages
62
+ high = @mmu.common_high_start
63
+ @read_pages.fill(@ram, 0, low)
64
+ @write_pages.fill(@ram, 0, low)
65
+ @read_pages.fill(@ram, high, 256 - high)
66
+ @write_pages.fill(@ram, high, 256 - high)
67
+ end
68
+
69
+ # In C128 mode page 0 and page 1 go to the pages P0 and P1 name, in
70
+ # bank 0 if common RAM holds them. In both modes, the page each moved
71
+ # to reaches back to page 0 or page 1, in the bank it named, if the
72
+ # CPU sees that bank there. Page 1's takes the page they both name.
73
+ # C64 mode moves neither page 0 nor page 1 forward, so either can be
74
+ # the page that reaches back.
75
+ def relocate_pages
76
+ mmu = @mmu
77
+ forward = !mmu.c64_mode?
78
+ if forward
79
+ map_relocated(0, @relocated[0], bank_of(0, mmu.p0_bank), mmu.p0_page)
80
+ map_relocated(1, @relocated[1], bank_of(1, mmu.p1_bank), mmu.p1_page)
81
+ end
82
+ reach_back(@relocated[2], mmu.p0_page, mmu.p0_bank, 0, forward)
83
+ reach_back(@relocated[3], mmu.p1_page, mmu.p1_bank, 1, forward)
84
+ end
85
+
86
+ def reach_back(window, page, bank, home, forward)
87
+ return if forward && page < 2
88
+ return unless bank_of(page, @mmu.cpu_bank) == bank
89
+
90
+ map_relocated(page, window, bank, home)
91
+ end
92
+
93
+ def map_relocated(page, window, bank, target)
94
+ window.base = (bank << 16) | (target << 8)
95
+ @read_pages[page] = @write_pages[page] = window
96
+ end
97
+
98
+ # The bank +page+ reaches when the CPU or a pointer asks for +bank+.
99
+ def bank_of(page, bank)
100
+ page < @mmu.common_low_pages || page >= @mmu.common_high_start ? 0 : bank
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ # The C128s badline builds by name: the chips each board has, and its
6
+ # video standard. The plastic C128D is a "c128" board with a 1571 built
7
+ # in. A VDC on either board can have 16K or 64K of RAM.
8
+ module Model
9
+ Profile = Data.define(:name, :vic_model, :cia_model, :sid_model, :region, :vdc_model, :vdc_ram_kb)
10
+
11
+ # The C128 and the plastic C128D, PAL: the 8566 VIC-IIe, 6526 CIAs,
12
+ # the 6581 SID and the 8563 VDC with 16K.
13
+ C128 = Profile.new(name: "c128", vic_model: :mos8566, cia_model: :mos6526, sid_model: :mos6581,
14
+ region: Region::PAL, vdc_model: :mos8563, vdc_ram_kb: 16)
15
+
16
+ # The NTSC C128, with the 8564.
17
+ C128_NTSC = Profile.new(name: "c128ntsc", vic_model: :mos8564, cia_model: :mos6526, sid_model: :mos6581,
18
+ region: Region::NTSC, vdc_model: :mos8563, vdc_ram_kb: 16)
19
+
20
+ # The metal C128DCR, PAL: 6526A CIAs, the 8580 SID and the 8568 VDC
21
+ # with 64K.
22
+ DCR = Profile.new(name: "c128dcr", vic_model: :mos8566, cia_model: :mos6526a, sid_model: :mos8580,
23
+ region: Region::PAL, vdc_model: :mos8568, vdc_ram_kb: 64)
24
+
25
+ # The NTSC C128DCR, with the 8564.
26
+ DCR_NTSC = Profile.new(name: "c128dcrntsc", vic_model: :mos8564, cia_model: :mos6526a, sid_model: :mos8580,
27
+ region: Region::NTSC, vdc_model: :mos8568, vdc_ram_kb: 64)
28
+
29
+ ALL = [C128, C128_NTSC, DCR, DCR_NTSC].freeze
30
+
31
+ def self.named(name)
32
+ model = ALL.find { |candidate| candidate.name == name }
33
+ raise ArgumentError, "no C128 model named #{name}" if model.nil?
34
+
35
+ model
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ # Following the MMU between C128 and C64 mode: the KERNAL traps go to
6
+ # the KERNAL the machine runs, C= held from power-on starts it in C64
7
+ # mode through the C128 KERNAL, and #active_screen tells a front end
8
+ # which screen the mode's editor prints to.
9
+ module Modes
10
+ # Holds C= down from power-on until the C128 KERNAL has taken the
11
+ # machine to C64 mode, as a user does to start a C128 in C64 mode.
12
+ # #on_init's handlers then wait for the C64 KERNAL's boot.
13
+ def hold_commodore_key
14
+ keyboard.press(:cbm)
15
+ @holding_commodore = true
16
+ end
17
+
18
+ # The screen the machine prints to: in C128 mode the screen editor's,
19
+ # :vdc while bit 7 of its 40/80 flag at $D7 is set, as GRAPHIC 5,
20
+ # ESC X and a reset with 40/80 DISPLAY down leave it, or :vic. C64
21
+ # mode has the VIC-IIe's alone.
22
+ def active_screen = mode == :c128 && ram.peek(0xd7).anybits?(0x80) ? :vdc : :vic
23
+
24
+ private
25
+
26
+ # The KERNAL the traps stand in for: the C128's in C128 mode, the C64's
27
+ # in C64 mode.
28
+ def trap_layout = mode == :c128 ? KernalTrap::C128_LAYOUT : KernalTrap::C64_LAYOUT
29
+
30
+ # The machine went to C64 mode or came back: the traps move to the
31
+ # KERNAL it now runs. C= held from power-on lets go, and #on_init's
32
+ # handlers wait for the C64 KERNAL to boot.
33
+ def mode_changed
34
+ layout = trap_layout
35
+ @capture_output&.layout = layout
36
+ if @drive
37
+ remove_kernal_traps
38
+ install_kernal_traps(layout)
39
+ end
40
+ return unless @holding_commodore && mode == :c64
41
+
42
+ keyboard.release(:cbm)
43
+ @holding_commodore = false
44
+ @init_threshold = @cycles + Computer::INIT_THRESHOLD if @cycles < @init_threshold
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ MARKER = "C128"
6
+
7
+ # The model, the SID and the mode it resets into of the machine a State
8
+ # from #snapshot was taken of, the arguments C128.new takes.
9
+ def self.setup(state)
10
+ input = Snapshot::StateReader.new(state)
11
+ input.marker(MARKER)
12
+ input.check_stamp
13
+ SavedState.read_setup(input)
14
+ end
15
+
16
+ # A new machine, built as the one a State from #snapshot was taken of,
17
+ # at that state.
18
+ def self.restored(state)
19
+ setup = setup(state)
20
+ new(model: setup[0], sid_model: setup[1], mode: setup[2]).apply_state(state)
21
+ end
22
+
23
+ # The whole machine's state for a snapshot, as Computer's: the
24
+ # machine's own latches, FAST and TEST as they last took hold, the bus with its RAM, the MMU, the chips and
25
+ # the VDC, the CPU, the cartridge, the disk device 8 serves through the
26
+ # traps and a true 1541. The serial lines go back into the bus from the
27
+ # restored CIA 2. What the host holds stays out: the keyboard, the
28
+ # joysticks, CAPS LOCK and 40/80 DISPLAY, the traps and callbacks, and
29
+ # which chip renders.
30
+ module SavedState
31
+ SID_MODELS = %i[mos6581 mos8580].freeze
32
+ MODES = %i[c64 c128].freeze
33
+
34
+ # The model's name, the SID's model and the mode.
35
+ def self.read_setup(input)
36
+ name = input.string
37
+ known = Model::ALL.any? { |model| model.name == name }
38
+ raise Snapshot::FormatError, "the state names a C128 model badline doesn't know" unless known
39
+
40
+ sid_model = SID_MODELS.fetch(input.int)
41
+ [name, sid_model, MODES.fetch(input.int)]
42
+ rescue IndexError
43
+ raise Snapshot::FormatError, "the state names a SID or a mode badline doesn't know"
44
+ end
45
+
46
+ # How many more on_init handlers the machine a State was taken of had
47
+ # yet to run than this one has. Nil until the machine is restored.
48
+ attr_reader :init_handlers_lost
49
+
50
+ def snapshot
51
+ out = Snapshot::StateWriter.new
52
+ save_state(out)
53
+ out.state
54
+ end
55
+
56
+ # Takes the machine back to a State from #snapshot, trying it on a new
57
+ # machine first, so one that fails leaves this machine as it was.
58
+ # Raises Snapshot::FormatError for a state of another model or SID.
59
+ def restore(state)
60
+ check_setup(C128.setup(state))
61
+ C128.restored(state)
62
+ apply_state(state)
63
+ end
64
+
65
+ # Puts a State into the machine without trying it on a new one first.
66
+ def apply_state(state)
67
+ input = Snapshot::StateReader.new(state)
68
+ load_state(input)
69
+ raise Snapshot::FormatError, "the state goes on past the machine" unless input.finished?
70
+
71
+ self
72
+ end
73
+
74
+ def save_state(out)
75
+ out.marker(MARKER).stamp
76
+ out.string(@model.name).int(SID_MODELS.index(@sid.model)).int(@c64_built ? 0 : 1)
77
+ out.int(@cycles).int(@clock_bits).boolean(@nmi_asserted).boolean(@cartridge_nmi).boolean(@restore_pulse)
78
+ out.boolean(!@pending_keys.nil?)
79
+ out.ints(@pending_keys) if @pending_keys
80
+ out.int(@cycles <= init_threshold ? @init_handlers.length : 0)
81
+ save_cartridge(out)
82
+ @bus.save_state(out)
83
+ @cpu.save_state(out)
84
+ save_trap_drive(out)
85
+ save_serial_bus(out)
86
+ end
87
+
88
+ def load_state(input)
89
+ input.marker(MARKER)
90
+ input.check_stamp
91
+ check_setup(SavedState.read_setup(input))
92
+ @cycles = input.int
93
+ @clock_bits = input.int
94
+ @nmi_asserted = input.boolean?
95
+ @cartridge_nmi = input.boolean?
96
+ @restore_pulse = input.boolean?
97
+ @pending_keys = nil
98
+ @pending_keys = input.ints if input.boolean?
99
+ @init_handlers_lost = [input.int - @init_handlers.length, 0].max
100
+ load_cartridge(input)
101
+ @bus.load_state(input)
102
+ push_serial_lines
103
+ @cpu.load_state(input)
104
+ load_trap_drive(input)
105
+ load_serial_bus(input)
106
+ push_fast_serial
107
+ end
108
+
109
+ private
110
+
111
+ def check_setup(setup)
112
+ ours = [@model.name, @sid.model, @c64_built ? :c64 : :c128]
113
+ return if setup == ours
114
+
115
+ raise Snapshot::FormatError, "the state is of a #{setup.join(' with a ')}, not a #{ours.join(' with a ')}"
116
+ end
117
+ end
118
+
119
+ include SavedState
120
+ end
121
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ class VDC
6
+ # The VDC's own DRAM: 16K of two 4416s (16K by 4) or 64K of 4164s or
7
+ # 4464s, behind the 16-bit addresses the VDC computes.
8
+ #
9
+ # The VDC drives an address onto the DRAM's pins as a row and a
10
+ # column, and R28 bit 4 picks the order (Programmer's Reference Guide,
11
+ # R28): set, for 64K parts, the row is A0-A7 and the column A8-A15;
12
+ # clear, for 4416s, the column pins carry A8, A8, A9-A13 and A15, and
13
+ # A14 goes nowhere. A 4416 has only six column lines and ignores the
14
+ # first and the last column pin. That gives the four layouts
15
+ # VDC/vdcdump's patterns.txt records from real machines: a 16K VDC in
16
+ # 64K mode sees A9-A14, so pages p, p ^ 1 and p ^ $80 are one page,
17
+ # and a 64K VDC in 16K mode sees page p at page
18
+ # (p & $80) | (p & $3E) << 1 | (p & 1) * 3.
19
+ class Memory
20
+ attr_reader :ram
21
+
22
+ def initialize(ram_kb)
23
+ @ram = Array.new(ram_kb * 1024, 0)
24
+ @layouts = [false, true].map { |mode64| Array.new(256) { |page| page_base(page, mode64) }.freeze }
25
+ @pages = @layouts[0]
26
+ end
27
+
28
+ # Selects 64K mode, R28 bit 4, or 16K mode.
29
+ def mode64=(on)
30
+ @pages = @layouts[on ? 1 : 0]
31
+ end
32
+
33
+ def fetch(addr) = @ram[@pages[(addr >> 8) & 0xff] | (addr & 0xff)]
34
+
35
+ def store(addr, value)
36
+ @ram[@pages[(addr >> 8) & 0xff] | (addr & 0xff)] = value
37
+ end
38
+
39
+ def clear!
40
+ @ram.fill(0)
41
+ end
42
+
43
+ private
44
+
45
+ def page_base(page, mode64)
46
+ pins = mode64 ? page : (page & 0x80) | ((page & 0x3e) << 1) | ((page & 1) * 3)
47
+ return pins << 8 if @ram.length == 0x10000
48
+
49
+ ((pins >> 1) & 0x3f) << 8
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end