Gopher2600/hardware/memory/cartridge/plusrom/plusrom.go
JetSetIlly d32262adff simplified how gui implements and handles notifications
debugger no longer sends play, pause notifications to the gui. the gui
polls for that information as required

govern package now has SubState type to complement the State type.
StateIntegrity() function enforces combinations of State and SubState,
called from debugger.setState() function

playmode notifications reworked and contained in a single playmode_overlay.go
file. this includes the FPS and screen detail

preference value sdlimgui.playmode.fpsOverlay replaced with
sdlimgui.playmode.fpsDetail. still toggled with F7 key

coproc icon moved to top-left corner of playmode overlay and only
visible when FPS detail is showing

when FPS detail is showing multiple (small) icons care shown. when it is
not showing, a single (large) icon is shown according to the priority of
the icon. eg. pause indicator has higher priority than the mute
indicator
2024-04-12 18:20:29 +01:00

331 lines
9.2 KiB
Go

// 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 plusrom
import (
"errors"
"fmt"
"strings"
"github.com/jetsetilly/gopher2600/environment"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
"github.com/jetsetilly/gopher2600/logger"
"github.com/jetsetilly/gopher2600/notifications"
)
// sentinal errors indicating a specific problem with the attempt to load the
// child cartridge into the PlusROM.
var NotAPlusROM = errors.New("not a plus rom")
var CannotAdoptROM = errors.New("cannot adopt ROM")
// PlusROM wraps another mapper.CartMapper inside a network aware format.
type PlusROM struct {
env *environment.Environment
net *network
state *state
// rewind boundary is indicated on every network activity
rewindBoundary bool
}
// rewindable state for the 3e cartridge.
type state struct {
child mapper.CartMapper
}
// Snapshot implements the mapper.CartMapper interface.
func (s *state) Snapshot() *state {
n := *s
n.child = s.child.Snapshot()
return &n
}
// Plumb implements the mapper.CartMapper interface.
func (s *state) Plumb(env *environment.Environment) {
s.child.Plumb(env)
}
func NewPlusROM(env *environment.Environment, child mapper.CartMapper) (mapper.CartMapper, error) {
cart := &PlusROM{env: env}
cart.state = &state{}
cart.state.child = child
cart.net = newNetwork(cart.env)
// get reference to last bank
banks := child.CopyBanks()
if banks == nil {
return nil, fmt.Errorf("plusrom: %w: %s is not providing bank data", CannotAdoptROM, child.ID())
}
lastBank := banks[cart.NumBanks()-1]
// host/path information is found at address 0x1ffa. we've got a reference
// to the last bank above but we need to consider that the last bank might not
// take the entirity of the cartridge map.
addrMask := uint16(len(lastBank.Data) - 1)
// host/path information are found at the address pointed to by the following
// 16bit address
const addrinfoMSB = 0x1ffb
const addrinfoLSB = 0x1ffa
a := uint16(lastBank.Data[addrinfoLSB&addrMask])
a |= (uint16(lastBank.Data[addrinfoMSB&addrMask]) << 8)
// get bank to which the NMI vector points
b := int((a & 0xf000) >> 12)
if b == 0 || b > cart.NumBanks() {
return nil, fmt.Errorf("plusrom: %w: invalid NMI vector", NotAPlusROM)
}
// normalise indirect address so it's suitable for indexing bank data
a &= addrMask
// get the bank to which the NMI vector points
lastBank = child.CopyBanks()[b-1]
// read path string from the first bank using the indirect address retrieved above
path := strings.Builder{}
for path.Len() < maxPathLength {
if int(a) >= len(lastBank.Data) {
a = 0x0000
}
c := lastBank.Data[a]
a++
if c == 0x00 {
break // for loop
}
path.WriteRune(rune(c))
}
// read host string. this string continues on from the path string. the
// address pointer will be in the correct place.
host := strings.Builder{}
for host.Len() <= maxHostLength {
if int(a) >= len(lastBank.Data) {
a = 0x0000
}
c := lastBank.Data[a]
a++
if c == 0x00 {
break // for loop
}
host.WriteRune(rune(c))
}
// fail if host or path is not valid
hostValid, pathValid := cart.SetAddrInfo(host.String(), path.String())
if !hostValid || !pathValid {
return nil, fmt.Errorf("%w: invalid host/path", NotAPlusROM)
}
// log success
logger.Logf("plusrom", "will connect to %s", cart.net.ai.String())
if cart.env.Prefs.PlusROM.NewInstallation {
err := cart.env.Notifications.Notify(notifications.NotifyPlusROMNewInstall)
if err != nil {
return nil, fmt.Errorf("plusrom %w:", err)
}
}
return cart, nil
}
// MappedBanks implements the mapper.CartMapper interface.
func (cart *PlusROM) MappedBanks() string {
return cart.state.child.MappedBanks()
}
// ID implements the mapper.CartMapper interface.
func (cart *PlusROM) ID() string {
// not altering the underlying cartmapper's ID
return cart.state.child.ID()
}
// Snapshot implements the mapper.CartMapper interface.
func (cart *PlusROM) Snapshot() mapper.CartMapper {
n := *cart
n.state = cart.state.Snapshot()
return &n
}
// Plumb implements the mapper.CartMapper interface.
func (cart *PlusROM) Plumb(env *environment.Environment) {
cart.env = env
cart.state.Plumb(env)
}
// ID implements the mapper.CartContainer interface.
func (cart *PlusROM) ContainerID() string {
return "PlusROM"
}
// Reset implements the mapper.CartMapper interface.
func (cart *PlusROM) Reset() {
cart.state.child.Reset()
}
// READ implements the mapper.CartMapper interface.
func (cart *PlusROM) Access(addr uint16, peek bool) (data uint8, mask uint8, err error) {
switch addr {
case 0x0ff2:
// 1FF2 contains the next byte of the response from the host, every
// read will increment the receive buffer pointer (receive buffer is
// max 256 bytes also!)
return cart.net.recv(), mapper.CartDrivenPins, nil
case 0x0ff3:
// 1FF3 contains the number of (unread) bytes left in the receive buffer
// (these bytes can be from multiple responses)
return uint8(cart.net.recvRemaining()), mapper.CartDrivenPins, nil
}
return cart.state.child.Access(addr, peek)
}
// AccessVolatile implements the mapper.CartMapper interface.
func (cart *PlusROM) AccessVolatile(addr uint16, data uint8, poke bool) error {
switch addr {
case 0x0ff0:
// 1FF0 is for writing a byte to the send buffer (max 256 bytes)
cart.net.buffer(data)
return nil
case 0x0ff1:
// 1FF1 is for writing a byte to the send buffer and submit the buffer
// to the back end API
cart.rewindBoundary = true
cart.net.buffer(data)
cart.net.commit()
err := cart.env.Notifications.Notify(notifications.NotifyPlusROMNetwork)
if err != nil {
return fmt.Errorf("plusrom %w:", err)
}
return nil
}
return cart.state.child.AccessVolatile(addr, data, poke)
}
// NumBanks implements the mapper.CartMapper interface.
func (cart *PlusROM) NumBanks() int {
return cart.state.child.NumBanks()
}
// GetBank implements the mapper.CartMapper interface.
func (cart *PlusROM) GetBank(addr uint16) mapper.BankInfo {
return cart.state.child.GetBank(addr)
}
// AccessPassive implements the mapper.CartMapper interface.
func (cart *PlusROM) AccessPassive(addr uint16, data uint8) error {
return cart.state.child.AccessPassive(addr, data)
}
// Step implements the mapper.CartMapper interface.
func (cart *PlusROM) Step(clock float32) {
cart.net.transmitWait()
cart.state.child.Step(clock)
}
// CopyBanks implements the mapper.CartMapper interface.
func (cart *PlusROM) CopyBanks() []mapper.BankContent {
return cart.state.child.CopyBanks()
}
// GetGetRegisters implements the mapper.CartRegistersBus interface.
func (cart *PlusROM) GetRegisters() mapper.CartRegisters {
if cart, ok := cart.state.child.(mapper.CartRegistersBus); ok {
return cart.GetRegisters()
}
return nil
}
// PutRegister implements the mapper.CartRegistersBus interface.
func (cart *PlusROM) PutRegister(register string, data string) {
if cart, ok := cart.state.child.(mapper.CartRegistersBus); ok {
cart.PutRegister(register, data)
}
}
// GetRAM implements the mapper.CartRAMbus interface.
func (cart *PlusROM) GetRAM() []mapper.CartRAM {
if cart, ok := cart.state.child.(mapper.CartRAMbus); ok {
return cart.GetRAM()
}
return nil
}
// PutRAM implements the mapper.CartRAMbus interface.
func (cart *PlusROM) PutRAM(bank int, idx int, data uint8) {
if cart, ok := cart.state.child.(mapper.CartRAMbus); ok {
cart.PutRAM(bank, idx, data)
}
}
// GetStatic implements the mapper.CartStaticBus interface.
func (cart *PlusROM) GetStatic() mapper.CartStatic {
if cart, ok := cart.state.child.(mapper.CartStaticBus); ok {
return cart.GetStatic()
}
return nil
}
// PutStatic implements the mapper.CartStaticBus interface.
func (cart *PlusROM) PutStatic(segment string, idx int, data uint8) bool {
if cart, ok := cart.state.child.(mapper.CartStaticBus); ok {
return cart.PutStatic(segment, idx, data)
}
return true
}
// Rewind implements the mapper.CartTapeBus interface.
func (cart *PlusROM) Rewind() {
if cart, ok := cart.state.child.(mapper.CartTapeBus); ok {
cart.Rewind()
}
}
// GetTapeState implements the mapper.CartTapeBus interface.
func (cart *PlusROM) GetTapeState() (bool, mapper.CartTapeState) {
if cart, ok := cart.state.child.(mapper.CartTapeBus); ok {
return cart.GetTapeState()
}
return false, mapper.CartTapeState{}
}
// Patch implements the mapper.CartPatchable interface.
func (cart *PlusROM) Patch(offset int, data uint8) error {
if cart, ok := cart.state.child.(mapper.CartPatchable); ok {
return cart.Patch(offset, data)
}
return nil
}
// RewindBoundary implements the mapper.CartRewindBoundary interface.
func (cart *PlusROM) RewindBoundary() bool {
if cart.rewindBoundary {
cart.rewindBoundary = false
return true
}
return false
}