diff --git a/src/emucore/CartEnhanced.cxx b/src/emucore/CartEnhanced.cxx index 487a22741..08cf5bab1 100644 --- a/src/emucore/CartEnhanced.cxx +++ b/src/emucore/CartEnhanced.cxx @@ -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(...) { diff --git a/src/emucore/CartEnhanced.hxx b/src/emucore/CartEnhanced.hxx index fb5b35509..6800938a8 100644 --- a/src/emucore/CartEnhanced.hxx +++ b/src/emucore/CartEnhanced.hxx @@ -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; diff --git a/src/emucore/PlusROM.cxx b/src/emucore/PlusROM.cxx new file mode 100644 index 000000000..f779b09a0 --- /dev/null +++ b/src/emucore/PlusROM.cxx @@ -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(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(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; +} diff --git a/src/emucore/PlusROM.hxx b/src/emucore/PlusROM.hxx new file mode 100644 index 000000000..d38979488 --- /dev/null +++ b/src/emucore/PlusROM.hxx @@ -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 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 diff --git a/src/emucore/module.mk b/src/emucore/module.mk index b7d88d331..93a577be9 100644 --- a/src/emucore/module.mk +++ b/src/emucore/module.mk @@ -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 \