badline 0.5.1 → 0.6.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 +52 -0
- data/CLAUDE.md +10 -9
- data/README.md +82 -10
- data/doc/pinned-behaviour.md +131 -30
- data/exe/badline-ruby +20 -23
- data/lib/badline/address_bus/saved_state.rb +72 -0
- data/lib/badline/address_bus.rb +11 -1
- data/lib/badline/audio/cli.rb +18 -5
- data/lib/badline/audio/console.rb +2 -1
- data/lib/badline/audio.rb +0 -3
- data/lib/badline/cartridge/action_replay.rb +15 -0
- data/lib/badline/cartridge/comal80.rb +8 -0
- data/lib/badline/cartridge/dinamic.rb +8 -0
- data/lib/badline/cartridge/easy_flash.rb +20 -1
- data/lib/badline/cartridge/epyx_fastload.rb +8 -0
- data/lib/badline/cartridge/final_cartridge3.rb +12 -0
- data/lib/badline/cartridge/flash.rb +41 -0
- data/lib/badline/cartridge/fun_play.rb +8 -0
- data/lib/badline/cartridge/g_mod2.rb +12 -0
- data/lib/badline/cartridge/game_system.rb +8 -0
- data/lib/badline/cartridge/geo_ram.rb +16 -0
- data/lib/badline/cartridge/magic_desk.rb +8 -0
- data/lib/badline/cartridge/ocean.rb +8 -0
- data/lib/badline/cartridge/pagefox.rb +12 -0
- data/lib/badline/cartridge/retro_replay.rb +44 -1
- data/lib/badline/cartridge/rgcd.rb +10 -0
- data/lib/badline/cartridge/super_games.rb +10 -0
- data/lib/badline/cartridge/zaxxon.rb +16 -0
- data/lib/badline/cartridge.rb +72 -0
- data/lib/badline/cia/interrupt_register.rb +17 -0
- data/lib/badline/cia/serial.rb +44 -0
- data/lib/badline/cia/state.rb +47 -0
- data/lib/badline/cia/timer.rb +33 -0
- data/lib/badline/cia.rb +8 -0
- data/lib/badline/computer/attachments.rb +132 -0
- data/lib/badline/computer/saved_state.rb +116 -0
- data/lib/badline/computer.rb +13 -33
- data/lib/badline/cpu/boundary.rb +54 -0
- data/lib/badline/cpu/core.rb +165 -0
- data/lib/badline/cpu/microcode.rb +8 -0
- data/lib/badline/cpu/saved_state.rb +115 -0
- data/lib/badline/cpu.rb +3 -212
- data/lib/badline/datasette.rb +27 -0
- data/lib/badline/drive1541/bus.rb +13 -0
- data/lib/badline/drive1541/cpu.rb +29 -0
- data/lib/badline/drive1541/disk.rb +7 -2
- data/lib/badline/drive1541/disk_state.rb +80 -0
- data/lib/badline/drive1541/mechanism.rb +2 -0
- data/lib/badline/drive1541/mechanism_state.rb +67 -0
- data/lib/badline/drive1541/rotation.rb +10 -0
- data/lib/badline/drive1541/serial_port.rb +10 -0
- data/lib/badline/drive1541.rb +35 -0
- data/lib/badline/ffi.rb +157 -0
- data/lib/badline/frontend/app.rb +225 -0
- data/lib/badline/frontend/audio_sink.rb +72 -0
- data/lib/badline/frontend/boot.rb +52 -0
- data/lib/badline/frontend/controls.rb +163 -0
- data/lib/badline/frontend/drive_led.rb +51 -0
- data/lib/badline/frontend/frame_rate.rb +83 -0
- data/lib/badline/frontend/gamepads.rb +76 -0
- data/lib/badline/frontend/keys.rb +48 -0
- data/lib/badline/frontend/pacer.rb +100 -0
- data/lib/badline/frontend/pad_port.rb +61 -0
- data/lib/badline/frontend/screen.rb +65 -0
- data/lib/badline/frontend/screenshot.rb +22 -0
- data/lib/badline/frontend/snapshots.rb +60 -0
- data/lib/badline/frontend/sound.rb +153 -0
- data/lib/badline/frontend/timeline.rb +171 -0
- data/lib/badline/frontend.rb +18 -0
- data/lib/badline/kernal_trap/channel.rb +27 -0
- data/lib/badline/kernal_trap/drive/channels.rb +25 -0
- data/lib/badline/kernal_trap/drive/memory.rb +9 -0
- data/lib/badline/kernal_trap/drive/status.rb +8 -0
- data/lib/badline/kernal_trap/drive/write_file.rb +21 -0
- data/lib/badline/kernal_trap/drive.rb +19 -0
- data/lib/badline/kernal_trap/save.rb +8 -0
- data/lib/badline/kernal_trap/serial.rb +16 -0
- data/lib/badline/memory.rb +8 -0
- data/lib/badline/options/event.rb +94 -0
- data/lib/badline/options/help.rb +78 -0
- data/lib/badline/options/option.rb +29 -0
- data/lib/badline/options/table.rb +62 -0
- data/lib/badline/options.rb +181 -144
- data/lib/badline/ram_expansion/plus256k.rb +16 -0
- data/lib/badline/ram_expansion/plus60k.rb +12 -0
- data/lib/badline/ram_expansion/unexpanded.rb +6 -0
- data/lib/badline/reu/dma/saved_state.rb +50 -0
- data/lib/badline/reu/dma.rb +56 -11
- data/lib/badline/reu/ram.rb +15 -0
- data/lib/badline/reu/saved_state.rb +53 -0
- data/lib/badline/reu.rb +23 -10
- data/lib/badline/sdl.rb +123 -51
- data/lib/badline/sid/envelope/state.rb +68 -0
- data/lib/badline/sid/envelope.rb +7 -0
- data/lib/badline/sid/filter.rb +38 -0
- data/lib/badline/sid/voice.rb +10 -0
- data/lib/badline/sid/waveform/noise_writeback.rb +33 -11
- data/lib/badline/sid/waveform/state.rb +66 -0
- data/lib/badline/sid/waveform.rb +4 -3
- data/lib/badline/sid.rb +34 -0
- data/lib/badline/snapshot/container.rb +117 -0
- data/lib/badline/snapshot/fields.rb +54 -0
- data/lib/badline/snapshot/image.rb +70 -0
- data/lib/badline/snapshot/machine_state.rb +77 -0
- data/lib/badline/snapshot/setup.rb +58 -0
- data/lib/badline/snapshot/state.rb +178 -0
- data/lib/badline/snapshot/vice/c64_mem.rb +53 -0
- data/lib/badline/snapshot/vice/cias.rb +89 -0
- data/lib/badline/snapshot/vice/cias_import.rb +70 -0
- data/lib/badline/snapshot/vice/main_cpu.rb +76 -0
- data/lib/badline/snapshot/vice/peripherals.rb +47 -0
- data/lib/badline/snapshot/vice/restore.rb +125 -0
- data/lib/badline/snapshot/vice/sid_extended.rb +96 -0
- data/lib/badline/snapshot/vice/sid_registers.rb +46 -0
- data/lib/badline/snapshot/vice/vicii.rb +119 -0
- data/lib/badline/snapshot/vice/vicii_import.rb +131 -0
- data/lib/badline/snapshot/vice.rb +74 -0
- data/lib/badline/snapshot.rb +52 -0
- data/lib/badline/storage/crt_file.rb +9 -3
- data/lib/badline/storage/d64_image.rb +2 -0
- data/lib/badline/storage/d71_image.rb +2 -0
- data/lib/badline/storage/d81_image.rb +2 -0
- data/lib/badline/storage/disk_image.rb +11 -2
- data/lib/badline/storage/host_directory.rb +5 -0
- data/lib/badline/storage/t64.rb +6 -0
- data/lib/badline/storage/tap.rb +10 -1
- data/lib/badline/storage/unavailable.rb +20 -0
- data/lib/badline/storage.rb +34 -0
- data/lib/badline/time_of_day.rb +56 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/control_lines.rb +15 -0
- data/lib/badline/via/interrupt_register.rb +9 -0
- data/lib/badline/via/shift_register.rb +13 -0
- data/lib/badline/via/state.rb +42 -0
- data/lib/badline/via/timer1.rb +13 -0
- data/lib/badline/via/timer2.rb +18 -0
- data/lib/badline/via.rb +2 -0
- data/lib/badline/vic/bank.rb +21 -0
- data/lib/badline/vic/border_mask.rb +10 -0
- data/lib/badline/vic/collisions.rb +11 -0
- data/lib/badline/vic/color_patches.rb +11 -0
- data/lib/badline/vic/display_state.rb +29 -1
- data/lib/badline/vic/graphics_shifter.rb +26 -0
- data/lib/badline/vic/register_log.rb +18 -0
- data/lib/badline/vic/registers.rb +13 -2
- data/lib/badline/vic/sequencer.rb +12 -0
- data/lib/badline/vic/sequencer_state.rb +89 -0
- data/lib/badline/vic/sprite/internal_bus.rb +11 -0
- data/lib/badline/vic/sprite/saved_state.rb +64 -0
- data/lib/badline/vic/sprite.rb +2 -0
- data/lib/badline/vic/sprites.rb +3 -0
- data/lib/badline/vic/sprites_state.rb +57 -0
- data/lib/badline/vic.rb +109 -16
- data/lib/badline.rb +1 -0
- metadata +59 -18
- data/.claude/hooks/session-start.sh +0 -57
- data/.claude/settings.json +0 -14
- data/lib/badline/audio/sdl_sink.rb +0 -70
- data/lib/badline/audio/stream.rb +0 -117
- data/lib/badline/gui/application.rb +0 -216
- data/lib/badline/gui/drive_led_pane.rb +0 -29
- data/lib/badline/gui/gamepads.rb +0 -97
- data/lib/badline/gui/joy_map.rb +0 -27
- data/lib/badline/gui/key_map.rb +0 -39
- data/lib/badline/gui/palette.rb +0 -35
- data/lib/badline/gui/pane.rb +0 -36
- data/lib/badline/gui/screen_pane.rb +0 -46
- data/lib/badline/gui/window.rb +0 -61
- data/lib/badline/gui.rb +0 -13
- data/lib/badline/sdl/events.rb +0 -47
- data/lib/badline/sdl/functions.rb +0 -74
- data/vendor/.gitkeep +0 -0
data/lib/badline/address_bus.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "badline/address_bus/saved_state"
|
|
3
4
|
require "badline/address_bus/ultimax_pages"
|
|
4
5
|
|
|
5
6
|
module Badline
|
|
@@ -88,6 +89,7 @@ module Badline
|
|
|
88
89
|
|
|
89
90
|
@color_ram = ColorMemory.new(@vic)
|
|
90
91
|
@open_bus = OpenBus.new(@vic)
|
|
92
|
+
@cpu_off_bus = false
|
|
91
93
|
|
|
92
94
|
@port_ddr = 0x00
|
|
93
95
|
@port_out = 0x00
|
|
@@ -153,11 +155,19 @@ module Badline
|
|
|
153
155
|
@ram.poke(addr, @vic.phi1_data)
|
|
154
156
|
addr.zero? ? @port_ddr = value : @port_out = value
|
|
155
157
|
update_port!
|
|
156
|
-
|
|
158
|
+
elsif !@cpu_off_bus
|
|
157
159
|
@write_pages[addr >> 8].poke(addr, value)
|
|
158
160
|
end
|
|
159
161
|
end
|
|
160
162
|
|
|
163
|
+
# Runs a CPU cycle with the CPU off the bus, as an REU's /DMA line
|
|
164
|
+
# holds it: a write it makes reaches nothing but its own port.
|
|
165
|
+
def cycle_cpu_off_bus(cpu)
|
|
166
|
+
@cpu_off_bus = true
|
|
167
|
+
cpu.cycle!
|
|
168
|
+
@cpu_off_bus = false
|
|
169
|
+
end
|
|
170
|
+
|
|
161
171
|
def inspect
|
|
162
172
|
"#<#{self.class.name} port=#{format('0x%02x', @io_port.value)} " \
|
|
163
173
|
"cartridge=#{@cartridge ? @cartridge.class.name : 'none'} " \
|
data/lib/badline/audio/cli.rb
CHANGED
|
@@ -2,18 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
module Audio
|
|
5
|
-
# Runs
|
|
6
|
-
# parsed: picks the song and its length, then plays it or renders
|
|
7
|
-
# to a file. Played on a terminal, the console lets the listener step
|
|
5
|
+
# Runs `--headless` or `--audio-out`, in either build, once its options
|
|
6
|
+
# have parsed: picks the song and its length, then plays it or renders
|
|
7
|
+
# it to a file. Played on a terminal, the console lets the listener step
|
|
8
8
|
# between songs.
|
|
9
9
|
class CLI
|
|
10
10
|
class Error < StandardError; end
|
|
11
11
|
|
|
12
|
+
# Plays or renders the tune as #run does, reports an error on stderr
|
|
13
|
+
# under the program's name, and returns the exit status.
|
|
14
|
+
def self.run(options, sink:, console:)
|
|
15
|
+
new(options, sink:, console:).run
|
|
16
|
+
0
|
|
17
|
+
rescue Error => e
|
|
18
|
+
warn "#{options.program}: #{e.message}"
|
|
19
|
+
1
|
|
20
|
+
rescue Interrupt
|
|
21
|
+
130
|
|
22
|
+
end
|
|
23
|
+
|
|
12
24
|
# Playing needs `sink`, which builds the audio device given the rate
|
|
13
25
|
# to ask for and whether it has to be exact, and, on a terminal,
|
|
14
26
|
# `console`, which builds the terminal to play on given the input and
|
|
15
|
-
# output.
|
|
16
|
-
#
|
|
27
|
+
# output. Each build's entry point hands them in: both builds' sink is
|
|
28
|
+
# Frontend::AudioSink, and the console is badline-ruby's Console or
|
|
29
|
+
# the native badline's Native::Console.
|
|
17
30
|
def initialize(options, sink: nil, console: nil, out: $stdout, input: $stdin)
|
|
18
31
|
@options = options
|
|
19
32
|
@out = out
|
|
@@ -6,7 +6,8 @@ require "io/wait"
|
|
|
6
6
|
module Badline
|
|
7
7
|
module Audio
|
|
8
8
|
# The terminal of `badline-ruby --headless`, put in raw mode with
|
|
9
|
-
# io/console.
|
|
9
|
+
# io/console. It is badline-ruby's side of the console seam, and
|
|
10
|
+
# exe/badline-ruby requires it: the native badline's is Native::Console.
|
|
10
11
|
class Console < Terminal
|
|
11
12
|
private
|
|
12
13
|
|
data/lib/badline/audio.rb
CHANGED
|
@@ -7,9 +7,6 @@ require "badline/audio/bare_player"
|
|
|
7
7
|
require "badline/audio/machine_player"
|
|
8
8
|
require "badline/audio/renderer"
|
|
9
9
|
require "badline/audio/playback"
|
|
10
|
-
require "badline/audio/sdl_sink"
|
|
11
|
-
require "badline/audio/stream"
|
|
12
10
|
require "badline/audio/terminal"
|
|
13
|
-
require "badline/audio/console"
|
|
14
11
|
require "badline/audio/jukebox"
|
|
15
12
|
require "badline/audio/cli"
|
|
@@ -64,6 +64,21 @@ module Badline
|
|
|
64
64
|
|
|
65
65
|
private
|
|
66
66
|
|
|
67
|
+
def save_mapper(out)
|
|
68
|
+
save_windows(out, windows)
|
|
69
|
+
out.int(window_index(@rom, windows)).boolean(@active).boolean(@io2_ram).blob(@ram_data)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def load_mapper(input)
|
|
73
|
+
load_windows(input, windows)
|
|
74
|
+
@rom = window_at(input.int, windows)
|
|
75
|
+
@active = input.boolean?
|
|
76
|
+
@io2_ram = input.boolean?
|
|
77
|
+
input.blob_into(@ram_data)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def windows = [@through, @isolated, @contended, *@rom_banks, EMPTY_BANK]
|
|
81
|
+
|
|
67
82
|
def io2_peek(addr)
|
|
68
83
|
@io2_ram ? @ram_data[IO2_PAGE | (addr & 0xff)] : @rom.peek(addr)
|
|
69
84
|
end
|
|
@@ -33,6 +33,14 @@ module Badline
|
|
|
33
33
|
|
|
34
34
|
private
|
|
35
35
|
|
|
36
|
+
def save_mapper(out)
|
|
37
|
+
save_windows(out, [*@roml_banks, *@romh_banks, EMPTY_BANK])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def load_mapper(input)
|
|
41
|
+
load_windows(input, [*@roml_banks, *@romh_banks, EMPTY_BANK])
|
|
42
|
+
end
|
|
43
|
+
|
|
36
44
|
def select(number, mode)
|
|
37
45
|
@roml = bank(@roml_banks, number)
|
|
38
46
|
@romh = bank(@romh_banks, number)
|
|
@@ -24,6 +24,14 @@ module Badline
|
|
|
24
24
|
|
|
25
25
|
private
|
|
26
26
|
|
|
27
|
+
def save_mapper(out)
|
|
28
|
+
save_windows(out, [*@banks, EMPTY_BANK])
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def load_mapper(input)
|
|
32
|
+
load_windows(input, [*@banks, EMPTY_BANK])
|
|
33
|
+
end
|
|
34
|
+
|
|
27
35
|
def install_chips(chips)
|
|
28
36
|
@banks = banks_from(chips).first
|
|
29
37
|
self.mode = :rom8k
|
|
@@ -78,9 +78,28 @@ module Badline
|
|
|
78
78
|
private
|
|
79
79
|
|
|
80
80
|
def select_bank
|
|
81
|
+
map_bank
|
|
82
|
+
changed!
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def map_bank
|
|
81
86
|
@roml = @low_flash.window(@bank * BANK_SIZE)
|
|
82
87
|
@romh = @high_flash.window(@bank * BANK_SIZE)
|
|
83
|
-
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def save_mapper(out)
|
|
91
|
+
out.int(@bank).int(@control).blob(@io_ram)
|
|
92
|
+
@low_flash.save_state(out)
|
|
93
|
+
@high_flash.save_state(out)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def load_mapper(input)
|
|
97
|
+
@bank = input.int
|
|
98
|
+
@control = input.int
|
|
99
|
+
input.blob_into(@io_ram)
|
|
100
|
+
@low_flash.load_state(input)
|
|
101
|
+
@high_flash.load_state(input)
|
|
102
|
+
map_bank
|
|
84
103
|
end
|
|
85
104
|
|
|
86
105
|
def apply_control
|
|
@@ -62,6 +62,18 @@ module Badline
|
|
|
62
62
|
|
|
63
63
|
private
|
|
64
64
|
|
|
65
|
+
def save_mapper(out)
|
|
66
|
+
save_windows(out, [*@roml_banks, *@romh_banks, EMPTY_BANK])
|
|
67
|
+
out.boolean(@register_visible).boolean(@phi1_ultimax)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def load_mapper(input)
|
|
71
|
+
load_windows(input, [*@roml_banks, *@romh_banks, EMPTY_BANK])
|
|
72
|
+
@io_bank = @roml
|
|
73
|
+
@register_visible = input.boolean?
|
|
74
|
+
@phi1_ultimax = input.boolean?
|
|
75
|
+
end
|
|
76
|
+
|
|
65
77
|
def select(number)
|
|
66
78
|
@io_bank = @roml = bank(@roml_banks, number)
|
|
67
79
|
@romh = bank(@romh_banks, number)
|
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
class Cartridge
|
|
5
|
+
# A Flash chip's state, for a snapshot.
|
|
6
|
+
module FlashState
|
|
7
|
+
STATES = %i[read program_setup autoselect programming erase_window erasing suspended chip_erase
|
|
8
|
+
program_error].freeze
|
|
9
|
+
|
|
10
|
+
# The array, the command state, a timed operation's end on the
|
|
11
|
+
# machine's clock, and the windows handed out so far, which load_state
|
|
12
|
+
# builds anew.
|
|
13
|
+
def save_state(out)
|
|
14
|
+
out.blob(@data).int(STATES.index(@state)).int(STATES.index(@base_state)).int(@cycle).int(@toggle)
|
|
15
|
+
out.optional_int(@done_at).optional_int(@programmed).optional_int(@remaining)
|
|
16
|
+
out.boolean(!@erasing_sectors.nil?)
|
|
17
|
+
out.ints(@erasing_sectors) if @erasing_sectors
|
|
18
|
+
out.ints(@windows.keys).ints(@status_windows.keys)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def load_state(input)
|
|
22
|
+
input.blob_into(@data)
|
|
23
|
+
@state = STATES.fetch(input.int).to_sym
|
|
24
|
+
@base_state = STATES.fetch(input.int).to_sym
|
|
25
|
+
@cycle = input.int
|
|
26
|
+
@toggle = input.int
|
|
27
|
+
@done_at = input.optional_int
|
|
28
|
+
@programmed = input.optional_int
|
|
29
|
+
@remaining = input.optional_int
|
|
30
|
+
@erasing_sectors = nil
|
|
31
|
+
@erasing_sectors = input.ints if input.boolean?
|
|
32
|
+
rebuild_windows(input.ints, input.ints)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
5
36
|
# An AMD 29F0x0 flash chip, driven through the JEDEC command set in the
|
|
6
37
|
# AMD datasheets. Each command opens with two unlock cycles, $AA to the
|
|
7
38
|
# first unlock address and $55 to the second, and a third write picks
|
|
@@ -66,6 +97,8 @@ module Badline
|
|
|
66
97
|
alias [] peek
|
|
67
98
|
end
|
|
68
99
|
|
|
100
|
+
include FlashState
|
|
101
|
+
|
|
69
102
|
attr_reader :data, :state
|
|
70
103
|
attr_writer :clock
|
|
71
104
|
|
|
@@ -95,6 +128,14 @@ module Badline
|
|
|
95
128
|
end
|
|
96
129
|
end
|
|
97
130
|
|
|
131
|
+
# Hands out new windows at the bases given, as #window would have.
|
|
132
|
+
def rebuild_windows(bases, status_bases)
|
|
133
|
+
@windows = {}
|
|
134
|
+
@status_windows = {}
|
|
135
|
+
bases.each { |base| @windows[base] = Window.new(self, base) }
|
|
136
|
+
status_bases.each { |base| @status_windows[base] = StatusWindow.new(self, base) }
|
|
137
|
+
end
|
|
138
|
+
|
|
98
139
|
def array_mode?
|
|
99
140
|
@state == :read || @state == :program_setup
|
|
100
141
|
end
|
|
@@ -28,6 +28,14 @@ module Badline
|
|
|
28
28
|
|
|
29
29
|
private
|
|
30
30
|
|
|
31
|
+
def save_mapper(out)
|
|
32
|
+
save_windows(out, [*@banks, EMPTY_BANK])
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def load_mapper(input)
|
|
36
|
+
load_windows(input, [*@banks, EMPTY_BANK])
|
|
37
|
+
end
|
|
38
|
+
|
|
31
39
|
def install_chips(chips)
|
|
32
40
|
@banks = []
|
|
33
41
|
chips.each { |chip| @banks[self.class.bank_number(chip.bank)] = rom_bank(chip.data) }
|
|
@@ -47,6 +47,18 @@ module Badline
|
|
|
47
47
|
changed!
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
def save_mapper(out)
|
|
51
|
+
out.int(@bank).boolean(@flash_writes)
|
|
52
|
+
@flash.save_state(out)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def load_mapper(input)
|
|
56
|
+
@bank = input.int
|
|
57
|
+
@flash_writes = input.boolean?
|
|
58
|
+
@flash.load_state(input)
|
|
59
|
+
@roml = @flash.window(@bank * BANK_SIZE)
|
|
60
|
+
end
|
|
61
|
+
|
|
50
62
|
def install_chips(chips)
|
|
51
63
|
data = Array.new(BANKS * BANK_SIZE, 0xff)
|
|
52
64
|
chips.each do |chip|
|
|
@@ -25,6 +25,14 @@ module Badline
|
|
|
25
25
|
|
|
26
26
|
private
|
|
27
27
|
|
|
28
|
+
def save_mapper(out)
|
|
29
|
+
save_windows(out, [*@banks, EMPTY_BANK])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def load_mapper(input)
|
|
33
|
+
load_windows(input, [*@banks, EMPTY_BANK])
|
|
34
|
+
end
|
|
35
|
+
|
|
28
36
|
def select(addr)
|
|
29
37
|
@roml = bank(@banks, addr & 0x3f)
|
|
30
38
|
changed!
|
|
@@ -52,8 +52,24 @@ module Badline
|
|
|
52
52
|
@block = @page = @window = 0
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
# A GEO-RAM is built from its size alone.
|
|
56
|
+
def save_setup(out)
|
|
57
|
+
out.int(GEO_RAM_SETUP).int(@size)
|
|
58
|
+
end
|
|
59
|
+
|
|
55
60
|
private
|
|
56
61
|
|
|
62
|
+
def save_mapper(out)
|
|
63
|
+
out.blob(@ram_data).int(@block).int(@page).int(@window)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def load_mapper(input)
|
|
67
|
+
input.blob_into(@ram_data)
|
|
68
|
+
@block = input.int
|
|
69
|
+
@page = input.int
|
|
70
|
+
@window = input.int
|
|
71
|
+
end
|
|
72
|
+
|
|
57
73
|
def install_chips(_chips)
|
|
58
74
|
@ram_data = Array.new(@size * 1024, 0)
|
|
59
75
|
reset
|
|
@@ -20,6 +20,14 @@ module Badline
|
|
|
20
20
|
|
|
21
21
|
private
|
|
22
22
|
|
|
23
|
+
def save_mapper(out)
|
|
24
|
+
save_windows(out, [*@banks, EMPTY_BANK])
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def load_mapper(input)
|
|
28
|
+
load_windows(input, [*@banks, EMPTY_BANK])
|
|
29
|
+
end
|
|
30
|
+
|
|
23
31
|
def install_chips(chips)
|
|
24
32
|
@banks = banks_from(chips).first
|
|
25
33
|
@bank_mask = (bank_mask(@banks) | 0x03) & 0x7f
|
|
@@ -19,6 +19,14 @@ module Badline
|
|
|
19
19
|
|
|
20
20
|
private
|
|
21
21
|
|
|
22
|
+
def save_mapper(out)
|
|
23
|
+
save_windows(out, [*@banks, EMPTY_BANK])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def load_mapper(input)
|
|
27
|
+
load_windows(input, [*@banks, EMPTY_BANK])
|
|
28
|
+
end
|
|
29
|
+
|
|
22
30
|
# In 16K mode the selected bank shows through ROMH as well.
|
|
23
31
|
def select_bank(number)
|
|
24
32
|
@roml = bank(@banks, number)
|
|
@@ -29,6 +29,18 @@ module Badline
|
|
|
29
29
|
|
|
30
30
|
private
|
|
31
31
|
|
|
32
|
+
def save_mapper(out)
|
|
33
|
+
save_windows(out, windows)
|
|
34
|
+
@ram_banks.each { |ram| out.blob(ram.data) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def load_mapper(input)
|
|
38
|
+
load_windows(input, windows)
|
|
39
|
+
@ram_banks.each { |ram| input.blob_into(ram.data) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def windows = [*@roml_banks, *@romh_banks, *@ram_banks, @open_bus || EMPTY_BANK, EMPTY_BANK]
|
|
43
|
+
|
|
32
44
|
def select(value)
|
|
33
45
|
half = (value >> 1) & 0x01
|
|
34
46
|
chip = (value >> 2) & 0x03
|
|
@@ -2,6 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
class Cartridge
|
|
5
|
+
# A Retro Replay's registers, RAM and flash, for a snapshot.
|
|
6
|
+
module RetroReplayState
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def save_jumpers(out)
|
|
10
|
+
out.boolean(true).boolean(@flash_jumper).boolean(@bank_jumper)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def save_mapper(out)
|
|
14
|
+
[@active, @frozen, @phi1_ultimax, @ram_enabled, @ram_at_a000, @allow_bank, @no_freeze, @reu_mapping,
|
|
15
|
+
@write_once].each { |flag| out.boolean(flag) }
|
|
16
|
+
out.int(@bank).int(RetroReplay::CONTROL_MODES.index(@config))
|
|
17
|
+
@ram_data.each { |data| out.blob(data) }
|
|
18
|
+
@flash.save_state(out)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def load_mapper(input)
|
|
22
|
+
load_flags(input)
|
|
23
|
+
@bank = input.int
|
|
24
|
+
@config = RetroReplay::CONTROL_MODES.fetch(input.int)
|
|
25
|
+
@ram_data.each { |data| input.blob_into(data) }
|
|
26
|
+
@flash.load_state(input)
|
|
27
|
+
map_windows
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def load_flags(input)
|
|
31
|
+
@active = input.boolean?
|
|
32
|
+
@frozen = input.boolean?
|
|
33
|
+
@phi1_ultimax = input.boolean?
|
|
34
|
+
@ram_enabled = input.boolean?
|
|
35
|
+
@ram_at_a000 = input.boolean?
|
|
36
|
+
@allow_bank = input.boolean?
|
|
37
|
+
@no_freeze = input.boolean?
|
|
38
|
+
@reu_mapping = input.boolean?
|
|
39
|
+
@write_once = input.boolean?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
5
43
|
# Retro Replay: an Am29F010 flash chip of two 64K halves, each eight 8K
|
|
6
44
|
# ROM banks, and four 8K RAM banks, with the Action Replay's register at
|
|
7
45
|
# $DE00 and an extended one at $DE01. The Nordic Replay (subtype 1) adds
|
|
@@ -44,6 +82,7 @@ module Badline
|
|
|
44
82
|
# the half in bit 5.
|
|
45
83
|
class RetroReplay < Cartridge
|
|
46
84
|
include Freezer
|
|
85
|
+
include RetroReplayState
|
|
47
86
|
|
|
48
87
|
CONTROL_MODES = %i[rom8k rom16k off ultimax].freeze
|
|
49
88
|
IO1_PAGE = 0x1e00
|
|
@@ -212,11 +251,15 @@ module Badline
|
|
|
212
251
|
end
|
|
213
252
|
|
|
214
253
|
def configure
|
|
254
|
+
map_windows
|
|
255
|
+
changed!
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def map_windows
|
|
215
259
|
self.mode = @config
|
|
216
260
|
@rom = flash_window(@bank)
|
|
217
261
|
@roml = roml_window
|
|
218
262
|
@romh = romh_window
|
|
219
|
-
changed!
|
|
220
263
|
end
|
|
221
264
|
|
|
222
265
|
def roml_window
|
|
@@ -28,6 +28,16 @@ module Badline
|
|
|
28
28
|
|
|
29
29
|
private
|
|
30
30
|
|
|
31
|
+
def save_mapper(out)
|
|
32
|
+
save_windows(out, [*@banks, EMPTY_BANK])
|
|
33
|
+
out.boolean(@disabled)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def load_mapper(input)
|
|
37
|
+
load_windows(input, [*@banks, EMPTY_BANK])
|
|
38
|
+
@disabled = input.boolean?
|
|
39
|
+
end
|
|
40
|
+
|
|
31
41
|
def select(value)
|
|
32
42
|
value ^= 0x07 if @hucky
|
|
33
43
|
if value.anybits?(0x08)
|
|
@@ -20,6 +20,16 @@ module Badline
|
|
|
20
20
|
|
|
21
21
|
private
|
|
22
22
|
|
|
23
|
+
def save_mapper(out)
|
|
24
|
+
save_windows(out, [*@roml_banks, *@romh_banks, EMPTY_BANK])
|
|
25
|
+
out.boolean(@locked)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def load_mapper(input)
|
|
29
|
+
load_windows(input, [*@roml_banks, *@romh_banks, EMPTY_BANK])
|
|
30
|
+
@locked = input.boolean?
|
|
31
|
+
end
|
|
32
|
+
|
|
23
33
|
def select(value)
|
|
24
34
|
@roml = bank(@roml_banks, value & 0x03)
|
|
25
35
|
@romh = bank(@romh_banks, value & 0x03)
|
|
@@ -31,6 +31,14 @@ module Badline
|
|
|
31
31
|
def peek(addr)
|
|
32
32
|
@bank.peek(addr)
|
|
33
33
|
end
|
|
34
|
+
|
|
35
|
+
def save_state(out)
|
|
36
|
+
out.int([*@banks, EMPTY_BANK].index { |bank| bank.equal?(@bank) })
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def load_state(input)
|
|
40
|
+
@bank = [*@banks, EMPTY_BANK].fetch(input.int)
|
|
41
|
+
end
|
|
34
42
|
end
|
|
35
43
|
|
|
36
44
|
def reset
|
|
@@ -39,6 +47,14 @@ module Badline
|
|
|
39
47
|
|
|
40
48
|
private
|
|
41
49
|
|
|
50
|
+
def save_mapper(out)
|
|
51
|
+
@romh.save_state(out)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def load_mapper(input)
|
|
55
|
+
@romh.load_state(input)
|
|
56
|
+
end
|
|
57
|
+
|
|
42
58
|
def install_chips(chips)
|
|
43
59
|
roml, romh = banks_from(chips)
|
|
44
60
|
@romh = High.new(romh)
|
data/lib/badline/cartridge.rb
CHANGED
|
@@ -69,9 +69,28 @@ module Badline
|
|
|
69
69
|
end
|
|
70
70
|
mapper.new(crt, **)
|
|
71
71
|
end
|
|
72
|
+
|
|
73
|
+
# A cartridge built afresh from what save_setup wrote, before
|
|
74
|
+
# load_state puts its state in.
|
|
75
|
+
def from_setup(input)
|
|
76
|
+
return GeoRAM.new(size: input.int) if input.int == GEO_RAM_SETUP
|
|
77
|
+
|
|
78
|
+
crt = Storage::CRTFile.new(bytes: input.string)
|
|
79
|
+
return RetroReplay.new(crt, flash_jumper: input.boolean?, bank_jumper: input.boolean?) if input.boolean?
|
|
80
|
+
|
|
81
|
+
from_crt(crt)
|
|
82
|
+
end
|
|
72
83
|
end
|
|
73
84
|
|
|
85
|
+
# What save_setup writes first: a cartridge from a CRT image, or a
|
|
86
|
+
# GEO-RAM.
|
|
87
|
+
CRT_SETUP = 0
|
|
88
|
+
GEO_RAM_SETUP = 1
|
|
89
|
+
|
|
90
|
+
# Keeps the image it was built from, so a snapshot's machine can build
|
|
91
|
+
# the same cartridge before the snapshot's state goes into it.
|
|
74
92
|
def initialize(crt)
|
|
93
|
+
@crt = crt
|
|
75
94
|
@name = crt.name
|
|
76
95
|
@exrom = crt.exrom
|
|
77
96
|
@game = crt.game
|
|
@@ -88,6 +107,31 @@ module Badline
|
|
|
88
107
|
@on_change = block
|
|
89
108
|
end
|
|
90
109
|
|
|
110
|
+
# What builds the same cartridge afresh (Cartridge.from_setup): the CRT
|
|
111
|
+
# image it was built from, and the jumpers of a mapper that has them.
|
|
112
|
+
def save_setup(out)
|
|
113
|
+
out.int(CRT_SETUP).string(Storage::CRTFile.encode(@crt))
|
|
114
|
+
save_jumpers(out)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# The lines, the pull on NMI, the button and the mapper's own state.
|
|
118
|
+
# Loading sets them without calling back into the machine, which maps
|
|
119
|
+
# the cartridge again afterwards.
|
|
120
|
+
def save_state(out)
|
|
121
|
+
out.marker("CARTRIDGE")
|
|
122
|
+
out.int(@exrom).int(@game).boolean(@nmi).boolean(@button == true)
|
|
123
|
+
save_mapper(out)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def load_state(input)
|
|
127
|
+
input.marker("CARTRIDGE")
|
|
128
|
+
@exrom = input.int
|
|
129
|
+
@game = input.int
|
|
130
|
+
@nmi = input.boolean?
|
|
131
|
+
@button = input.boolean?
|
|
132
|
+
load_mapper(input)
|
|
133
|
+
end
|
|
134
|
+
|
|
91
135
|
# Called with the level of the cartridge's pull on the NMI line when it
|
|
92
136
|
# changes. The line is wired-OR with CIA 2's.
|
|
93
137
|
def on_nmi_change(&block)
|
|
@@ -142,6 +186,34 @@ module Badline
|
|
|
142
186
|
|
|
143
187
|
private
|
|
144
188
|
|
|
189
|
+
def save_jumpers(out)
|
|
190
|
+
out.boolean(false)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def save_mapper(_out); end
|
|
194
|
+
|
|
195
|
+
def load_mapper(_input); end
|
|
196
|
+
|
|
197
|
+
# Where the ROML and ROMH windows point, as their places in `windows`,
|
|
198
|
+
# the banks and views the mapper picks them from, or -1 for none.
|
|
199
|
+
def save_windows(out, windows)
|
|
200
|
+
out.int(window_index(@roml, windows)).int(window_index(@romh, windows))
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def load_windows(input, windows)
|
|
204
|
+
@roml = window_at(input.int, windows)
|
|
205
|
+
@romh = window_at(input.int, windows)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def window_index(window, windows)
|
|
209
|
+
return -1 if window.nil?
|
|
210
|
+
|
|
211
|
+
windows.index { |candidate| candidate.equal?(window) } ||
|
|
212
|
+
raise(ArgumentError, "#{self.class.name}: a window that isn't among its banks")
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def window_at(index, windows) = index.negative? ? nil : windows.fetch(index)
|
|
216
|
+
|
|
145
217
|
def nmi=(level)
|
|
146
218
|
return if @nmi == level
|
|
147
219
|
|
|
@@ -34,6 +34,23 @@ module Badline
|
|
|
34
34
|
|
|
35
35
|
def interrupted? = status.value >= 0x80
|
|
36
36
|
|
|
37
|
+
def save_state(out)
|
|
38
|
+
out.int(@mask.value).int(@status.value).int(@pending)
|
|
39
|
+
[@read, @read_last_cycle, @read_two_cycles_ago].each { |read| out.optional_int(read) }
|
|
40
|
+
out.boolean(@timer_b_bug).boolean(@quiet)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def load_state(input)
|
|
44
|
+
@mask.value = input.int
|
|
45
|
+
@status.value = input.int
|
|
46
|
+
@pending = input.int
|
|
47
|
+
@read = input.optional_int
|
|
48
|
+
@read_last_cycle = input.optional_int
|
|
49
|
+
@read_two_cycles_ago = input.optional_int
|
|
50
|
+
@timer_b_bug = input.boolean?
|
|
51
|
+
@quiet = input.boolean?
|
|
52
|
+
end
|
|
53
|
+
|
|
37
54
|
# Raises IR on this cycle.
|
|
38
55
|
def assert_now
|
|
39
56
|
status.interrupt = true
|