Added beginning infrastructure for PlusROM support.

For now, none of the network functionality is present; PlusROM's are correctly detected, though.
This commit is contained in:
Stephen Anthony 2020-12-17 23:27:22 -03:30
parent 026f64d69d
commit 37c61fe93e
5 changed files with 277 additions and 3 deletions

View File

@ -17,6 +17,7 @@
#include "Logger.hxx"
#include "System.hxx"
#include "PlusROM.hxx"
#include "CartEnhanced.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -54,6 +55,11 @@ CartridgeEnhanced::CartridgeEnhanced(const ByteBuffer& image, size_t size,
// Only copy up to the amount of data the ROM provides; extra unused
// space will be filled with 0's from above
std::copy_n(image.get(), std::min(mySize, size), myImage.get());
// Determine whether we have a PlusROM cart
// PlusROM needs to call peek() method, so disable direct peeks
if(myPlusROM.initialize(myImage, mySize))
myDirectPeek = false;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -91,7 +97,7 @@ void CartridgeEnhanced::install(System& system)
System::PageAccess access(this, System::PageAccessType::READ);
// Set the page accessing method for the RAM writing pages
// Note: Writes are mapped to poke() (NOT using direcPokeBase) to check for read from write port (RWP)
// Note: Writes are mapped to poke() (NOT using directPokeBase) to check for read from write port (RWP)
access.type = System::PageAccessType::WRITE;
for(uInt16 addr = ROM_OFFSET + myWriteOffset; addr < ROM_OFFSET + myWriteOffset + myRamSize; addr += System::PAGE_SIZE)
{
@ -141,6 +147,14 @@ uInt8 CartridgeEnhanced::peek(uInt16 address)
{
const uInt16 peekAddress = address;
// Is this a PlusROM?
if(myPlusROM.isValid())
{
uInt8 value = 0;
if(myPlusROM.peekHotspot(address, value))
return value;
}
// hotspots in TIA range are reacting to pokes only
if (hotspot() >= 0x80)
checkSwitchBank(address & ADDR_MASK);
@ -170,6 +184,10 @@ uInt8 CartridgeEnhanced::peek(uInt16 address)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartridgeEnhanced::poke(uInt16 address, uInt8 value)
{
// Is this a PlusROM?
if(myPlusROM.isValid() && myPlusROM.pokeHotspot(address, value))
return true;
// Switch banks if necessary
if (checkSwitchBank(address & ADDR_MASK, value))
return false;
@ -260,7 +278,7 @@ bool CartridgeEnhanced::bank(uInt16 bank, uInt16 segment)
myCurrentSegOffset[segment] = uInt32(mySize) + (ramBank << myBankShift);
// Set the page accessing method for the RAM writing pages
// Note: Writes are mapped to poke() (NOT using direcPokeBase) to check for read from write port (RWP)
// Note: Writes are mapped to poke() (NOT using directPokeBase) to check for read from write port (RWP)
uInt16 fromAddr = (ROM_OFFSET + segmentOffset + myWriteOffset) & ~System::PAGE_MASK;
uInt16 toAddr = (ROM_OFFSET + segmentOffset + myWriteOffset + (myBankSize >> 1)) & ~System::PAGE_MASK;
System::PageAccess access(this, System::PageAccessType::WRITE);
@ -362,6 +380,8 @@ bool CartridgeEnhanced::save(Serializer& out) const
out.putIntArray(myCurrentSegOffset.get(), myBankSegs);
if(myRamSize > 0)
out.putByteArray(myRAM.get(), myRamSize);
if(myPlusROM.isValid())
if(!myPlusROM.save(out)) return false;
}
catch(...)
{
@ -380,6 +400,8 @@ bool CartridgeEnhanced::load(Serializer& in)
in.getIntArray(myCurrentSegOffset.get(), myBankSegs);
if(myRamSize > 0)
in.getByteArray(myRAM.get(), myRamSize);
if(myPlusROM.isValid())
if(!myPlusROM.load(in)) return false;
}
catch(...)
{

View File

@ -22,6 +22,7 @@ class System;
#include "bspf.hxx"
#include "Cart.hxx"
#include "PlusROM.hxx"
#ifdef DEBUGGER_SUPPORT
#include "CartEnhancedWidget.hxx"
#endif
@ -159,7 +160,7 @@ class CartridgeEnhanced : public Cartridge
/**
Get the hotspot in ROM address space.
@return The first hotspot address (ususally in ROM) space or 0
@return The first hotspot address (usually in ROM) space or 0
*/
virtual uInt16 hotspot() const { return 0; }
// TODO: handle cases where there the hotspots cover multiple pages
@ -224,6 +225,9 @@ class CartridgeEnhanced : public Cartridge
// The size of the ROM image
size_t mySize{0};
// Handle PlusROM functionality, if available
PlusROM myPlusROM;
protected:
// The mask for 6507 address space
static constexpr uInt16 ADDR_MASK = 0x1FFF;

122
src/emucore/PlusROM.cxx Normal file
View File

@ -0,0 +1,122 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2020 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include "PlusROM.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool PlusROM::initialize(const ByteBuffer& image, size_t size)
{
// Host and path are stored at the NMI vector
size_t i = ((image[size - 5] - 16) << 8) | image[size - 6]; // NMI @ $FFFA
if(i >= size)
return myIsPlusROM = false; // Invalid NMI
// Convenience functions to detect valid path and host characters
auto isValidPathChar = [](uInt8 c) {
return ((c > 44 && c < 58) || (c > 64 && c < 91) || (c > 96 && c < 122));
};
auto isValidHostChar = [](uInt8 c) {
return (c == 45 || c == 46 || (c > 47 && c < 58) ||
(c > 64 && c < 91) || (c > 96 && c < 122));
};
// Path stored first, 0-terminated
while(i < size && isValidPathChar(image[i]))
myPath += static_cast<char>(image[i++]);
// Did we get a 0-terminated path?
if(i >= size || image[i] != 0)
return myIsPlusROM = false; // Wrong delimiter
i++; // advance past 0 terminator
// Host stored next, 0-terminated
while(i < size && isValidHostChar(image[i]))
myHost += static_cast<char>(image[i++]);
// Did we get a valid, 0-terminated host?
if(i >= size || image[i] != 0 || myHost.size() < 3 || myHost.find(".") == string::npos)
return myIsPlusROM = false; // Wrong delimiter or dotless IP
cerr << "Path: " << myPath << endl;
cerr << "Host: " << myHost << endl;
return myIsPlusROM = true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool PlusROM::peekHotspot(uInt16 address, uInt8& value)
{
switch(address & 0x0FFF)
{
case 0x0FF2: // Read next byte from Rx buffer
return false;
case 0x0FF3: // Get number of unread bytes in Rx buffer
return false;
}
return false;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool PlusROM::pokeHotspot(uInt16 address, uInt8 value)
{
switch(address & 0x0FFF)
{
case 0x0FF0: // Write byte to Tx buffer
return false;
case 0x0FF1: // Write byte to Tx buffer and send to backend
// (and receive into Rx buffer)
return false;
}
return false;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool PlusROM::save(Serializer& out) const
{
try
{
out.putByteArray(myRxBuffer.data(), myRxBuffer.size());
out.putByteArray(myTxBuffer.data(), myTxBuffer.size());
}
catch(...)
{
cerr << "ERROR: PlusROM::save" << endl;
return false;
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool PlusROM::load(Serializer& in)
{
try
{
in.getByteArray(myRxBuffer.data(), myRxBuffer.size());
in.getByteArray(myTxBuffer.data(), myTxBuffer.size());
}
catch(...)
{
cerr << "ERROR: PlusROM::load" << endl;
return false;
}
return true;
}

125
src/emucore/PlusROM.hxx Normal file
View File

@ -0,0 +1,125 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2020 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef PLUSROM_HXX
#define PLUSROM_HXX
#include "bspf.hxx"
#include "Serializable.hxx"
/**
Class used to emulate the 'PlusROM' meta-scheme, documented at
http://pluscart.firmaplus.de/pico/?PlusROM
This scheme basically wraps a normal bankswitching scheme, but includes
network functionality.
Host and path names are stored as 0-terminated strings, located at the
NMI vector, stored path first and then host next.
PlusROMs functions use 4 hotspot addresses (before the bankswitching area):
$1FF0 is for writing a byte to the send buffer (max 256 bytes)
$1FF1 is for writing a byte to the send buffer and submit the buffer
to the back end API
$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!)
$1FF3 contains the number of (unread) bytes left in the receive buffer
(these bytes can be from multiple responses)
@author Stephen Anthony
*/
class PlusROM : public Serializable
{
public:
PlusROM() = default;
~PlusROM() override = default;
public:
/**
Determine whether this is actually a PlusROM cart, and if so create
and initialize all state variables it will use. This includes
whether there is a valid hostname and path embedded in the ROM.
@param image Pointer to the ROM image
@param size The size of the ROM image
@return Whether this is actually a valid PlusROM cart
*/
bool initialize(const ByteBuffer& image, size_t size);
/**
Answer whether this is a PlusROM cart. Note that until the
initialize method has been called, this will always return false.
@return Whether this is actually a PlusROM cart
*/
bool isValid() const { return myIsPlusROM; }
/**
Read from hotspot addresses ($1FF2 and $1FF3).
@param address The hotspot where the value should be read
@param value The value read from the hotspot
@return Indicates whether the peek succeeded or failed
(ie, whether it hit a hotspot)
On failure, 'value' is not considered valid
*/
bool peekHotspot(uInt16 address, uInt8& value);
/**
Write to hotspot addresses ($1FF0 and $1FF1).
@param address The hotspot where the value should be written
@param value The value to be stored at the hotspot
@return Indicates whether the poke succeeded or failed
(ie, whether it hit a hotspot)
*/
bool pokeHotspot(uInt16 address, uInt8 value);
/**
Save the current state of this device to the given Serializer.
@param out The Serializer object to use
@return False on any errors, else true
*/
bool save(Serializer& out) const override;
/**
Load the current state of this device from the given Serializer.
@param in The Serializer object to use
@return False on any errors, else true
*/
bool load(Serializer& in) override;
private:
bool myIsPlusROM{false};
string myPath, myHost;
std::array<uInt8, 256> myRxBuffer, myTxBuffer;
private:
// Following constructors and assignment operators not supported
PlusROM(const PlusROM&) = delete;
PlusROM(PlusROM&&) = delete;
PlusROM& operator=(const PlusROM&) = delete;
PlusROM& operator=(PlusROM&&) = delete;
};
#endif

View File

@ -76,6 +76,7 @@ MODULE_OBJS := \
src/emucore/MD5.o \
src/emucore/OSystem.o \
src/emucore/Paddles.o \
src/emucore/PlusROM.o \
src/emucore/PointingDevice.o \
src/emucore/ProfilingRunner.o \
src/emucore/Props.o \