mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-06-02 03:58:02 -04:00
24f3f32342
notifications interface instance moved to environment from cartridgeloader. the cartridgeloader package predates the environment package and had started to be used inappropriately simplified how notifications.Notify() is called. in particular the supercharger fastload starter no longer bundles a function hook. nor is the cartridge instance sent with the notification
236 lines
7.9 KiB
Go
236 lines
7.9 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 supercharger
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
|
"github.com/jetsetilly/gopher2600/hardware/cpu"
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/vcs"
|
|
"github.com/jetsetilly/gopher2600/hardware/riot/timer"
|
|
"github.com/jetsetilly/gopher2600/logger"
|
|
"github.com/jetsetilly/gopher2600/notifications"
|
|
)
|
|
|
|
// FastLoad implements the Tape interface. It loads data from a binary file
|
|
// rather than a sound file.
|
|
//
|
|
// On success it returns the FastLoaded error. This must be interpreted by the
|
|
// emulator driver and called with the arguments listed in the error type.
|
|
//
|
|
// Format information for fast-loca binary rom mailing list post:
|
|
//
|
|
// Subject: Re: [stella] Supercharger BIN format
|
|
// From: Eckhard Stolberg
|
|
// Date: Fri, 08 Jan 1999.
|
|
type FastLoad struct {
|
|
cart *Supercharger
|
|
data []byte
|
|
|
|
// number of loads in the data
|
|
numLoads int
|
|
|
|
// which load is to be tried next
|
|
loadCt int
|
|
|
|
// value of loadCt on last successful load. we use this to prevent endless
|
|
// rewinding and searching
|
|
lastLoadCt int
|
|
|
|
// fastload binaries have a header which controls how the binary is read
|
|
fastloadHeader fastloadHeader
|
|
}
|
|
|
|
type fastloadHeader struct {
|
|
// PC address to jump to once loading has finished
|
|
startAddress uint16
|
|
|
|
// RAM config to be set adter tape load
|
|
configByte uint8
|
|
|
|
// number of pages to load
|
|
numPages uint8
|
|
|
|
// not using checksum in any meaningful way
|
|
checksum uint8
|
|
|
|
// we'll use this to check if the correct multiload is being read
|
|
multiload uint8
|
|
|
|
// not using progress speed in any meaningul way
|
|
progressSpeed uint16
|
|
|
|
// data is loaded according to page table
|
|
pageTable []byte
|
|
}
|
|
|
|
// FastLoaded defines the callback function that is sent to the core emulation
|
|
// along with the HookActionFastloadEnded action. The core emulation in turn
|
|
// calls this function to complete the supercharger fastload process.
|
|
type FastLoaded func(*cpu.CPU, *vcs.RAM, *timer.Timer) error
|
|
|
|
const fastLoadBlockSize = 8448
|
|
|
|
// newFastLoad is the preferred method of initialisation for the FastLoad type.
|
|
func newFastLoad(cart *Supercharger, loader cartridgeloader.Loader) (tape, error) {
|
|
tap := &FastLoad{
|
|
cart: cart,
|
|
data: *loader.Data,
|
|
}
|
|
|
|
if len(tap.data)%fastLoadBlockSize != 0 {
|
|
return nil, fmt.Errorf("fastload: wrong number of bytes in cartridge data")
|
|
}
|
|
tap.numLoads = len(tap.data) / fastLoadBlockSize
|
|
|
|
return tap, nil
|
|
}
|
|
|
|
// snapshot implements the tape interface.
|
|
func (tap *FastLoad) snapshot() tape {
|
|
// this function doesn't copy anything. data array in each snapshot will
|
|
// point to the same data array
|
|
return tap
|
|
}
|
|
|
|
// load implements the tape interface.
|
|
func (tap *FastLoad) load() (uint8, error) {
|
|
// length check on tape data
|
|
if len(tap.data) < fastLoadBlockSize {
|
|
return 0, fmt.Errorf("fastload: not a supercharger binary")
|
|
}
|
|
|
|
// get data for the next multiload
|
|
offset := tap.loadCt * fastLoadBlockSize
|
|
data := tap.data[offset : offset+fastLoadBlockSize]
|
|
tap.loadCt++
|
|
if tap.loadCt*fastLoadBlockSize >= len(tap.data) {
|
|
tap.loadCt = 0
|
|
logger.Log("supercharger: fastload", "rewind")
|
|
}
|
|
|
|
// game header appears after main data
|
|
gameHeader := data[0x2000:0x2008]
|
|
tap.fastloadHeader.startAddress = (uint16(gameHeader[1]) << 8) | uint16(gameHeader[0])
|
|
tap.fastloadHeader.configByte = gameHeader[2]
|
|
tap.fastloadHeader.numPages = uint8(gameHeader[3])
|
|
tap.fastloadHeader.checksum = gameHeader[4]
|
|
tap.fastloadHeader.multiload = gameHeader[5]
|
|
tap.fastloadHeader.progressSpeed = (uint16(gameHeader[7]) << 8) | uint16(gameHeader[6])
|
|
tap.fastloadHeader.pageTable = data[0x2010:0x2028]
|
|
|
|
logger.Logf("supercharger: fastload", "header: start address: %#04x", tap.fastloadHeader.startAddress)
|
|
logger.Logf("supercharger: fastload", "header: config byte: %#08b", tap.fastloadHeader.configByte)
|
|
logger.Logf("supercharger: fastload", "header: num pages: %d", tap.fastloadHeader.numPages)
|
|
logger.Logf("supercharger: fastload", "header: checksum: %#02x", tap.fastloadHeader.checksum)
|
|
logger.Logf("supercharger: fastload", "header: multiload: %#02x", tap.fastloadHeader.multiload)
|
|
logger.Logf("supercharger: fastload", "header: progress speed: %#02x", tap.fastloadHeader.progressSpeed)
|
|
logger.Logf("supercharger: fastload", "page-table: %v", tap.fastloadHeader.pageTable)
|
|
|
|
// setup cartridge according to tape instructions
|
|
tap.cart.env.Notifications.Notify(notifications.NotifySuperchargerFastloadEnded)
|
|
|
|
return 0, nil
|
|
}
|
|
|
|
// step implements the Tape interface.
|
|
func (tap *FastLoad) step() {
|
|
}
|
|
|
|
// CommitFastLoad implements the mapper.CartSuperChargerFastLoad interface.
|
|
func (tap *FastLoad) CommitFastload(mc *cpu.CPU, ram *vcs.RAM, tmr *timer.Timer) error {
|
|
// look up requested multiload address
|
|
m, err := ram.Peek(MutliloadByteAddress)
|
|
if err != nil {
|
|
return fmt.Errorf("fastload %w", err)
|
|
}
|
|
|
|
data := tap.data
|
|
|
|
// this is not the mutliload we're looking for
|
|
if m != uint8(tap.loadCt) {
|
|
logger.Logf("supercharger: fastload", "fastload header (%d) not matching multiload request (%d)", m, tap.fastloadHeader.multiload)
|
|
|
|
// test for whether the tape has looped. if it has just load the first multiload
|
|
if tap.loadCt == tap.lastLoadCt {
|
|
logger.Logf("supercharger: fastload", "cannot find requested multiload (%d) loading mutliload 00", m)
|
|
tap.loadCt = 0
|
|
offset := tap.loadCt * fastLoadBlockSize
|
|
data = data[offset : offset+fastLoadBlockSize]
|
|
tap.loadCt++
|
|
}
|
|
}
|
|
|
|
logger.Logf("supercharger: fastload", "loading multiload (%d)", tap.fastloadHeader.multiload)
|
|
|
|
// copy data to RAM banks
|
|
for i := 0; i < int(tap.fastloadHeader.numPages); i++ {
|
|
bank := tap.fastloadHeader.pageTable[i] & 0x3
|
|
page := tap.fastloadHeader.pageTable[i] >> 2
|
|
bankOffset := int(page) * 0x100
|
|
binOffset := i * 0x100
|
|
copy(tap.cart.state.ram[bank][bankOffset:bankOffset+0x100], data[binOffset:binOffset+0x100])
|
|
|
|
// logger.Logf("supercharger: fastload", "copying %#04x:%#04x to bank %d page %d, offset %#04x", binOffset, binOffset+0x100, bank, page, bankOffset)
|
|
}
|
|
|
|
// initialise VCS RAM with zeros
|
|
for a := uint16(0x80); a <= 0xff; a++ {
|
|
_ = ram.Poke(a, 0x00)
|
|
}
|
|
|
|
// poke values into RAM. these values would be the by-product of the
|
|
// tape-loading process. because we are short-circuiting that process
|
|
// however, by injecting the binary data into supercharger RAM
|
|
// directly, the necessary code will not be run.
|
|
|
|
// RAM address 0x80 contains the initial configbyte
|
|
_ = ram.Poke(0x80, tap.fastloadHeader.configByte)
|
|
|
|
// CMP $fff8
|
|
_ = ram.Poke(0xfa, 0xcd)
|
|
_ = ram.Poke(0xfb, 0xf8)
|
|
_ = ram.Poke(0xfc, 0xff)
|
|
|
|
// JMP <absolute address>
|
|
_ = ram.Poke(0xfd, 0x4c)
|
|
_ = ram.Poke(0xfe, uint8(tap.fastloadHeader.startAddress))
|
|
_ = ram.Poke(0xff, uint8(tap.fastloadHeader.startAddress>>8))
|
|
|
|
// reset timer. in references to real tape loading, the number of ticks
|
|
// is the value at the moment the PC reaches address 0x00fa
|
|
tmr.PokeField("divider", timer.TIM64T)
|
|
tmr.PokeField("ticksRemaining", 0x1e)
|
|
tmr.PokeField("intim", uint8(0x0a))
|
|
|
|
// jump to VCS RAM location 0x00fa. a short bootstrap program has been
|
|
// poked there already
|
|
err = mc.LoadPC(0x00fa)
|
|
if err != nil {
|
|
return fmt.Errorf("fastload: %w", err)
|
|
}
|
|
|
|
// set the value to be used in the first instruction of the bootstrap program
|
|
tap.cart.state.registers.Value = tap.fastloadHeader.configByte
|
|
tap.cart.state.registers.Delay = 0
|
|
|
|
// note the multiload request
|
|
tap.lastLoadCt = tap.loadCt
|
|
|
|
return nil
|
|
}
|