badline 0.16.1 → 0.17.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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -0
  3. data/CLAUDE.md +1 -1
  4. data/README.md +102 -12
  5. data/doc/media.md +5 -2
  6. data/doc/pinned-behaviour.md +61 -0
  7. data/doc/snapshot-format.md +376 -69
  8. data/doc/snapshots.md +148 -14
  9. data/lib/badline/cbm2/acia.rb +10 -0
  10. data/lib/badline/cbm2/bus.rb +263 -0
  11. data/lib/badline/cbm2/cpu.rb +58 -0
  12. data/lib/badline/cbm2/crtc_video.rb +133 -0
  13. data/lib/badline/cbm2/ieee_drive.rb +18 -0
  14. data/lib/badline/cbm2/ieee_layout.rb +69 -0
  15. data/lib/badline/cbm2/ieee_ports.rb +104 -0
  16. data/lib/badline/cbm2/ieee_traps.rb +81 -0
  17. data/lib/badline/cbm2/keyboard_ports.rb +60 -0
  18. data/lib/badline/cbm2/keyed_modules.rb +67 -0
  19. data/lib/badline/cbm2/keyed_state.rb +91 -0
  20. data/lib/badline/cbm2/model.rb +44 -0
  21. data/lib/badline/cbm2/p500/control_ports.rb +80 -0
  22. data/lib/badline/cbm2/p500/ieee_layout.rb +41 -0
  23. data/lib/badline/cbm2/p500/keyed_state.rb +85 -0
  24. data/lib/badline/cbm2/p500/model.rb +39 -0
  25. data/lib/badline/cbm2/p500/pages.rb +57 -0
  26. data/lib/badline/cbm2/p500.rb +262 -0
  27. data/lib/badline/cbm2/sid_page.rb +19 -0
  28. data/lib/badline/cbm2.rb +267 -0
  29. data/lib/badline/{plus4 → chips}/acia/frames.rb +1 -1
  30. data/lib/badline/chips/acia/keyed_state.rb +47 -0
  31. data/lib/badline/chips/acia.rb +307 -0
  32. data/lib/badline/chips/crtc.rb +3 -2
  33. data/lib/badline/chips/riot/fast_forward.rb +72 -0
  34. data/lib/badline/chips/riot/keyed_parts.rb +69 -0
  35. data/lib/badline/chips/riot/timer.rb +80 -0
  36. data/lib/badline/chips/riot.rb +162 -0
  37. data/lib/badline/chips/rriot/keyed_state.rb +30 -0
  38. data/lib/badline/chips/rriot.rb +132 -0
  39. data/lib/badline/chips/tpi/keyed_state.rb +38 -0
  40. data/lib/badline/chips/tpi.rb +233 -0
  41. data/lib/badline/chips/vic/bank.rb +60 -5
  42. data/lib/badline/chips/vic/sprite.rb +4 -3
  43. data/lib/badline/drives/datasette/keyed_state.rb +9 -1
  44. data/lib/badline/drives/datasette.rb +27 -3
  45. data/lib/badline/drives/model8050/disk.rb +163 -0
  46. data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
  47. data/lib/badline/drives/model8050/idle.rb +177 -0
  48. data/lib/badline/drives/model8050/ieee_port.rb +147 -0
  49. data/lib/badline/drives/model8050/ip_bus.rb +93 -0
  50. data/lib/badline/drives/model8050/keyed_state.rb +160 -0
  51. data/lib/badline/drives/model8050/mechanism.rb +230 -0
  52. data/lib/badline/drives/model8050/parts.rb +37 -0
  53. data/lib/badline/drives/model8050.rb +243 -0
  54. data/lib/badline/frontend/app.rb +10 -3
  55. data/lib/badline/frontend/boot.rb +1 -1
  56. data/lib/badline/frontend/controls.rb +51 -15
  57. data/lib/badline/frontend/gamepads.rb +3 -3
  58. data/lib/badline/frontend/keys.rb +61 -2
  59. data/lib/badline/frontend/media_slots.rb +1 -1
  60. data/lib/badline/frontend/menu/menu_media.rb +14 -11
  61. data/lib/badline/frontend/menu/menu_pages.rb +18 -4
  62. data/lib/badline/frontend/menu/menu_ports.rb +40 -4
  63. data/lib/badline/frontend/menu/pause_menu.rb +1 -1
  64. data/lib/badline/frontend/pet_keyboard.rb +23 -7
  65. data/lib/badline/frontend/timeline.rb +19 -10
  66. data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
  67. data/lib/badline/kernal_trap/dos/status.rb +3 -0
  68. data/lib/badline/kernal_trap/dos.rb +18 -5
  69. data/lib/badline/kernal_trap/ieee.rb +4 -2
  70. data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
  71. data/lib/badline/machine/ieee_drives.rb +194 -0
  72. data/lib/badline/machine/settling.rb +4 -0
  73. data/lib/badline/machine.rb +23 -6
  74. data/lib/badline/media/cbm2_media.rb +29 -0
  75. data/lib/badline/media/program.rb +6 -3
  76. data/lib/badline/media/true_drive.rb +38 -9
  77. data/lib/badline/media.rb +15 -6
  78. data/lib/badline/options/family.rb +15 -3
  79. data/lib/badline/options/help.rb +16 -2
  80. data/lib/badline/options/table.rb +18 -4
  81. data/lib/badline/options/validation.rb +27 -3
  82. data/lib/badline/options.rb +21 -7
  83. data/lib/badline/pet/cassettes.rb +22 -1
  84. data/lib/badline/pet/ieee_drive.rb +5 -64
  85. data/lib/badline/pet/ieee_ports.rb +7 -0
  86. data/lib/badline/pet.rb +15 -4
  87. data/lib/badline/plus4/acia.rb +4 -298
  88. data/lib/badline/plus4/keyed_parts.rb +0 -44
  89. data/lib/badline/plus4.rb +1 -0
  90. data/lib/badline/roms/8050/dos-2.7.rom +0 -0
  91. data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
  92. data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
  93. data/lib/badline/roms/README +111 -0
  94. data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
  95. data/lib/badline/roms/cbm2/basic128.rom +0 -0
  96. data/lib/badline/roms/cbm2/basic256.rom +0 -0
  97. data/lib/badline/roms/cbm2/characters-600.rom +0 -0
  98. data/lib/badline/roms/cbm2/characters-700.rom +0 -0
  99. data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
  100. data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
  101. data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
  102. data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
  103. data/lib/badline/snapshot/cbm2_setup.rb +53 -0
  104. data/lib/badline/snapshot/container.rb +7 -2
  105. data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
  106. data/lib/badline/snapshot/field_doc.rb +38 -3
  107. data/lib/badline/snapshot/image.rb +62 -9
  108. data/lib/badline/snapshot/vice/cbm2.rb +93 -0
  109. data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
  110. data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
  111. data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
  112. data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
  113. data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
  114. data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
  115. data/lib/badline/snapshot/vice/cias.rb +13 -9
  116. data/lib/badline/snapshot/vice/p500.rb +96 -0
  117. data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
  118. data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
  119. data/lib/badline/snapshot/vice/pet.rb +7 -3
  120. data/lib/badline/snapshot/vice/pet_cpu.rb +5 -2
  121. data/lib/badline/snapshot/vice/pet_crtc.rb +10 -15
  122. data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
  123. data/lib/badline/snapshot/vice/pet_drive8050.rb +216 -0
  124. data/lib/badline/snapshot/vice/pet_restore.rb +70 -6
  125. data/lib/badline/snapshot/vice/pet_via.rb +6 -5
  126. data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
  127. data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
  128. data/lib/badline/snapshot/vice.rb +3 -0
  129. data/lib/badline/snapshot.rb +11 -3
  130. data/lib/badline/storage/crt_file.rb +7 -1
  131. data/lib/badline/storage/d64_image.rb +7 -0
  132. data/lib/badline/storage/d80_image.rb +17 -1
  133. data/lib/badline/storage/d81_image.rb +14 -0
  134. data/lib/badline/storage/disk_image/bam.rb +18 -3
  135. data/lib/badline/storage/disk_image/formatting.rb +69 -0
  136. data/lib/badline/storage/disk_image/writing.rb +35 -0
  137. data/lib/badline/storage/disk_image.rb +2 -0
  138. data/lib/badline/storage.rb +1 -0
  139. data/lib/badline/support/cbm2_checkpoint.rb +48 -0
  140. data/lib/badline/support/p500_checkpoint.rb +35 -0
  141. data/lib/badline/version.rb +1 -1
  142. data/lib/badline.rb +1 -0
  143. metadata +72 -2
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Vice
6
+ module CBM2
7
+ # Restores a CBM-II from a snapshot xcbm2 wrote. The modules badline
8
+ # reads become the machine's own keyed modules (Keyed), which it
9
+ # loads as it loads its own snapshot: the CIA and the SID are read
10
+ # into a machine of their own first, as the C64's are, and saved
11
+ # from there. Whatever they don't set stays as the machine has it
12
+ # at power-on, and the disk device 8 serves through the traps stays
13
+ # as it is.
14
+ module Restore
15
+ # MAINCPU and CBM2MEM hold the machine itself, so a version badline
16
+ # doesn't read fails the restore. The others are left out and
17
+ # reported.
18
+ READ = [MainCPU::NAME, CBM2Mem::NAME, CRTC::NAME, "CIA1", *TPI::NAMES, ACIA::NAME, SIDRegisters::NAME,
19
+ SIDExtended::NAME].freeze
20
+ REQUIRED = [MainCPU::NAME, CBM2Mem::NAME].freeze
21
+ # TPI1's inputs: the mains on I0, the CIA on I2 and the ACIA on I4.
22
+ MAINS = 0x01
23
+ CIA = 0x04
24
+ ACIA_LINE = 0x10
25
+ # I1, the IEEE-488 bus's SRQ, and I3, the coprocessor's, which
26
+ # nothing pulls.
27
+ IDLE_LINES = 0x0a
28
+ # The modules read into a machine of their own first.
29
+ CHIPS = %w[BL-CIA BL-SID].freeze
30
+
31
+ module_function
32
+
33
+ # Applies the modules badline reads to `computer`, and returns a
34
+ # Report. The snapshot goes into a new machine first, so one that
35
+ # fails leaves `computer` as it was, and one of a machine built
36
+ # another way fails too.
37
+ def import(container, computer)
38
+ unless computer.family == :cbm2
39
+ raise FormatError, "the snapshot is of a CBM-II, not a #{Image::FAMILIES.fetch(computer.family.to_s)}"
40
+ end
41
+
42
+ setup = CBM2.setup(container)
43
+ ours = CBM2Setup.of(computer)
44
+ raise FormatError, "the snapshot is of a #{setup}, not a #{ours}" unless setup == ours
45
+
46
+ apply(container, setup.build)
47
+ reapply(computer) { apply(container, computer) }
48
+ end
49
+
50
+ # Switches `computer` off and on and applies the snapshot with the
51
+ # block, then reports the disk the traps keep serving and a true
52
+ # drive taken off the bus.
53
+ def reapply(computer)
54
+ had_drive = computer.true_drive
55
+ computer.power_cycle!
56
+ report = yield
57
+ kept = computer.mounted? ? ["the disk mounted in device 8 stays mounted"] : []
58
+ if had_drive && !computer.true_drive.equal?(had_drive)
59
+ kept << "the #{had_drive.model_name} on device #{had_drive.device} is taken off the bus"
60
+ end
61
+ Report.new(applied: report.applied, ignored: report.ignored + kept)
62
+ end
63
+
64
+ # Applies the modules badline reads to `computer`, a machine built
65
+ # as the snapshot's was, and returns the Report.
66
+ def apply(container, computer)
67
+ raise FormatError, "the snapshot has no #{MainCPU::NAME} module" if container[MainCPU::NAME].nil?
68
+
69
+ applied = []
70
+ ignored = []
71
+ READ.each do |name|
72
+ section = container[name]
73
+ next if section.nil?
74
+
75
+ reason = refusal(section, container)
76
+ if reason.nil?
77
+ applied << name
78
+ else
79
+ raise FormatError, "#{section}: #{reason}" if REQUIRED.include?(name)
80
+
81
+ ignored << "#{section}: #{reason}, left out"
82
+ end
83
+ end
84
+ computer.load_modules(Keyed::Modules.new(modules(container, computer, applied, ignored)))
85
+ Report.new(applied:, ignored: ignored + left_out(container, applied))
86
+ end
87
+
88
+ # Why badline leaves the module out, or nil when it reads it.
89
+ def refusal(section, container)
90
+ return "badline doesn't read this version" unless reads?(section)
91
+
92
+ "another SID engine wrote it" if section.name == SIDExtended::NAME && !Vice::Restore.resid?(container)
93
+ end
94
+
95
+ def reads?(section)
96
+ case section.name
97
+ when MainCPU::NAME then PET::MainCPU.reads?(section)
98
+ when CBM2Mem::NAME then CBM2Mem.reads?(section)
99
+ when CRTC::NAME then CRTC.reads?(section)
100
+ when "CIA1" then CIAs.reads?(section)
101
+ when ACIA::NAME then ACIA.reads?(section)
102
+ when SIDRegisters::NAME then SIDRegisters.reads?(section)
103
+ when SIDExtended::NAME then SIDExtended.reads?(section)
104
+ else TPI.reads?(section)
105
+ end
106
+ end
107
+
108
+ # The machine's keyed modules: its own at power-on, with those the
109
+ # modules `applied` names stand for in their place.
110
+ def modules(container, computer, applied, ignored)
111
+ chips = chips(container, computer, applied)
112
+ own = {}
113
+ computer.save_modules.each { |section| own[section.name] = section }
114
+ drive(container, computer, applied, ignored, own)
115
+ chips.save_modules.each { |section| own[section.name] = section if CHIPS.include?(section.name) }
116
+ cycles = FieldReader.new(container[MainCPU::NAME]).qword
117
+ crtc = crtc(container, computer, applied, own)
118
+ high = machine(own, computer, cycles, crtc)
119
+ own["BL-RAM"] = Keyed.section("BL-RAM") do |out|
120
+ CBM2Mem.import(container[CBM2Mem::NAME], out, computer.model.ram_segments)
121
+ end
122
+ own["BL-CPU"] = Keyed.section("BL-CPU") { |out| PET::MainCPU.import(container[MainCPU::NAME], out) }
123
+ if applied.include?(ACIA::NAME)
124
+ own["BL-ACIA"] = Keyed.section("BL-ACIA") { |out| ACIA.import(container[ACIA::NAME], out, cycles) }
125
+ end
126
+ lines = IDLE_LINES | (high ? MAINS : 0) | (chips.cia.interrupted? ? 0 : CIA)
127
+ tpis(container, applied, own, acia_irq?(container, applied) ? lines : lines | ACIA_LINE)
128
+ own.values
129
+ end
130
+
131
+ # Puts the true drive's modules in `own` in place of badline's: the
132
+ # 2031, 8050 or 8250 the snapshot has, as xpet's are read
133
+ # (PET::Restore), with the disks badline's drive of that model has,
134
+ # or none.
135
+ def drive(container, computer, applied, ignored, own)
136
+ disks = PET::Restore::DISKS.filter_map { |name| own[name] }
137
+ own.delete_if { |name, _| name.match?(/\ABL-D\d+(-|\z)/) }
138
+ kept = PET::Restore.kept_disks(container, computer, disks)
139
+ PET::Restore.drive(container, applied, ignored, kept).each { |section| own[section.name] = section }
140
+ end
141
+
142
+ # A machine of its own with xcbm2's CIA and SID read into it.
143
+ def chips(container, computer, applied)
144
+ machine = CBM2Setup.of(computer).build
145
+ CIAs.import_into(container["CIA1"], machine.cia) if applied.include?("CIA1")
146
+ SIDRegisters.import(container[SIDRegisters::NAME], machine) if applied.include?(SIDRegisters::NAME)
147
+ SIDExtended.import(container[SIDExtended::NAME], machine) if applied.include?(SIDExtended::NAME)
148
+ machine
149
+ end
150
+
151
+ # Puts the CRTC's and the picture's modules in `own`, and returns
152
+ # the CRTC's fields.
153
+ def crtc(container, computer, applied, own)
154
+ return Keyed::Reader.parse(own.fetch("BL-CRTC")) unless applied.include?(CRTC::NAME)
155
+
156
+ section = CRTC.import(container[CRTC::NAME], computer)
157
+ fields = Keyed::Reader.parse(section)
158
+ own["BL-CRTC"] = section
159
+ own["BL-VIDEO"] = CRTC.video(container[CRTC::NAME], fields)
160
+ fields
161
+ end
162
+
163
+ # Puts BL-MACHINE in `own`, with the mains' next falling edge where
164
+ # xcbm2 ticks it for the CRTC's fields `crtc`, and returns whether
165
+ # the mains is high.
166
+ def machine(own, computer, cycles, crtc)
167
+ high = true
168
+ own["BL-MACHINE"] = Keyed.section("BL-MACHINE") do |out|
169
+ out.text("family", "cbm2")
170
+ CBM2Setup.of(computer).write_fields(out)
171
+ out.int("cycles", cycles)
172
+ high = Mains.write(out, Mains.next_tick(crtc), Badline::CBM2::CLOCK_HZ / (2 * computer.mains_hz))
173
+ end
174
+ high
175
+ end
176
+
177
+ # Puts both TPIs' modules in `own`, TPI1's with its interrupt
178
+ # inputs at `lines`, TPI2's all high.
179
+ def tpis(container, applied, own, lines)
180
+ TPI::NAMES.zip([lines, 0x1f]).each do |name, levels|
181
+ next unless applied.include?(name)
182
+
183
+ own["BL-#{name}"] = Keyed.section("BL-#{name}") { |out| TPI.import(container[name], out, levels) }
184
+ end
185
+ end
186
+
187
+ # Whether the ACIA pulls its IRQ line: its status has the
188
+ # interrupt, and DTR enables it.
189
+ def acia_irq?(container, applied)
190
+ return false unless applied.include?(ACIA::NAME)
191
+
192
+ fields = FieldReader.new(container[ACIA::NAME]).skip(2)
193
+ fields.byte.anybits?(ACIA::IRQ) && fields.byte.anybits?(0x01)
194
+ end
195
+
196
+ def left_out(container, applied)
197
+ container.sections.reject { |section| applied.include?(section.name) }
198
+ .map { |section| "#{section}: badline doesn't read this module, left out" }
199
+ end
200
+ end
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Vice
6
+ module CBM2
7
+ # TPI1 and TPI2 1.0, the 6525s: PRA, PRB, PRC, DDRA, DDRB, DDRC, CR
8
+ # and the AIR, then the interrupts latched and not yet taken through
9
+ # the AIR, and CA in bit 7 and CB in bit 6 of the last byte. In mode
10
+ # 1 xcbm2 keeps the latches in PRC's low five bits and the mask in
11
+ # DDRC's.
12
+ #
13
+ # xcbm2 saves no priority stack: a TPI read from its snapshot has
14
+ # none in service, and one written for it loses the one it has. The
15
+ # levels on the interrupt inputs aren't saved either, so they are
16
+ # read as the machine drives them (CBM2::Restore).
17
+ #
18
+ # Written and read: the registers, the latches, the mask, the AIR,
19
+ # CA and CB.
20
+ module TPI
21
+ NAMES = %w[TPI1 TPI2].freeze
22
+ MAJOR = 1
23
+ MINOR = 0
24
+ LATCHES = 0x1f
25
+ CA = 0x80
26
+ CB = 0x40
27
+
28
+ module_function
29
+
30
+ def reads?(section) = section.major == MAJOR && section.minor == MINOR
31
+
32
+ # The module `name` from the TPI's BL-TPI fields in `input`.
33
+ def export(name, input)
34
+ pra, prb, prc = input.ints("ports", [0, 0, 0])
35
+ ddra, ddrb, ddrc = input.ints("directions", [0, 0, 0])
36
+ control = input.int("control", 0)
37
+ latches = input.int("latches", 0)
38
+ prc = (prc & ~LATCHES) | latches if control.anybits?(0x01)
39
+ lines = (input.bool("ca", false) ? CA : 0) | (input.bool("cb", false) ? CB : 0)
40
+ FieldWriter.new.bytes([pra, prb, prc, ddra, ddrb, ddrc, control, input.int("air", 0), latches, lines])
41
+ .section(name, MAJOR, MINOR)
42
+ end
43
+
44
+ # The registers by their BL-TPI keys, in a keyed module's writer,
45
+ # with `lines` the levels on the interrupt inputs.
46
+ def import(section, out, lines)
47
+ pra, prb, prc, ddra, ddrb, ddrc, control, air, _taken, edges = FieldReader.new(section).bytes(10)
48
+ out.ints("ports", [pra, prb, prc]).ints("directions", [ddra, ddrb, ddrc]).int("control", control)
49
+ mode1 = control.anybits?(0x01)
50
+ out.int("latches", mode1 ? prc & LATCHES : 0)
51
+ out.int("air", mode1 ? air & LATCHES : 0).ints("stack", [])
52
+ out.bool("ca", edges.anybits?(CA)).bool("cb", edges.anybits?(CB)).int("lines", lines)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -28,13 +28,16 @@ module Badline
28
28
  module_function
29
29
 
30
30
  def export(computer)
31
- [computer.cia1, computer.cia2].each_with_index.map do |cia, i|
32
- fields = FieldWriter.new
33
- registers(cia, fields)
34
- time_of_day(cia.time_of_day, fields)
35
- internals(cia, fields)
36
- fields.section(NAMES[i], MAJOR, MINOR)
37
- end
31
+ [computer.cia1, computer.cia2].each_with_index.map { |cia, i| section(cia, NAMES[i]) }
32
+ end
33
+
34
+ # One CIA's module, named `name`.
35
+ def section(cia, name)
36
+ fields = FieldWriter.new
37
+ registers(cia, fields)
38
+ time_of_day(cia.time_of_day, fields)
39
+ internals(cia, fields)
40
+ fields.section(name, MAJOR, MINOR)
38
41
  end
39
42
 
40
43
  def registers(cia, fields)
@@ -80,9 +83,10 @@ module Badline
80
83
  def reads?(section) = section.major == MAJOR && section.minor >= MINOR
81
84
 
82
85
  def import(section, computer)
83
- cia = section.name == NAMES.first ? computer.cia1 : computer.cia2
84
- Import.new(FieldReader.new(section)).apply(cia)
86
+ import_into(section, section.name == NAMES.first ? computer.cia1 : computer.cia2)
85
87
  end
88
+
89
+ def import_into(section, cia) = Import.new(FieldReader.new(section)).apply(cia)
86
90
  end
87
91
  end
88
92
  end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/snapshot/vice/p500_vicii"
4
+
5
+ module Badline
6
+ module Snapshot
7
+ module Vice
8
+ # xcbm5x0's modules for the P500, the CBM 510: the B series' CPU,
9
+ # CIA, TPIs, ACIA and SID modules (CBM2), CBM2MEM with segment 15's
10
+ # video matrix and colour RAM, xcbm5x0's own VIC-II (VICII) and
11
+ # C500DATA, the cycles to the mains' next tick as a dword, then 4
12
+ # bytes xcbm5x0 leaves 0. They go
13
+ # through the machine's keyed modules as the B series' do, and the
14
+ # VIC through badline's VIC as the C64's does.
15
+ module P500
16
+ # The modules xcbm5x0 resets the machine without, each as xcbm5x0
17
+ # leaves them with nothing attached.
18
+ BEFORE_VIC = [["FSDRIVE", 0, 0, Array.new(258, 0)]].freeze
19
+ AFTER_VIC = [["TAPEPORT", 1, 0, [0x01, 0x01]], ["JOYPORT0", 0, 0, [0x01]], ["JOYSTICK0", 1, 2, [0x00, 0x00]],
20
+ ["JOYPORT1", 0, 0, [0x01]], ["JOYSTICK1", 1, 2, [0x00, 0x00]]].freeze
21
+ MAINS = "C500DATA"
22
+ # CBM2MEM's second byte, which names the P500.
23
+ P500_RAM = 0x40
24
+ MATRIX = 0x400
25
+ # How many cycles after C500DATA's count badline's mains falls, so
26
+ # that TPI1 latches it on xcbm5x0's cycle.
27
+ TICK_LEAD = 4
28
+
29
+ module_function
30
+
31
+ def export(machine, sections)
32
+ readers = {}
33
+ sections.each { |section| readers[section.name] = Keyed::Reader.parse(section) }
34
+ machine.sid.catch_up!
35
+ irqs = [machine.tpi1.irq?, machine.vic.interrupted?].count(true)
36
+ [PET::MainCPU.write(machine, irqs), memory(machine), fixed("CBM2CART", 0, 1, [0x00]),
37
+ CIAs.section(machine.cia, "CIA1"),
38
+ *CBM2::TPI::NAMES.map { |name| CBM2::TPI.export(name, CBM2.reader(readers, "BL-#{name}")) },
39
+ CBM2::ACIA.export(CBM2.reader(readers, "BL-ACIA"), machine.cycles),
40
+ SIDRegisters.export(machine), SIDExtended.export(machine), *CBM2.drives(machine, readers),
41
+ *BEFORE_VIC.map { |entry| fixed(*entry) },
42
+ VICII.export(machine), mains(CBM2.reader(readers, "BL-MACHINE")), *AFTER_VIC.map { |entry| fixed(*entry) }]
43
+ end
44
+
45
+ def fixed(name, major, minor, bytes) = FieldWriter.new.bytes(bytes).section(name, major, minor)
46
+
47
+ # CBM2MEM as xcbm5x0 writes it: 128K, the P500's byte, the model
48
+ # line, both segment registers, segment 15's RAM with the registers
49
+ # in its first two bytes, the video matrix, the colour RAM a nibble
50
+ # a byte, then segments 0 and 1.
51
+ def memory(machine)
52
+ bus = machine.bus
53
+ system = bus.ram.snapshot
54
+ system[0] = bus.execution_segment
55
+ system[1] = bus.indirect_segment
56
+ fields = FieldWriter.new.byte(1).byte(P500_RAM).byte(CBM2::CBM2Mem::M600_50HZ)
57
+ fields.byte(bus.execution_segment).byte(bus.indirect_segment).bytes(system).bytes(bus.screen.snapshot)
58
+ fields.bytes(Array.new(MATRIX) { |offset| machine.color_ram.nibble(0xd400 + offset) })
59
+ Badline::CBM2::P500::Model::RAM_SEGMENTS.each { |segment| fields.bytes(bus.segment_ram[segment].snapshot) }
60
+ fields.section(CBM2::CBM2Mem::NAME, CBM2::CBM2Mem::MAJOR, CBM2::CBM2Mem::MINOR)
61
+ end
62
+
63
+ # C500DATA: the cycles to the mains' next tick, TICK_LEAD less than
64
+ # to the falling edge BL-MACHINE's countdown and level put it at.
65
+ def mains(machine)
66
+ countdown = machine.int("mains_countdown", 1)
67
+ cycles = machine.bool("mains_high", true) ? countdown : countdown + half_period(machine)
68
+ FieldWriter.new.dword([cycles - TICK_LEAD, 0].max).zeros(4).section(MAINS, 0, 0)
69
+ end
70
+
71
+ def half_period(machine)
72
+ setup = CBM2Setup.read_fields(machine)
73
+ Badline::CBM2::P500::Model.named(setup.model).region.clock_hz / (2 * setup.mains_hz)
74
+ end
75
+
76
+ # The cycles to the mains' falling edge where xcbm5x0 ticks it.
77
+ def cycles_to_tick(section) = FieldReader.new(section).dword + TICK_LEAD
78
+
79
+ # How the machine xcbm5x0 saved was built: a 510, NTSC where
80
+ # xcbm5x0's picture has NTSC's lines, at its region's mains.
81
+ def setup(container)
82
+ memory = CBM2.memory(container)
83
+ unless CBM2::CBM2Mem.mapped(memory) == P500_RAM && CBM2::CBM2Mem.ram_kb(memory) == 128
84
+ raise FormatError, "#{memory}: a P500 built otherwise than badline's 510, with 128K of RAM"
85
+ end
86
+
87
+ vic = container[VICII::NAME]
88
+ ntsc = !vic.nil? && VICII.reads?(vic) && VICII.picture_lines(vic) == VICII::SIZES.fetch(263).last
89
+ model = ntsc ? "510ntsc" : "510"
90
+ CBM2Setup.new(model:, kernal: Badline::CBM2::P500::Model::KERNALS.keys.first,
91
+ mains_hz: Badline::CBM2::P500::Model.named(model).region.mains_hz, refresh: false)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Snapshot
5
+ module Vice
6
+ module P500
7
+ # Restores a P500 from a snapshot xcbm5x0 wrote, as CBM2::Restore
8
+ # restores a B series: the modules badline reads become the
9
+ # machine's keyed modules, the CIA, the SID and the VIC read into a
10
+ # machine of their own first. Whatever they don't set stays as the
11
+ # machine has it at power-on, and the disk device 8 serves through
12
+ # the traps stays as it is.
13
+ module Restore
14
+ READ = [MainCPU::NAME, CBM2::CBM2Mem::NAME, "CIA1", *CBM2::TPI::NAMES, CBM2::ACIA::NAME, SIDRegisters::NAME,
15
+ SIDExtended::NAME, VICII::NAME, MAINS].freeze
16
+ REQUIRED = [MainCPU::NAME, CBM2::CBM2Mem::NAME].freeze
17
+ # The modules read into a machine of their own first.
18
+ CHIPS = %w[BL-CIA BL-SID BL-VIC BL-COLORRAM].freeze
19
+
20
+ module_function
21
+
22
+ def import(container, computer)
23
+ unless computer.is_a?(Badline::CBM2::P500)
24
+ raise FormatError, "the snapshot is of a P500, not a #{computer_name(computer)}"
25
+ end
26
+
27
+ setup = P500.setup(container)
28
+ ours = CBM2Setup.of(computer)
29
+ raise FormatError, "the snapshot is of a #{setup}, not a #{ours}" unless setup == ours
30
+
31
+ apply(container, setup.build)
32
+ CBM2::Restore.reapply(computer) { apply(container, computer) }
33
+ end
34
+
35
+ def computer_name(computer)
36
+ computer.family == :cbm2 ? "CBM-II's B series" : Image::FAMILIES.fetch(computer.family.to_s)
37
+ end
38
+
39
+ def apply(container, computer)
40
+ raise FormatError, "the snapshot has no #{MainCPU::NAME} module" if container[MainCPU::NAME].nil?
41
+
42
+ applied = []
43
+ ignored = []
44
+ READ.each do |name|
45
+ section = container[name]
46
+ next if section.nil?
47
+
48
+ reason = refusal(section, container)
49
+ if reason.nil?
50
+ applied << name
51
+ else
52
+ raise FormatError, "#{section}: #{reason}" if REQUIRED.include?(name)
53
+
54
+ ignored << "#{section}: #{reason}, left out"
55
+ end
56
+ end
57
+ computer.load_modules(Keyed::Modules.new(modules(container, computer, applied, ignored)))
58
+ Report.new(applied:, ignored: ignored + CBM2::Restore.left_out(container, applied))
59
+ end
60
+
61
+ def refusal(section, container)
62
+ case section.name
63
+ when VICII::NAME then VICII.reads?(section) ? nil : "badline doesn't read this version"
64
+ when MAINS then section.major.zero? && section.minor.zero? ? nil : "badline doesn't read this version"
65
+ else CBM2::Restore.refusal(section, container)
66
+ end
67
+ end
68
+
69
+ def modules(container, computer, applied, ignored)
70
+ chips = chips(container, computer, applied)
71
+ own = {}
72
+ computer.save_modules.each { |section| own[section.name] = section }
73
+ CBM2::Restore.drive(container, computer, applied, ignored, own)
74
+ chips.save_modules.each { |section| own[section.name] = section if CHIPS.include?(section.name) }
75
+ cycles = FieldReader.new(container[MainCPU::NAME]).qword
76
+ high = machine(own, container, computer, cycles, applied)
77
+ own["BL-RAM"] = Keyed.section("BL-RAM") { |out| memory(container[CBM2::CBM2Mem::NAME], out) }
78
+ own["BL-CPU"] = Keyed.section("BL-CPU") { |out| PET::MainCPU.import(container[MainCPU::NAME], out) }
79
+ if applied.include?(CBM2::ACIA::NAME)
80
+ own["BL-ACIA"] = Keyed.section("BL-ACIA") { |out| CBM2::ACIA.import(container[CBM2::ACIA::NAME], out, cycles) }
81
+ end
82
+ lines = CBM2::Restore::IDLE_LINES | (high ? CBM2::Restore::MAINS : 0)
83
+ lines |= CBM2::Restore::CIA unless chips.cia.interrupted?
84
+ lines |= CBM2::Restore::ACIA_LINE unless CBM2::Restore.acia_irq?(container, applied)
85
+ CBM2::Restore.tpis(container, applied, own, lines)
86
+ own.values
87
+ end
88
+
89
+ # A machine of its own with xcbm5x0's CIA, SID and VIC read into
90
+ # it.
91
+ def chips(container, computer, applied)
92
+ machine = CBM2::Restore.chips(container, computer, applied)
93
+ VICII.import(container[VICII::NAME], machine.vic, machine.color_ram) if applied.include?(VICII::NAME)
94
+ machine
95
+ end
96
+
97
+ # Puts BL-MACHINE in `own`, with the mains' next falling edge where
98
+ # C500DATA has xcbm5x0's tick, and returns whether the mains is
99
+ # high.
100
+ def machine(own, container, computer, cycles, applied)
101
+ half = computer.model.region.clock_hz / (2 * computer.mains_hz)
102
+ tick = applied.include?(MAINS) ? P500.cycles_to_tick(container[MAINS]) : half
103
+ high = true
104
+ own["BL-MACHINE"] = Keyed.section("BL-MACHINE") do |out|
105
+ out.text("family", "cbm2")
106
+ CBM2Setup.of(computer).write_fields(out)
107
+ out.int("cycles", cycles)
108
+ high = CBM2::Mains.write(out, [tick, 1].max, half)
109
+ end
110
+ high
111
+ end
112
+
113
+ # The RAM, segment 15's video matrix and the segment registers by
114
+ # their BL-RAM keys.
115
+ def memory(section, out)
116
+ fields = FieldReader.new(section).skip(3)
117
+ out.int("execution_segment", fields.byte & 0x0f).int("indirect_segment", fields.byte & 0x0f)
118
+ system = fields.string(CBM2::CBM2Mem::SYSTEM_RAM)
119
+ out.bytes("system", "\0\0".b + system.byteslice(2, CBM2::CBM2Mem::SYSTEM_RAM - 2))
120
+ out.bytes("screen", fields.string(MATRIX))
121
+ fields.skip(MATRIX)
122
+ Badline::CBM2::P500::Model::RAM_SEGMENTS.each do |segment|
123
+ out.bytes("segment#{segment}", fields.string(CBM2::CBM2Mem::SEGMENT))
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end