Skip to content

Commit b76abf6

Browse files
committed
sketched in savekey support
-savekey argument added to play and debug mode savekey window available in debug menu
1 parent fcd297e commit b76abf6

File tree

17 files changed

+738
-35
lines changed

17 files changed

+738
-35
lines changed

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ In addition to the debugger, `Gopher2600` can [record and playback gameplay sess
2323

2424
The gameplay playback feature is also used in the inbuilt [regression database](#regression-database). This database allow for easy testing of the emulator's integrity and was of invaluable use during development of the emulator. This feature will also be of use perhaps when developing new ROMs for the Atari 2600 - a way of recording the ideal output of the ROM for future comparison.
2525

26-
The Atari 2600 comes with a variety of [hand controllers](#hand-controllers) and `Gopher2600` does it's very best to automatically select the correct input device. This is an feature that will be expanded on greatly in the future but currently joysticks, paddles and keyboard are all supported.
26+
The Atari 2600 comes with a variety of [hand controllers](#hand-controllers) and `Gopher2600` does it's very best to automatically select the correct input device. `Gopher2600` also supports the [SaveKey](#savekey) peripheral.
2727

2828
`Gopher2600` supports a variety of cartridge formats including [Supercharger](#supercharger-roms) from MP3 or WAV files. It supports `DPC+` format although there is no support for the ARM as yet. Most other formats are also supported.
2929

@@ -281,6 +281,22 @@ The file can be placed in the current working directory or in the same
281281
directory as the supercharger ROM being loaded. Alternatively, it can be placed
282282
in the emulator's [configuration directory](#configuration-directory).
283283

284+
## SaveKey
285+
286+
`Gopher2600` has basic support for the `SaveKey` peripheral. This will be
287+
expanded on in the future.
288+
289+
For now, the presence of the peripheral must be specified with the `-savekey`
290+
arugment. This is only available in `play` and `debug` mode. The simplest
291+
invocation to load a ROM with the `SaveKey` peripheral:
292+
293+
> gopher2600 -savekey roms/mgd.bin
294+
295+
Note that the `SaveKey` will always be inserted in the second player port.
296+
297+
Data saved to the `SaveKey` will be saved in the [configuration directory](#configuration-directory) to the
298+
binary file named simply, `savekey`.
299+
284300
## Recording Gameplay
285301

286302
`Gopher2600` can record all user input and playback for future viewing. This is a very efficient way

debugger/debugger.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import (
3030
"github.com/jetsetilly/gopher2600/hardware"
3131
"github.com/jetsetilly/gopher2600/hardware/cpu/execution"
3232
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/banks"
33+
"github.com/jetsetilly/gopher2600/hardware/riot/ports"
34+
"github.com/jetsetilly/gopher2600/hardware/riot/ports/savekey"
3335
"github.com/jetsetilly/gopher2600/logger"
3436
"github.com/jetsetilly/gopher2600/reflection"
3537
"github.com/jetsetilly/gopher2600/setup"
@@ -140,7 +142,7 @@ type Debugger struct {
140142

141143
// NewDebugger creates and initialises everything required for a new debugging
142144
// session. Use the Start() method to actually begin the session.
143-
func NewDebugger(tv television.Television, scr gui.GUI, term terminal.Terminal) (*Debugger, error) {
145+
func NewDebugger(tv television.Television, scr gui.GUI, term terminal.Terminal, useSavekey bool) (*Debugger, error) {
144146
var err error
145147

146148
dbg := &Debugger{
@@ -158,6 +160,14 @@ func NewDebugger(tv television.Television, scr gui.GUI, term terminal.Terminal)
158160
return nil, errors.New(errors.DebuggerError, err)
159161
}
160162

163+
// replace player 1 port with savekey
164+
if useSavekey {
165+
err = dbg.VCS.RIOT.Ports.AttachPlayer(ports.Player1ID, savekey.NewSaveKey)
166+
if err != nil {
167+
return nil, errors.New(errors.DebuggerError, err)
168+
}
169+
}
170+
161171
// create a new disassembly instance
162172
dbg.Disasm, err = disassembly.NewDisassembly()
163173
if err != nil {

debugger/debugger_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ func (trm *mockTerm) testSequence() {
217217
func TestDebugger_withNonExistantInitScript(t *testing.T) {
218218
trm := newMockTerm(t)
219219

220-
dbg, err := debugger.NewDebugger(&mockTV{}, &mockGUI{}, trm)
220+
dbg, err := debugger.NewDebugger(&mockTV{}, &mockGUI{}, trm, false)
221221
if err != nil {
222222
t.Fatalf(err.Error())
223223
}
@@ -233,7 +233,7 @@ func TestDebugger_withNonExistantInitScript(t *testing.T) {
233233
func TestDebugger(t *testing.T) {
234234
trm := newMockTerm(t)
235235

236-
dbg, err := debugger.NewDebugger(&mockTV{}, &mockGUI{}, trm)
236+
dbg, err := debugger.NewDebugger(&mockTV{}, &mockGUI{}, trm, false)
237237
if err != nil {
238238
t.Fatalf(err.Error())
239239
}

gopher2600.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ func play(md *modalflag.Modes, sync *mainSync) error {
241241
patchFile := md.AddString("patch", "", "patch file to apply (cartridge args only)")
242242
hiscore := md.AddBool("hiscore", false, "contact hiscore server [EXPERIMENTAL]")
243243
log := md.AddBool("log", false, "echo debugging log to stdout")
244+
useSavekey := md.AddBool("savekey", false, "use savekey in player 1 port")
244245

245246
p, err := md.Parse()
246247
if err != nil || p != modalflag.ParseContinue {
@@ -313,7 +314,7 @@ func play(md *modalflag.Modes, sync *mainSync) error {
313314
}
314315
}
315316

316-
err = playmode.Play(tv, scr, *record, cartload, *patchFile, *hiscore)
317+
err = playmode.Play(tv, scr, *record, cartload, *patchFile, *hiscore, *useSavekey)
317318
if err != nil {
318319
return err
319320
}
@@ -348,6 +349,7 @@ func debug(md *modalflag.Modes, sync *mainSync) error {
348349
termType := md.AddString("term", "IMGUI", "terminal type to use in debug mode: IMGUI, COLOR, PLAIN")
349350
initScript := md.AddString("initscript", defInitScript, "script to run on debugger start")
350351
profile := md.AddBool("profile", false, "run debugger through cpu profiler")
352+
useSavekey := md.AddBool("savekey", false, "use savekey in player 1 port")
351353

352354
p, err := md.Parse()
353355
if err != nil || p != modalflag.ParseContinue {
@@ -410,7 +412,7 @@ func debug(md *modalflag.Modes, sync *mainSync) error {
410412
sync.state <- reqNoIntSig
411413

412414
// prepare new debugger instance
413-
dbg, err := debugger.NewDebugger(tv, scr, term)
415+
dbg, err := debugger.NewDebugger(tv, scr, term, *useSavekey)
414416
if err != nil {
415417
return err
416418
}

gui/sdlimgui/lazyvalues/lazy.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ type Lazy struct {
5252
Collisions *LazyCollisions
5353
ChipRegisters *LazyChipRegisters
5454
Log *LazyLog
55+
SaveKey *LazySaveKey
5556

5657
// \/\/\/ the following are updated on demand rather than through the update
5758
// function, because they require more context
@@ -91,6 +92,7 @@ func NewValues() *Lazy {
9192
val.Collisions = newLazyCollisions(val)
9293
val.ChipRegisters = newLazyChipRegisters(val)
9394
val.Log = newLazyLog(val)
95+
val.SaveKey = newLazySaveKey(val)
9496

9597
// allocating enough space for every byte in cartridge space. not worrying
9698
// about bank sizes or anything like that.
@@ -144,6 +146,7 @@ func (val *Lazy) Update() {
144146
val.Collisions.update()
145147
val.ChipRegisters.update()
146148
val.Log.update()
149+
val.SaveKey.update()
147150
}
148151

149152
// HasBreak checks to see if disassembly entry has a break point

gui/sdlimgui/lazyvalues/savekey.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// This file is part of Gopher2600.
2+
//
3+
// Gopher2600 is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License as published by
5+
// the Free Software Foundation, either version 3 of the License, or
6+
// (at your option) any later version.
7+
//
8+
// Gopher2600 is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU General Public License
14+
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
15+
16+
package lazyvalues
17+
18+
import (
19+
"sync/atomic"
20+
21+
"github.com/jetsetilly/gopher2600/hardware/riot/ports/savekey"
22+
)
23+
24+
// LazyChipRegisters lazily accesses chip registere information from the emulator
25+
type LazySaveKey struct {
26+
val *Lazy
27+
28+
SaveKeyActive bool
29+
30+
atomicSDA atomic.Value // []float32
31+
atomicSCL atomic.Value // []float32
32+
SDA []float32
33+
SCL []float32
34+
}
35+
36+
func newLazySaveKey(val *Lazy) *LazySaveKey {
37+
return &LazySaveKey{val: val}
38+
}
39+
40+
func (lz *LazySaveKey) update() {
41+
lz.val.Dbg.PushRawEvent(func() {
42+
if l, ok := lz.val.Dbg.VCS.RIOT.Ports.Player1.(*savekey.SaveKey); ok {
43+
lz.atomicSDA.Store(l.SDA.Copy())
44+
lz.atomicSCL.Store(l.SCL.Copy())
45+
}
46+
})
47+
48+
if l, ok := lz.atomicSDA.Load().([]float32); ok {
49+
lz.SaveKeyActive = ok
50+
lz.SDA = l
51+
}
52+
53+
if l, ok := lz.atomicSCL.Load().([]float32); ok {
54+
lz.SaveKeyActive = ok
55+
lz.SCL = l
56+
}
57+
}

gui/sdlimgui/win_savekey.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// This file is part of Gopher2600.
2+
//
3+
// Gopher2600 is free software: you can redistribute it and/or modify
4+
// it under the terms of the GNU General Public License as published by
5+
// the Free Software Foundation, either version 3 of the License, or
6+
// (at your option) any later version.
7+
//
8+
// Gopher2600 is distributed in the hope that it will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
// GNU General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU General Public License
14+
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
15+
16+
package sdlimgui
17+
18+
import (
19+
"github.com/inkyblackness/imgui-go/v2"
20+
"github.com/jetsetilly/gopher2600/hardware/riot/ports/savekey"
21+
)
22+
23+
const winSaveKeyTitle = "SaveKey (work in progress)"
24+
25+
type winSaveKey struct {
26+
windowManagement
27+
widgetDimensions
28+
29+
img *SdlImgui
30+
}
31+
32+
func newWinSaveKey(img *SdlImgui) (managedWindow, error) {
33+
win := &winSaveKey{
34+
img: img,
35+
}
36+
37+
return win, nil
38+
}
39+
40+
func (win *winSaveKey) init() {
41+
win.widgetDimensions.init()
42+
}
43+
44+
func (win *winSaveKey) destroy() {
45+
}
46+
47+
func (win *winSaveKey) id() string {
48+
return winSaveKeyTitle
49+
}
50+
51+
func (win *winSaveKey) draw() {
52+
if !win.open {
53+
return
54+
}
55+
56+
imgui.SetNextWindowPosV(imgui.Vec2{633, 358}, imgui.ConditionFirstUseEver, imgui.Vec2{0, 0})
57+
imgui.BeginV(winSaveKeyTitle, &win.open, imgui.WindowFlagsAlwaysAutoResize)
58+
59+
// oscilloscope
60+
imgui.PushStyleColor(imgui.StyleColorFrameBg, win.img.cols.AudioOscBg)
61+
62+
imgui.PushStyleColor(imgui.StyleColorPlotLines, imgui.Vec4{1.0, 0.0, 0.0, 1.0})
63+
imgui.PlotLinesV("", win.img.lz.SaveKey.SCL, 0, "SCL", savekey.TraceLo, savekey.TraceHi, imgui.Vec2{X: 512, Y: imgui.FrameHeight() * 2})
64+
65+
imgui.PushStyleColor(imgui.StyleColorPlotLines, imgui.Vec4{0.0, 1.0, 0.0, 1.0})
66+
imgui.PlotLinesV("", win.img.lz.SaveKey.SDA, 0, "SDA", savekey.TraceLo, savekey.TraceHi, imgui.Vec2{X: 512, Y: imgui.FrameHeight() * 2})
67+
68+
imgui.PopStyleColorV(3)
69+
70+
imgui.End()
71+
}

gui/sdlimgui/windowmanager.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,10 @@ type windowManager struct {
6666

6767
// the window menus grouped by type. the types are:
6868
const (
69-
windowMenuDebugger = "Debugger"
70-
windowMenuVCS = "VCS"
71-
windowMenuCart = "Cartridge"
69+
windowMenuDebugger = "Debugger"
70+
windowMenuVCS = "VCS"
71+
windowMenuCart = "Cartridge"
72+
windowMenuPeripherals = "Peripherals"
7273

7374
// additional window menus are grouped by cartridge type
7475
)
@@ -165,6 +166,9 @@ func newWindowManager(img *SdlImgui) (*windowManager, error) {
165166
if err := addWindow(newWinCartTape, false, windowMenuCart); err != nil {
166167
return nil, err
167168
}
169+
if err := addWindow(newWinSaveKey, false, windowMenuPeripherals); err != nil {
170+
return nil, err
171+
}
168172

169173
// associate cartridge types with cartridge specific menus. using cartridge
170174
// ID as the key in the windowMenu map
@@ -289,6 +293,15 @@ func (wm *windowManager) drawMenu() {
289293
}
290294
}
291295

296+
// add savekey specific menu
297+
if wm.img.lz.SaveKey.SaveKeyActive {
298+
if imgui.BeginMenu("SaveKey") {
299+
wm.drawMenuWindowEntry(wm.windows[winSaveKeyTitle], winSaveKeyTitle)
300+
imgui.EndMenu()
301+
}
302+
}
303+
304+
// filename in titlebar
292305
imgui.SameLineV(imgui.WindowWidth()-imguiGetFrameDim(wm.img.lz.Cart.Filename).X-20.0, 0.0)
293306
imgui.Text(wm.img.lz.Cart.Filename)
294307

hardware/riot/ports/peripherals.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ const (
3131
PanelID
3232
)
3333

34+
func (id *PortID) String() string {
35+
switch *id {
36+
case Player0ID:
37+
return "player 0"
38+
case Player1ID:
39+
return "player 1"
40+
case PanelID:
41+
return "panel"
42+
}
43+
44+
return "not attached"
45+
}
46+
3447
// Peripheral represents a (input or output) device that can attached to the
3548
// VCS ports.
3649
type Peripheral interface {

0 commit comments

Comments
 (0)