mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-05-09 17:04:03 -04:00
added support for stella.pro files
added properties package ROM select window display property information
This commit is contained in:
parent
58315e5182
commit
685bf7ccc7
|
@ -17,6 +17,7 @@ package cartridgeloader
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -65,6 +66,9 @@ type Loader struct {
|
|||
// string is empty then that check passes.
|
||||
Hash string
|
||||
|
||||
// HashMD5 is an alternative to hash for use with the properties package
|
||||
HashMD5 string
|
||||
|
||||
// does the Data field consist of sound (PCM) data
|
||||
IsSoundData bool
|
||||
|
||||
|
@ -320,6 +324,7 @@ func NewLoaderFromEmbed(name string, data []byte, mapping string) (Loader, error
|
|||
Data: &data,
|
||||
embedded: true,
|
||||
Hash: fmt.Sprintf("%x", sha1.Sum(data)),
|
||||
HashMD5: fmt.Sprintf("%x", md5.Sum(data)),
|
||||
NotificationHook: func(cart mapper.CartMapper, notice notifications.Notify, args ...interface{}) error {
|
||||
return nil
|
||||
},
|
||||
|
@ -450,5 +455,12 @@ func (cl *Loader) Load() error {
|
|||
}
|
||||
cl.Hash = hash
|
||||
|
||||
// generate md5 hash and check for consistency
|
||||
hashmd5 := fmt.Sprintf("%x", md5.Sum(*cl.Data))
|
||||
if cl.HashMD5 != "" && cl.HashMD5 != hashmd5 {
|
||||
return fmt.Errorf("cartridgeloader: unexpected hash value")
|
||||
}
|
||||
cl.HashMD5 = hashmd5
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ import (
|
|||
"github.com/jetsetilly/gopher2600/patch"
|
||||
"github.com/jetsetilly/gopher2600/prefs"
|
||||
"github.com/jetsetilly/gopher2600/preview"
|
||||
"github.com/jetsetilly/gopher2600/properties"
|
||||
"github.com/jetsetilly/gopher2600/recorder"
|
||||
"github.com/jetsetilly/gopher2600/reflection"
|
||||
"github.com/jetsetilly/gopher2600/reflection/counter"
|
||||
|
@ -120,6 +121,9 @@ type Debugger struct {
|
|||
term terminal.Terminal
|
||||
controllers *userinput.Controllers
|
||||
|
||||
// stella.pro file support
|
||||
pro properties.Properties
|
||||
|
||||
// bots coordinator
|
||||
bots *wrangler.Bots
|
||||
|
||||
|
@ -364,6 +368,12 @@ func NewDebugger(opts CommandLineOptions, create CreateUserInterface) (*Debugger
|
|||
// create bot coordinator
|
||||
dbg.bots = wrangler.NewBots(dbg.vcs.Input, dbg.vcs.TV)
|
||||
|
||||
// stella.pro support
|
||||
dbg.pro, err = properties.Load()
|
||||
if err != nil {
|
||||
logger.Logf("debugger", err.Error())
|
||||
}
|
||||
|
||||
// create preview emulation
|
||||
dbg.preview, err = preview.NewEmulation(dbg.vcs.Env.Prefs)
|
||||
if err != nil {
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/jetsetilly/gopher2600/debugger/govern"
|
||||
"github.com/jetsetilly/gopher2600/disassembly"
|
||||
"github.com/jetsetilly/gopher2600/logger"
|
||||
"github.com/jetsetilly/gopher2600/properties"
|
||||
)
|
||||
|
||||
// PushFunction onto the event queue. Used to ensure that the events are
|
||||
|
@ -72,3 +73,11 @@ func (dbg *Debugger) PushTogglePCBreak(e *disassembly.Entry) {
|
|||
dbg.halting.breakpoints.togglePCBreak(f)
|
||||
})
|
||||
}
|
||||
|
||||
// PushPropertyLookup looks up the supplied MD5 hash in the properties table
|
||||
func (dbg *Debugger) PushPropertyLookup(hashMD5 string, result chan properties.Entry) {
|
||||
dbg.PushFunctionImmediate(func() {
|
||||
e, _ := dbg.pro.Lookup(hashMD5)
|
||||
result <- e
|
||||
})
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||
"github.com/jetsetilly/gopher2600/hardware/television/specification"
|
||||
"github.com/jetsetilly/gopher2600/logger"
|
||||
"github.com/jetsetilly/gopher2600/properties"
|
||||
"github.com/jetsetilly/gopher2600/thumbnailer"
|
||||
)
|
||||
|
||||
|
@ -43,7 +44,12 @@ type winSelectROM struct {
|
|||
entries []os.DirEntry
|
||||
err error
|
||||
|
||||
selectedFile string
|
||||
selectedFile string
|
||||
selectedFileBase string
|
||||
loader cartridgeloader.Loader
|
||||
properties properties.Entry
|
||||
propertiesOpen bool
|
||||
|
||||
showAllFiles bool
|
||||
showHidden bool
|
||||
|
||||
|
@ -58,15 +64,20 @@ type winSelectROM struct {
|
|||
|
||||
thmbImage *image.RGBA
|
||||
thmbDimensions image.Point
|
||||
|
||||
// the return channel from the emulation goroutine for the property lookup
|
||||
// for the selected cartridge
|
||||
propertyResult chan properties.Entry
|
||||
}
|
||||
|
||||
func newSelectROM(img *SdlImgui) (window, error) {
|
||||
win := &winSelectROM{
|
||||
img: img,
|
||||
showAllFiles: false,
|
||||
showHidden: false,
|
||||
scrollToTop: true,
|
||||
centreOnFile: true,
|
||||
img: img,
|
||||
showAllFiles: false,
|
||||
showHidden: false,
|
||||
scrollToTop: true,
|
||||
centreOnFile: true,
|
||||
propertyResult: make(chan properties.Entry, 1),
|
||||
}
|
||||
win.debuggerGeom.noFousTracking = true
|
||||
|
||||
|
@ -219,6 +230,12 @@ func (win *winSelectROM) render() {
|
|||
}
|
||||
|
||||
func (win *winSelectROM) draw() {
|
||||
// check for new property information
|
||||
select {
|
||||
case win.properties = <-win.propertyResult:
|
||||
default:
|
||||
}
|
||||
|
||||
// reset centreOnFile at end of draw
|
||||
defer func() {
|
||||
win.centreOnFile = false
|
||||
|
@ -310,7 +327,7 @@ func (win *winSelectROM) draw() {
|
|||
}
|
||||
|
||||
if fi.Mode().IsRegular() {
|
||||
selected := f.Name() == filepath.Base(win.selectedFile)
|
||||
selected := f.Name() == win.selectedFileBase
|
||||
|
||||
if selected && win.centreOnFile {
|
||||
imgui.SetScrollHereY(0.0)
|
||||
|
@ -337,19 +354,75 @@ func (win *winSelectROM) draw() {
|
|||
|
||||
// control buttons. start controlHeight measurement
|
||||
win.controlHeight = imguiMeasureHeight(func() {
|
||||
if win.thmb.IsEmulating() {
|
||||
imgui.Text(win.thmb.String())
|
||||
} else {
|
||||
imgui.Text("")
|
||||
}
|
||||
func() {
|
||||
if !win.thmb.IsEmulating() {
|
||||
imgui.PushItemFlag(imgui.ItemFlagsDisabled, true)
|
||||
imgui.PushStyleVarFloat(imgui.StyleVarAlpha, disabledAlpha)
|
||||
defer imgui.PopItemFlag()
|
||||
defer imgui.PopStyleVar()
|
||||
}
|
||||
|
||||
imgui.SetNextItemOpen(win.propertiesOpen, imgui.ConditionAlways)
|
||||
if !imgui.CollapsingHeaderV(win.selectedFileBase, imgui.TreeNodeFlagsNone) {
|
||||
win.propertiesOpen = false
|
||||
} else {
|
||||
win.propertiesOpen = true
|
||||
if win.properties.IsValid() {
|
||||
if imgui.BeginTable("#properties", 2) {
|
||||
imgui.TableSetupColumnV("#category", imgui.TableColumnFlagsWidthFixed, -1, 0)
|
||||
imgui.TableSetupColumnV("#detail", imgui.TableColumnFlagsWidthFixed, -1, 1)
|
||||
|
||||
imgui.TableNextRow()
|
||||
imgui.TableNextColumn()
|
||||
imgui.Text("Name")
|
||||
imgui.TableNextColumn()
|
||||
imgui.Text(win.properties.Name)
|
||||
|
||||
if win.properties.Manufacturer != "" {
|
||||
imgui.TableNextRow()
|
||||
imgui.TableNextColumn()
|
||||
imgui.Text("Manufacturer")
|
||||
imgui.TableNextColumn()
|
||||
imgui.Text(win.properties.Manufacturer)
|
||||
}
|
||||
if win.properties.Rarity != "" {
|
||||
imgui.TableNextRow()
|
||||
imgui.TableNextColumn()
|
||||
imgui.Text("Rarity")
|
||||
imgui.TableNextColumn()
|
||||
imgui.Text(win.properties.Rarity)
|
||||
}
|
||||
if win.properties.Model != "" {
|
||||
imgui.TableNextRow()
|
||||
imgui.TableNextColumn()
|
||||
imgui.Text("Model")
|
||||
imgui.TableNextColumn()
|
||||
imgui.Text(win.properties.Model)
|
||||
}
|
||||
|
||||
if win.properties.Note != "" {
|
||||
imgui.TableNextRow()
|
||||
imgui.TableNextColumn()
|
||||
imgui.Text("Note")
|
||||
imgui.TableNextColumn()
|
||||
imgui.Text(win.properties.Note)
|
||||
}
|
||||
|
||||
imgui.EndTable()
|
||||
}
|
||||
} else {
|
||||
imgui.Text("No information")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
imguiSeparator()
|
||||
|
||||
imgui.Checkbox("Show all files", &win.showAllFiles)
|
||||
imgui.SameLine()
|
||||
imgui.Checkbox("Show hidden files", &win.showHidden)
|
||||
// imgui.Checkbox("Show all files", &win.showAllFiles)
|
||||
// imgui.SameLine()
|
||||
// imgui.Checkbox("Show hidden files", &win.showHidden)
|
||||
|
||||
imgui.Spacing()
|
||||
// imgui.Spacing()
|
||||
|
||||
if imgui.Button("Cancel") {
|
||||
// close rom selected in both the debugger and playmode
|
||||
|
@ -363,9 +436,9 @@ func (win *winSelectROM) draw() {
|
|||
|
||||
// load or reload button
|
||||
if win.selectedFile == win.img.cache.VCS.Mem.Cart.Filename {
|
||||
s = fmt.Sprintf("Reload %s", filepath.Base(win.selectedFile))
|
||||
s = fmt.Sprintf("Reload %s", win.selectedFileBase)
|
||||
} else {
|
||||
s = fmt.Sprintf("Load %s", filepath.Base(win.selectedFile))
|
||||
s = fmt.Sprintf("Load %s", win.selectedFileBase)
|
||||
}
|
||||
|
||||
// only show load cartridge button if the file is being
|
||||
|
@ -419,15 +492,28 @@ func (win *winSelectROM) setSelectedFile(filename string) {
|
|||
// update selected file. return immediately if the filename is empty
|
||||
win.selectedFile = filename
|
||||
if filename == "" {
|
||||
win.selectedFileBase = ""
|
||||
return
|
||||
}
|
||||
|
||||
// base filename for presentation purposes
|
||||
win.selectedFileBase = filepath.Base(filename)
|
||||
|
||||
var err error
|
||||
|
||||
// create cartridge loader and start thumbnail emulation
|
||||
cartload, err := cartridgeloader.NewLoader(filename, "AUTO")
|
||||
win.loader, err = cartridgeloader.NewLoader(filename, "AUTO")
|
||||
if err != nil {
|
||||
logger.Logf("ROM Select", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
win.thmb.Create(cartload, thumbnailer.UndefinedNumFrames)
|
||||
// push function to emulation goroutine. result will be checked for in
|
||||
// draw() function
|
||||
if err := win.loader.Load(); err == nil {
|
||||
win.img.dbg.PushPropertyLookup(win.loader.HashMD5, win.propertyResult)
|
||||
}
|
||||
|
||||
// create thumbnail animation
|
||||
win.thmb.Create(win.loader, thumbnailer.UndefinedNumFrames)
|
||||
}
|
||||
|
|
18
properties/doc.go
Normal file
18
properties/doc.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
// This file is part of Gopher2600.
|
||||
//
|
||||
// Gopher2600 is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Gopher2600 is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Package properties adds support for the Stella properties file, named
|
||||
// stella.pro. The file should be placed in the Gopher2600 configuration folder
|
||||
package properties
|
132
properties/properties.go
Normal file
132
properties/properties.go
Normal file
|
@ -0,0 +1,132 @@
|
|||
// This file is part of Gopher2600.
|
||||
//
|
||||
// Gopher2600 is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Gopher2600 is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package properties
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/jetsetilly/gopher2600/logger"
|
||||
"github.com/jetsetilly/gopher2600/resources"
|
||||
)
|
||||
|
||||
const propertiesFile = "stella.pro"
|
||||
|
||||
type Entry struct {
|
||||
Hash string
|
||||
Name string
|
||||
Manufacturer string
|
||||
Note string
|
||||
Rarity string
|
||||
Model string
|
||||
}
|
||||
|
||||
func (e Entry) IsValid() bool {
|
||||
return len(e.Hash) > 0
|
||||
}
|
||||
|
||||
type Properties struct {
|
||||
available bool
|
||||
entries map[string]Entry
|
||||
}
|
||||
|
||||
func Load() (Properties, error) {
|
||||
pro := Properties{
|
||||
entries: make(map[string]Entry),
|
||||
}
|
||||
|
||||
path, err := resources.JoinPath(propertiesFile)
|
||||
if err != nil {
|
||||
return Properties{}, fmt.Errorf("properties: %w", err)
|
||||
}
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return Properties{}, fmt.Errorf("properties: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner := bufio.NewScanner(f)
|
||||
|
||||
var entry Entry
|
||||
var line int
|
||||
var rejected int
|
||||
|
||||
for scanner.Scan() {
|
||||
line++
|
||||
|
||||
flds := strings.SplitN(scanner.Text(), " ", 2)
|
||||
if len(flds) < 2 {
|
||||
continue // for loop
|
||||
}
|
||||
|
||||
flds[0] = strings.Trim(flds[0], "\"")
|
||||
flds[1] = strings.Trim(flds[1], "\"")
|
||||
switch strings.ToUpper(flds[0]) {
|
||||
case "CART.MD5":
|
||||
// create new entry
|
||||
if len(entry.Hash) == 32 {
|
||||
pro.entries[entry.Hash] = entry
|
||||
}
|
||||
|
||||
// prepare new entry if has is valid
|
||||
if len(flds[1]) != 32 {
|
||||
logger.Logf("properties", "invalid hash entry at line %d", line)
|
||||
rejected++
|
||||
} else {
|
||||
entry = Entry{
|
||||
Hash: flds[1],
|
||||
}
|
||||
}
|
||||
|
||||
case "CART.NAME":
|
||||
entry.Name = flds[1]
|
||||
|
||||
case "CART.MANUFACTURER":
|
||||
entry.Manufacturer = flds[1]
|
||||
|
||||
case "CART.NOTE":
|
||||
entry.Note = flds[1]
|
||||
|
||||
case "CART.RARITY":
|
||||
entry.Rarity = flds[1]
|
||||
|
||||
case "CART.MODEL":
|
||||
entry.Model = flds[1]
|
||||
}
|
||||
}
|
||||
|
||||
if err = scanner.Err(); err != nil {
|
||||
return Properties{}, fmt.Errorf("pro: %w", err)
|
||||
}
|
||||
|
||||
logger.Logf("properties", "%d entries loaded", len(pro.entries))
|
||||
if rejected > 0 {
|
||||
logger.Logf("properties", "%d entries rejected", rejected)
|
||||
}
|
||||
|
||||
return pro, nil
|
||||
}
|
||||
|
||||
// Find the property entry for the ROM with the supplied md5 hash
|
||||
func (pro Properties) Lookup(md5Hash string) (Entry, bool) {
|
||||
if e, ok := pro.entries[md5Hash]; ok {
|
||||
return e, true
|
||||
}
|
||||
return Entry{}, false
|
||||
}
|
|
@ -55,6 +55,9 @@ type Anim struct {
|
|||
emulationCompleted chan bool
|
||||
|
||||
Render chan *image.RGBA
|
||||
|
||||
snapshot chan bool
|
||||
snapshotVCS chan *hardware.VCS
|
||||
}
|
||||
|
||||
// NewAnim is the preferred method of initialisation for the Anim type
|
||||
|
@ -63,6 +66,8 @@ func NewAnim(prefs *preferences.Preferences) (*Anim, error) {
|
|||
emulationQuit: make(chan bool, 1),
|
||||
emulationCompleted: make(chan bool, 1),
|
||||
Render: make(chan *image.RGBA, 60),
|
||||
snapshot: make(chan bool, 1),
|
||||
snapshotVCS: make(chan *hardware.VCS, 1),
|
||||
}
|
||||
|
||||
// emulation has completed, by definition, on startup
|
||||
|
@ -211,6 +216,7 @@ func (thmb *Anim) Create(cartload cartridgeloader.Loader, numFrames int) {
|
|||
select {
|
||||
case <-thmb.emulationQuit:
|
||||
return govern.Ending, nil
|
||||
case <-thmb.snapshot:
|
||||
default:
|
||||
}
|
||||
|
||||
|
@ -323,3 +329,9 @@ func (thmb *Anim) Reset() {
|
|||
func (thmb *Anim) EndRendering() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Snapshot safely returns a copy of the emulation
|
||||
func (thmb *Anim) Snapshot() *hardware.VCS {
|
||||
thmb.snapshot <- true
|
||||
return <-thmb.snapshotVCS
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue