diff --git a/Changes.txt b/Changes.txt index b79a29346..07b70ecf6 100644 --- a/Changes.txt +++ b/Changes.txt @@ -14,6 +14,9 @@ 6.5.3 to 6.6 (??? ??, 202?) + * Added preliminary support for 'MVC' bankswitching scheme by + Rob Bairos. + * Added web links for many games * Debugger: added optional logging of breaks and traps diff --git a/src/common/FSNodeZIP.cxx b/src/common/FSNodeZIP.cxx index 754f848d6..f7720d72e 100644 --- a/src/common/FSNodeZIP.cxx +++ b/src/common/FSNodeZIP.cxx @@ -180,7 +180,7 @@ bool FilesystemNodeZIP::getChildren(AbstractFSList& myList, ListMode mode) const } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -size_t FilesystemNodeZIP::read(ByteBuffer& image) const +size_t FilesystemNodeZIP::read(ByteBuffer& image, size_t) const { switch(_error) { @@ -205,7 +205,7 @@ size_t FilesystemNodeZIP::read(stringstream& image) const // For now, we just read into a buffer and store in the stream // TODO: maybe there's a more efficient way to do this? ByteBuffer buffer; - size_t size = read(buffer); + size_t size = read(buffer, 0); if(size > 0) image.write(reinterpret_cast(buffer.get()), size); diff --git a/src/common/FSNodeZIP.hxx b/src/common/FSNodeZIP.hxx index 77246d35f..7747b7acc 100644 --- a/src/common/FSNodeZIP.hxx +++ b/src/common/FSNodeZIP.hxx @@ -62,8 +62,8 @@ class FilesystemNodeZIP : public AbstractFSNode bool getChildren(AbstractFSList& list, ListMode mode) const override; AbstractFSNodePtr getParent() const override; - size_t read(ByteBuffer& image) const override; - size_t read(stringstream& image) const override; + size_t read(ByteBuffer& buffer, size_t size) const override; + size_t read(stringstream& buffer) const override; size_t write(const ByteBuffer& buffer, size_t size) const override; size_t write(const stringstream& buffer) const override; diff --git a/src/emucore/Bankswitch.cxx b/src/emucore/Bankswitch.cxx index ba1f855a7..ce3fa69d9 100644 --- a/src/emucore/Bankswitch.cxx +++ b/src/emucore/Bankswitch.cxx @@ -140,6 +140,7 @@ Bankswitch::BSList = {{ { "FC" , "FC (32K Amiga)" }, { "FE" , "FE (8K Decathlon)" }, { "MDM" , "MDM (Menu Driven Megacart)" }, + { "MVC" , "MVC (Movie Cart)" }, { "SB" , "SB (128-256K SUPERbank)" }, { "TVBOY" , "TV Boy (512K)" }, { "UA" , "UA (8K UA Ltd.)" }, @@ -226,6 +227,7 @@ Bankswitch::ExtensionMap Bankswitch::ourExtensions = { { "FC" , Bankswitch::Type::_FC }, { "FE" , Bankswitch::Type::_FE }, { "MDM" , Bankswitch::Type::_MDM }, + { "MVC" , Bankswitch::Type::_MVC }, { "SB" , Bankswitch::Type::_SB }, { "TVB" , Bankswitch::Type::_TVBOY }, { "TVBOY" , Bankswitch::Type::_TVBOY }, @@ -284,6 +286,7 @@ Bankswitch::NameToTypeMap Bankswitch::ourNameToTypes = { { "FC" , Bankswitch::Type::_FC }, { "FE" , Bankswitch::Type::_FE }, { "MDM" , Bankswitch::Type::_MDM }, + { "MVC" , Bankswitch::Type::_MVC }, { "SB" , Bankswitch::Type::_SB }, { "TVBOY" , Bankswitch::Type::_TVBOY }, { "UA" , Bankswitch::Type::_UA }, diff --git a/src/emucore/Bankswitch.hxx b/src/emucore/Bankswitch.hxx index 3c1b2454a..38485d2ea 100644 --- a/src/emucore/Bankswitch.hxx +++ b/src/emucore/Bankswitch.hxx @@ -44,8 +44,8 @@ class Bankswitch _CDF, _CM, _CTY, _CV, _DF, _DFSC, _DPC, _DPCP, _E0, _E7, _E78K, _EF, _EFSC, _F0, _F4, _F4SC, _F6, _F6SC, _F8, _F8SC, _FA, - _FA2, _FC, _FE, _MDM, _SB, _TVBOY, _UA, - _UASW, _WD, _WDSW, _X07, + _FA2, _FC, _FE, _MDM, _MVC, _SB, _TVBOY, + _UA, _UASW, _WD, _WDSW, _X07, #ifdef CUSTOM_ARM _CUSTOM, #endif diff --git a/src/emucore/CartCreator.cxx b/src/emucore/CartCreator.cxx index f792b449c..71cc118b8 100644 --- a/src/emucore/CartCreator.cxx +++ b/src/emucore/CartCreator.cxx @@ -55,6 +55,7 @@ #include "CartFC.hxx" #include "CartFE.hxx" #include "CartMDM.hxx" +#include "CartMVC.hxx" #include "CartSB.hxx" #include "CartTVBoy.hxx" #include "CartUA.hxx" @@ -197,6 +198,10 @@ unique_ptr CartCreator::create(const FilesystemNode& file, Bankswitch::typeToName(type) + "'"); break; + case Bankswitch::Type::_MVC: + cartridge = make_unique(file.getPath(), size, md5, settings); + break; + default: cartridge = createFromImage(image, size, detectedType, md5, settings); break; diff --git a/src/emucore/CartDetector.cxx b/src/emucore/CartDetector.cxx index 1e37a961f..a83a9b1ea 100644 --- a/src/emucore/CartDetector.cxx +++ b/src/emucore/CartDetector.cxx @@ -19,6 +19,7 @@ #include "Logger.hxx" #include "CartDetector.hxx" +#include "CartMVC.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bankswitch::Type CartDetector::autodetectType(const ByteBuffer& image, size_t size) @@ -242,6 +243,8 @@ Bankswitch::Type CartDetector::autodetectType(const ByteBuffer& image, size_t si type = Bankswitch::Type::_3EP; else if(isProbablyMDM(image, size)) type = Bankswitch::Type::_MDM; + else if(isProbablyMVC(image, size)) + type = Bankswitch::Type::_MVC; // If we get here and autodetection failed, then we force '4K' if(type == Bankswitch::Type::_AUTO) @@ -455,7 +458,7 @@ bool CartDetector::isProbablyCDF(const ByteBuffer& image, size_t size) // 0x10adab1e (LOADABLE) if needed for future improvement uInt8 cdf[] = { 'C', 'D', 'F' }; uInt8 cdfjplus[] = { 'P', 'L', 'U', 'S', 'C', 'D', 'F', 'J' }; - return (searchForBytes(image, size, cdf, 3, 3) || + return (searchForBytes(image, size, cdf, 3, 3) || searchForBytes(image, size, cdfjplus, 8, 1)); } @@ -691,6 +694,38 @@ bool CartDetector::isProbablyMDM(const ByteBuffer& image, size_t size) return searchForBytes(image, std::min(size, 8_KB), mdmc, 4); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool CartDetector::isProbablyMVC(const ByteBuffer& image, size_t size) +{ + // MVC version 0 + uInt8 sig[] = { 'M', 'V', 'C', 0 }; + int sigSize = sizeof(sig); + return searchForBytes(image, std::min(size, sigSize+1), sig, sigSize); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +size_t CartDetector::isProbablyMVC(const FilesystemNode& rom) +{ + constexpr size_t frameSize = 2 * CartridgeMVC::MVC_FIELD_PAD_SIZE; + + if(Bankswitch::typeFromExtension(rom) == Bankswitch::Type::_MVC) + return frameSize; + + Serializer s(rom.getPath(), Serializer::Mode::ReadOnly); + if(s) + { + if(s.size() < frameSize) + return 0; + + uInt8 image[frameSize]; + s.getByteArray(image, frameSize); + + uInt8 sig[] = { 'M', 'V', 'C', 0 }; // MVC version 0 + return searchForBytes(image, frameSize, sig, 4) ? frameSize : 0; + } + return 0; +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool CartDetector::isProbablySB(const ByteBuffer& image, size_t size) { diff --git a/src/emucore/CartDetector.hxx b/src/emucore/CartDetector.hxx index 1ff16fc30..dc9025c76 100644 --- a/src/emucore/CartDetector.hxx +++ b/src/emucore/CartDetector.hxx @@ -19,6 +19,7 @@ #define CARTRIDGE_DETECTOR_HXX #include "Bankswitch.hxx" +#include "FSNode.hxx" #include "bspf.hxx" /** @@ -40,6 +41,12 @@ class CartDetector */ static Bankswitch::Type autodetectType(const ByteBuffer& image, size_t size); + /** + MVC cartridges are of arbitary large length + Returns size of frame if stream is probably an MVC movie cartridge + */ + static size_t isProbablyMVC(const FilesystemNode& rom); + private: /** Search the image for the specified byte signature @@ -189,6 +196,11 @@ class CartDetector */ static bool isProbablyMDM(const ByteBuffer& image, size_t size); + /** + Returns true if the image is probably an MVC movie cartridge + */ + static bool isProbablyMVC(const ByteBuffer& image, size_t size); + /** Returns true if the image is probably a SB bankswitching cartridge */ diff --git a/src/emucore/CartMVC.cxx b/src/emucore/CartMVC.cxx new file mode 100755 index 000000000..37d3d6024 --- /dev/null +++ b/src/emucore/CartMVC.cxx @@ -0,0 +1,1579 @@ +//============================================================================ +// +// 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-2021 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 "Serializer.hxx" +#include "Serializable.hxx" +#include "System.hxx" +#include "CartMVC.hxx" + +/** + Implementation of MovieCart. + 1K of memory is presented on the bus, but is repeated to fill the 4K image space. + Contents are dynamically altered with streaming image and audio content as specific + 128-byte regions are entered. + Original implementation: github.com/lodefmode/moviecart + + @author Rob Bairos +*/ + +#define LO_JUMP_BYTE(X) ((X) & 0xff) +#define HI_JUMP_BYTE(X) ((((X) & 0xff00) >> 8) | 0x10) + +#define COLOR_BLUE 0x9A +// #define COLOR_WHITE 0x0E + +#define OSD_FRAMES 180 +#define BACK_SECONDS 10 + +#define TITLE_CYCLES 1000000 + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +/** + Simulate retrieval 512 byte chunks from a serial source +*/ +class StreamReader : public Serializable +{ + public: + StreamReader() = default; + + bool open(const string& path) { + myFile = Serializer(path, Serializer::Mode::ReadOnly); + if(myFile) + myFileSize = myFile.size(); + + return bool(myFile); + } + + void blankPartialLines(bool index) { + constexpr int colorSize = 192 * 5; + if (index) + { + // top line + myColor[0] = 0; + myColor[1] = 0; + myColor[2] = 0; + myColor[3] = 0; + myColor[4] = 0; + } + else + { + // bottom line + myColor[colorSize - 5] = 0; + myColor[colorSize - 4] = 0; + myColor[colorSize - 3] = 0; + myColor[colorSize - 2] = 0; + myColor[colorSize - 1] = 0; + } + } + + void swapField(bool index) { + uInt8* offset = index ? myBuffer1 : myBuffer2; + + myVersion = offset + VERSION_DATA_OFFSET; + myFrame = offset + FRAME_DATA_OFFSET; + myAudio = offset + AUDIO_DATA_OFFSET; + myGraph = offset + GRAPH_DATA_OFFSET; + myTimecode = offset + TIMECODE_DATA_OFFSET; + myColor = offset + COLOR_DATA_OFFSET; + } + + bool readField(uInt32 fnum, bool index) { + if(myFile) + { + size_t offset = ((fnum + 0) * CartridgeMVC::MVC_FIELD_PAD_SIZE); + + if(offset + CartridgeMVC::MVC_FIELD_PAD_SIZE < myFileSize) + { + myFile.setPosition(offset); + if(index) + myFile.getByteArray(myBuffer1, CartridgeMVC::MVC_FIELD_SIZE); + else + myFile.getByteArray(myBuffer2, CartridgeMVC::MVC_FIELD_SIZE); + + return true; + } + } + return false; + } + + uInt8 readVersion() { return *myVersion++; } + uInt8 readFrame() { return *myFrame++; } + uInt8 readColor() { return *myColor++; } + + uInt8 readGraph() { + return myGraphOverride ? *myGraphOverride++ : *myGraph++; + } + + void overrideGraph(const uInt8* p) { myGraphOverride = p; } + + uInt8 readAudio() { return *myAudio++; } + + uInt8 peekAudio() const { return *myAudio; } + + void startTimeCode() { myGraph = myTimecode; } + + bool save(Serializer& out) const override { + try + { + out.putByteArray(myBuffer1, CartridgeMVC::MVC_FIELD_SIZE); + out.putByteArray(myBuffer2, CartridgeMVC::MVC_FIELD_SIZE); + + #if 0 // FIXME - determine whether we need to load/save this + const uInt8* myAudio + const uInt8* myGraph + const uInt8* myGraphOverride + const uInt8* myTimecode + const uInt8* myColor + const uInt8* myVersion + const uInt8* myFrame + #endif + } + catch(...) + { + return false; + } + return true; + } + + bool load(Serializer& in) override { + try + { + in.getByteArray(myBuffer1, CartridgeMVC::MVC_FIELD_SIZE); + in.getByteArray(myBuffer2, CartridgeMVC::MVC_FIELD_SIZE); + + #if 0 // FIXME - determine whether we need to load/save this + const uInt8* myAudio + const uInt8* myGraph + const uInt8* myGraphOverride + const uInt8* myTimecode + const uInt8* myColor + const uInt8* myVersion + const uInt8* myFrame + #endif + } + catch(...) + { + return false; + } + return true; + } + + private: + static constexpr int + VERSION_DATA_OFFSET = 0, + FRAME_DATA_OFFSET = 4, + AUDIO_DATA_OFFSET = 7, + GRAPH_DATA_OFFSET = 269, + TIMECODE_DATA_OFFSET = 1229, + COLOR_DATA_OFFSET = 1289, + END_DATA_OFFSET = 2249; + + const uInt8* myAudio{nullptr}; + + const uInt8* myGraph{nullptr}; + const uInt8* myGraphOverride{nullptr}; + + const uInt8* myTimecode{nullptr}; + uInt8* myColor{nullptr}; + const uInt8* myVersion{nullptr}; + const uInt8* myFrame{nullptr}; + + uInt8 myBuffer1[CartridgeMVC::MVC_FIELD_SIZE]; + uInt8 myBuffer2[CartridgeMVC::MVC_FIELD_SIZE]; + + Serializer myFile; + size_t myFileSize{0}; +}; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +/** + State of current switches and joystick positions to control MovieCart +*/ +class MovieInputs : public Serializable +{ + public: + MovieInputs() = default; + + void init() { + bw = fire = select = reset = false; + right = left = up = down = false; + } + + bool bw{false}, fire{false}, select{false}, reset{false}; + bool right{false}, left{false}, up{false}, down{false}; + + void updateDirection(uInt8 val) { + right = val & TRANSPORT_RIGHT; + left = val & TRANSPORT_LEFT; + up = val & TRANSPORT_UP; + down = val & TRANSPORT_DOWN; + } + + void updateTransport(uInt8 val) { + bw = val & TRANSPORT_BW; + fire = val & TRANSPORT_BUTTON; + select = val & TRANSPORT_SELECT; + reset = val & TRANSPORT_RESET; + } + + bool save(Serializer& out) const override { + try + { + out.putBool(bw); out.putBool(fire); + out.putBool(select); out.putBool(reset); + out.putBool(right); out.putBool(left); + out.putBool(up); out.putBool(down); + } + catch(...) + { + return false; + } + return true; + } + + bool load(Serializer& in) override { + try + { + bw = in.getBool(); fire = in.getBool(); + select = in.getBool(); reset = in.getBool(); + right = in.getBool(); left = in.getBool(); + up = in.getBool(); down = in.getBool(); + } + catch(...) + { + return false; + } + return true; + } + + private: + static constexpr uInt8 + TRANSPORT_RIGHT = 0x10, + TRANSPORT_LEFT = 0x08, + TRANSPORT_DOWN = 0x04, + TRANSPORT_UP = 0x02, + TRANSPORT_UNUSED1 = 0x01; // Right-2 + + static constexpr uInt8 + TRANSPORT_BW = 0x10, + TRANSPORT_UNUSED2 = 0x08, + TRANSPORT_SELECT = 0x04, + TRANSPORT_RESET = 0x02, + TRANSPORT_BUTTON = 0x01; + }; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +/** + Various kernel, OSD and scale definitions + @author Rob Bairos +*/ + +#define TIMECODE_HEIGHT 12 +#define MAX_LEVEL 11 +#define DEFAULT_LEVEL 6 +#define BLANK_LINE_SIZE (30+3+37-1) // 70-1 + +// #define addr_kernel_48 0x800 +#define addr_right_line 0x946 +#define addr_set_aud_right 0x94a +#define addr_set_gdata9 0x94c +#define addr_set_gcol9 0x950 +#define addr_set_gdata6 0x952 +#define addr_set_gcol6 0x956 +#define addr_set_gdata5 0x95a +#define addr_set_gcol5 0x95e +#define addr_set_gdata8 0x962 +#define addr_set_gcol7 0x966 +#define addr_set_gdata7 0x96a +#define addr_set_gcol8 0x96e +// #define addr_left_line 0x980 +#define addr_set_gdata1 0x982 +#define addr_set_aud_left 0x986 +#define addr_set_gdata4 0x98a +#define addr_set_gcol4 0x98c +#define addr_set_gcol1 0x98e +#define addr_set_gdata0 0x992 +#define addr_set_gcol0 0x996 +#define addr_set_gdata3 0x99a +#define addr_set_gcol2 0x99e +#define addr_set_gdata2 0x9a2 +#define addr_set_gcol3 0x9a6 +#define addr_pick_continue 0x9b4 +// #define addr_main_start 0xa00 +// #define addr_aud_bank_setup 0xa0c +// #define addr_tg0 0xa24 +// #define addr_title_again 0xa3b +// #define addr_wait_cnt 0xa77 +#define addr_end_lines 0xa80 +#define addr_set_aud_endlines 0xa8c +#define addr_set_overscan_size 0xa90 +#define addr_set_vblank_size 0xaa6 +#define addr_pick_extra_lines 0xaaf +#define addr_pick_transport 0xab6 +#define addr_transport_direction 0xab9 +#define addr_transport_buttons 0xac2 +// #define addr_transport_done 0xacb +// #define addr_wait_lines 0xad0 +// #define addr_transport_done1 0xae1 +// #define addr_draw_title 0xb00 +#define addr_title_loop 0xb50 +// #define addr_black_bar 0xb52 +// #define addr_animate_bar1 0xb58 +// #define addr_animate_bar_again1 0xb5a +// #define addr_animate_dex1 0xb65 +#define addr_audio_bank 0xb80 +// #define addr_reset_loop 0xbfa + +// scale adjustments, automatically generated +static constexpr uInt8 scale0[16] = { + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 /* 0.0000 */ +}; +static constexpr uInt8 scale1[16] = { + 6, 6, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9 /* 0.1667 */ +}; +static constexpr uInt8 scale2[16] = { + 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10 /* 0.3333 */ +}; +static constexpr uInt8 scale3[16] = { + 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11 /* 0.5000 */ +}; +static constexpr uInt8 scale4[16] = { + 3, 3, 4, 5, 5, 6, 7, 7, 8, 9, 9, 10, 11, 11, 12, 13 /* 0.6667 */ +}; +static constexpr uInt8 scale5[16] = { + 1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10, 10, 11, 12, 13, 14 /* 0.8333 */ +}; +static constexpr uInt8 scale6[16] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 /* 1.0000 */ +}; +static constexpr uInt8 scale7[16] = { + 0, 0, 0, 1, 3, 4, 5, 7, 8, 10, 11, 12, 14, 15, 15, 15 /* 1.3611 */ +}; +static constexpr uInt8 scale8[16] = { + 0, 0, 0, 0, 1, 3, 5, 7, 8, 10, 12, 14, 15, 15, 15, 15 /* 1.7778 */ +}; +static constexpr uInt8 scale9[16] = { + 0, 0, 0, 0, 0, 2, 4, 6, 9, 11, 13, 15, 15, 15, 15, 15 /* 2.2500 */ +}; +static constexpr uInt8 scale10[16] = { + 0, 0, 0, 0, 0, 1, 3, 6, 9, 12, 14, 15, 15, 15, 15, 15 /* 2.7778 */ +}; +static const uInt8* scales[11] = { + scale0, scale1, scale2, scale3, scale4, scale5, + scale6, scale7, scale8, scale9, scale10 +}; + +// lower bit is ignored anyways +static constexpr uInt8 shiftBright[16 + MAX_LEVEL - 1] = { + 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 15, 15, 15 +}; + +// Compiled kernel +static constexpr unsigned char kernelROM[] = { + 133, 2, 185, 50, 248, 133, 27, 185, 62, 248, 133, 28, 185, 74, 248, 133, + 27, 185, 86, 248, 133, 135, 185, 98, 248, 190, 110, 248, 132, 136, 164, 135, + 132, 28, 133, 27, 134, 28, 134, 27, 164, 136, 102, 137, 176, 210, 136, 16, + 207, 96, 0, 1, 1, 1, 0, 0, 48, 48, 50, 53, 56, 48, 249, 129, + 129, 128, 248, 0, 99, 102, 102, 102, 230, 99, 140, 252, 140, 136, 112, 0, + 192, 97, 99, 102, 102, 198, 198, 198, 248, 198, 248, 0, 193, 32, 48, 24, + 24, 25, 24, 24, 24, 24, 126, 0, 249, 97, 97, 97, 97, 249, 0, 0, + 0, 0, 0, 0, 248, 128, 128, 224, 128, 248, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 133, 2, 133, 42, 169, 0, 162, 223, 133, 25, + 160, 98, 169, 159, 133, 28, 169, 248, 133, 7, 169, 231, 133, 27, 169, 242, + 133, 6, 169, 247, 133, 28, 169, 172, 133, 6, 169, 253, 133, 27, 169, 216, + 133, 7, 134, 27, 132, 6, 133, 43, 133, 128, 133, 128, 133, 128, 133, 42, + 133, 2, 169, 207, 133, 28, 169, 0, 133, 25, 162, 191, 160, 114, 169, 54, + 133, 7, 169, 243, 133, 27, 169, 66, 133, 6, 169, 239, 133, 28, 169, 238, + 133, 6, 169, 251, 133, 27, 169, 182, 133, 7, 134, 27, 132, 6, 169, 128, + 133, 32, 133, 33, 76, 70, 249, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 120, 216, 162, 255, 154, 169, 0, 149, 0, 202, 208, 251, 169, 128, 133, 130, + 169, 251, 133, 131, 169, 1, 133, 37, 133, 38, 169, 3, 133, 4, 133, 5, + 133, 2, 162, 4, 133, 128, 202, 208, 251, 133, 128, 133, 16, 133, 17, 169, + 208, 133, 32, 169, 224, 133, 33, 133, 2, 133, 42, 165, 132, 106, 106, 133, + 6, 133, 7, 169, 85, 133, 137, 32, 0, 251, 176, 239, 169, 0, 133, 9, + 133, 37, 133, 27, 133, 16, 234, 133, 17, 133, 28, 133, 27, 133, 28, 169, + 6, 133, 4, 169, 2, 133, 5, 169, 1, 133, 38, 169, 0, 133, 32, 169, + 240, 133, 33, 133, 42, 162, 5, 202, 208, 253, 133, 43, 76, 128, 250, 255, + 160, 0, 132, 27, 132, 28, 132, 27, 169, 0, 169, 0, 169, 0, 133, 25, + 162, 29, 32, 208, 250, 169, 2, 133, 0, 162, 3, 32, 208, 250, 169, 0, + 133, 0, 169, 2, 133, 1, 162, 37, 32, 208, 250, 162, 0, 134, 1, 162, + 0, 240, 3, 32, 208, 250, 76, 194, 250, 173, 128, 2, 74, 74, 74, 76, + 203, 250, 165, 12, 10, 173, 130, 2, 42, 41, 23, 133, 129, 76, 70, 249, + 133, 2, 169, 0, 177, 130, 133, 25, 165, 129, 240, 5, 198, 129, 173, 128, + 20, 200, 202, 208, 235, 96, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 162, 30, 32, 82, 251, 169, 2, 133, 0, 162, 3, 32, 82, 251, 169, 0, + 133, 0, 169, 2, 133, 1, 162, 37, 32, 82, 251, 169, 0, 133, 1, 198, + 132, 165, 132, 133, 133, 160, 255, 162, 30, 32, 88, 251, 162, 54, 32, 82, + 251, 160, 11, 32, 0, 248, 169, 0, 133, 27, 133, 28, 133, 27, 133, 28, + 162, 54, 32, 82, 251, 165, 132, 133, 133, 160, 1, 162, 30, 32, 88, 251, + 56, 96, 169, 0, 133, 133, 160, 0, 132, 134, 24, 165, 133, 101, 134, 133, + 133, 133, 2, 133, 9, 202, 208, 242, 96, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 250, 0, 250, 0, 250, +}; + +// OSD labels +static constexpr uInt8 brightLabelEven[] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 225, 48, 12, 252, + 6, 140, 231, 96, 0, + 0, 113, 48, 12, 96, + 6, 140, 192, 96, 0, + 0, 225, 49, 15, 96, + 6, 152, 195, 96, 0, + 0, 49, 48, 12, 96, + 6, 140, 231, 96, 0, + 0, 225, 48, 12, 96, +}; + +static constexpr uInt8 brightLabelOdd[] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 7, 252, 126, 99, 0, + 0, 113, 48, 12, 96, + 6, 140, 192, 96, 0, + 0, 97, 49, 12, 96, + 7, 248, 223, 224, 0, + 0, 113, 49, 12, 96, + 6, 156, 195, 96, 0, + 0, 113, 48, 12, 96, + 7, 142, 127, 96, 0, + 0, 0, 0, 0, 0 +}; + +static constexpr uInt8 volumeLabelEven[] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 0, 199, 192, 14, 254, + 113, 112, 99, 112, 0, + 0, 140, 192, 14, 192, + 51, 48, 99, 240, 0, + 0, 28, 192, 15, 254, + 31, 48, 99, 240, 0, + 0, 12, 192, 15, 192, + 30, 112, 119, 176, 0, + 0, 7, 252, 12, 254, +}; + +static constexpr uInt8 volumeLabelOdd[] = { + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + 97, 224, 99, 112, 0, + 0, 142, 192, 14, 192, + 51, 112, 99, 112, 0, + 0, 28, 192, 15, 192, + 59, 48, 99, 240, 0, + 0, 28, 192, 15, 192, + 30, 112, 99, 176, 0, + 0, 14, 192, 13, 192, + 14, 224, 62, 48, 0, + 0, 0, 0, 0, 0 +}; + +// Level bars +// 8 rows * 5 columns = 40 +static constexpr uInt8 levelBarsEvenData[] = { + /**/ + 0, 0, 0, 0, 0, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 2, 40, 161, 133, 20, + 20, 80, 66, 10, 32, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 0, 0, 0, 0, 0, + /**/ + 0, 0, 0, 0, 0, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 3, 40, 161, 133, 20, + 244, 80, 66, 10, 32, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 0, 0, 0, 0, 0, + /**/ + 0, 0, 0, 0, 0, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 3, 232, 161, 133, 20, + 247, 80, 66, 10, 32, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 0, 0, 0, 0, 0, + /**/ + 0, 0, 0, 0, 0, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 3, 239, 161, 133, 20, + 247, 208, 66, 10, 32, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 0, 0, 0, 0, 0, + /**/ + 0, 0, 0, 0, 0, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 3, 239, 161, 133, 20, + 247, 223, 66, 10, 32, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 0, 0, 0, 0, 0, + /**/ + 0, 0, 0, 0, 0, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 3, 239, 191, 133, 20, + 247, 223, 66, 10, 32, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 0, 0, 0, 0, 0, + /**/ + 0, 0, 0, 0, 0, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 3, 239, 191, 133, 20, + 247, 223, 126, 10, 32, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 0, 0, 0, 0, 0, + /**/ + 0, 0, 0, 0, 0, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 20, + 247, 223, 126, 10, 32, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 0, 0, 0, 0, 0, + /**/ + 0, 0, 0, 0, 0, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 20, + 247, 223, 126, 250, 32, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 0, 0, 0, 0, 0, + /**/ + 0, 0, 0, 0, 0, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 244, + 247, 223, 126, 251, 32, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 0, 0, 0, 0, 0, + /**/ + 0, 0, 0, 0, 0, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 0, 0, 0, 0, 0, +}; + +static constexpr uInt8 levelBarsOddData[] = { + /**/ + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 20, 80, 66, 10, 32, + 2, 40, 161, 133, 20, + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + /**/ + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 244, 80, 66, 10, 32, + 3, 40, 161, 133, 20, + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + /**/ + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 247, 80, 66, 10, 32, + 3, 232, 161, 133, 20, + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + /**/ + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 247, 208, 66, 10, 32, + 3, 239, 161, 133, 20, + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + /**/ + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 247, 223, 66, 10, 32, + 3, 239, 161, 133, 20, + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + /**/ + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 247, 223, 66, 10, 32, + 3, 239, 191, 133, 20, + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + /**/ + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 247, 223, 126, 10, 32, + 3, 239, 191, 133, 20, + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + /**/ + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 247, 223, 126, 10, 32, + 3, 239, 191, 253, 20, + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + /**/ + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 247, 223, 126, 250, 32, + 3, 239, 191, 253, 20, + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + /**/ + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 32, + 3, 239, 191, 253, 244, + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, + /**/ + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 247, 223, 126, 251, 224, + 3, 239, 191, 253, 247, + 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, +}; + +//////////////////////////////////////////////////////////////////////////////// +class MovieCart : public Serializable +{ + public: + MovieCart() = default; + + bool init(const string& path); + bool process(uInt16 address); + + bool save(Serializer& out) const override; + bool load(Serializer& in) override; + + uInt8 readROM(uInt16 address) const { + return myROM[address & 1023]; + } + + void writeROM(uInt16 address, uInt8 data) { + myROM[address & 1023] = data; + } + + private: + enum Mode : uInt8 + { + Volume, + Bright, + Time, + Last = Time + }; + + enum class TitleState + { + Display, + Exiting, + Stream + }; + + void stopTitleScreen() { + // clear carry, one bit difference from 0x38 sec + writeROM(addr_title_loop + 0, 0x18); + } + + void writeColor(uInt16 address); + void writeAudioData(uInt16 address, uInt8 val) { + writeROM(address, myVolumeScale[val]); + } + void writeAudio(uInt16 address) { + writeAudioData(address, myStream.readAudio()); + } + void writeGraph(uInt16 address) { + writeROM(address, myStream.readGraph()); + } + + void runStateMachine(); + + void fill_addr_right_line(); + void fill_addr_left_line(bool again); + void fill_addr_end_lines(); + void fill_addr_blank_lines(); + + void updateTransport(); + + // data + uInt8 myROM[1024]; + + // title screen state + int myTitleCycles{0}; + TitleState myTitleState{TitleState::Display}; + + // address info + bool myA7{false}; + bool myA10{false}; + uInt8 myA10_Count{0}; + + // state machine info + uInt8 myState{3}; + bool myPlaying{true}; + bool myOdd{true}; + bool myBufferIndex{false}; + + uInt8 myLines{0}; + Int32 myFrameNumber{1}; // signed + + uInt8 myMode{Mode::Volume}; + uInt8 myBright{DEFAULT_LEVEL}; + uInt8 myForceColor{0}; + + // expressed in frames + uInt8 myDrawLevelBars{0}; + uInt8 myDrawTimeCode{0}; + + StreamReader myStream; + MovieInputs myInputs; + MovieInputs myLastInputs; + + Int8 mySpeed{1}; // signed + uInt8 myJoyRepeat{0}; + uInt8 myDirectionValue{0}; + uInt8 myButtonsValue{0}; + + uInt8 myVolume{DEFAULT_LEVEL}; + const uInt8* myVolumeScale{scales[DEFAULT_LEVEL]}; + uInt8 myFirstAudioVal{0}; +}; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool MovieCart::init(const string& path) +{ + memcpy(myROM, kernelROM, 1024); + + myTitleCycles = 0; + myTitleState = TitleState::Display; + + myA7 = false; + myA10 = false; + myA10_Count = 0; + + myState = 3; + myPlaying = true; + myOdd = true; + myBufferIndex = false; + myFrameNumber = 1; + + myInputs.init(); + myLastInputs.init(); + mySpeed = 1; + myJoyRepeat = 0; + myDirectionValue = 0; + myButtonsValue = 0; + + myLines = 0; + myForceColor = 0; + myDrawLevelBars = 0; + myDrawTimeCode = 0; + myFirstAudioVal = 0; + + myMode = Mode::Volume; + myVolume = DEFAULT_LEVEL; + myVolumeScale = scales[DEFAULT_LEVEL]; + myBright = DEFAULT_LEVEL; + + if(!myStream.open(path)) + return false; + + myStream.swapField(true); + + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void MovieCart::writeColor(uInt16 address) +{ + uint8_t v = myStream.readColor(); + + v = (v & 0xf0) | shiftBright[(v & 0x0f) + myBright]; + + if(myForceColor) + v = myForceColor; + if(myInputs.bw) + v &= 0x0f; + + writeROM(address, v); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void MovieCart::updateTransport() +{ + myStream.overrideGraph(nullptr); + + // have to cut rate in half, to remove glitches...todo.. + { + if(myBufferIndex) + { + uInt8 temp = ~(myA10_Count & 0x1e) & 0x1e; + + if (temp == myDirectionValue) + myInputs.updateDirection(temp); + + myDirectionValue = temp; + } + else + { + uInt8 temp = ~(myA10_Count & 0x17) & 0x17; + + if(temp == myButtonsValue) + myInputs.updateTransport(temp); + + myButtonsValue = temp; + } + + myA10_Count = 0; + } + + if(myInputs.reset) + { + myFrameNumber = 1; + myPlaying = true; + myDrawTimeCode = OSD_FRAMES; + + // goto update_stream; + myLastInputs = myInputs; + return; + } + + uInt8 lastMainMode = myMode; + + if(myInputs.up && !myLastInputs.up) + { + if(myMode == 0) + myMode = Mode::Last; + else + myMode--; + } + else if(myInputs.down && !myLastInputs.down) + { + if (myMode == Mode::Last) + myMode = 0; + else + myMode++; + } + + if(myInputs.left || myInputs.right) + { + myJoyRepeat++; + } + else + { + myJoyRepeat = 0; + mySpeed = 1; + } + + if(myJoyRepeat & 16) + { + myJoyRepeat = 0; + + if(myInputs.left || myInputs.right) + { + if(myMode == Mode::Time) + { + myDrawTimeCode = OSD_FRAMES; + mySpeed += 4; + if (mySpeed < 0) + mySpeed -= 4; + } + else if(myMode == Mode::Volume) + { + myDrawLevelBars = OSD_FRAMES; + if(myInputs.left) + { + if (myVolume) + myVolume--; + } + else + { + myVolume++; + if(myVolume >= MAX_LEVEL) + myVolume--; + } + } + else if(myMode == Mode::Bright) + { + myDrawLevelBars = OSD_FRAMES; + if(myInputs.left) + { + if(myBright) + myBright--; + } + else + { + myBright++; + if(myBright >= MAX_LEVEL) + myBright--; + } + } + } + } + + if(myInputs.select && !myLastInputs.select) + { + myDrawTimeCode = OSD_FRAMES; + myFrameNumber -= 60 * BACK_SECONDS + 1; + //goto update_stream; + myLastInputs = myInputs; + return; + } + + if(myInputs.fire && !myLastInputs.fire) + myPlaying = !myPlaying; + + switch(myMode) + { + case Mode::Time: + if(lastMainMode != myMode) + myDrawTimeCode = OSD_FRAMES; + break; + + case Mode::Bright: + case Mode::Volume: + default: + if(lastMainMode != myMode) + myDrawLevelBars = OSD_FRAMES; + break; + } + + // just draw one + if(myDrawLevelBars > myDrawTimeCode) + myDrawTimeCode = 0; + else + myDrawLevelBars = 0; + + if(myPlaying) + myVolumeScale = scales[myVolume]; + else + myVolumeScale = scales[0]; + + // update frame + Int8 step = 1; + + if(!myPlaying) // step while paused + { + if(myMode == Mode::Time) + { + if(myInputs.right && !myLastInputs.right) + step = 1; + else if(myInputs.left && !myLastInputs.left) + step = -3; + else + step = (myFrameNumber & 1) ? -1 : 1; + } + else + { + step = (myFrameNumber & 1) ? -1 : 1; + } + } + else + { + if(myMode == Mode::Time) + { + if(myInputs.right) + step = mySpeed; + else if (myInputs.left) + step = -mySpeed; + } + else + { + step = 1; + } + } + + myFrameNumber += step; + if(myFrameNumber < 1) + { + myFrameNumber = 1; + mySpeed = 1; + } + + myLastInputs = myInputs; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void MovieCart::fill_addr_right_line() +{ + writeAudio(addr_set_aud_right + 1); + + writeGraph(addr_set_gdata5 + 1); + writeGraph(addr_set_gdata6 + 1); + writeGraph(addr_set_gdata7 + 1); + writeGraph(addr_set_gdata8 + 1); + writeGraph(addr_set_gdata9 + 1); + + writeColor(addr_set_gcol5 + 1); + writeColor(addr_set_gcol6 + 1); + writeColor(addr_set_gcol7 + 1); + writeColor(addr_set_gcol8 + 1); + writeColor(addr_set_gcol9 + 1); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void MovieCart::fill_addr_left_line(bool again) +{ + writeAudio(addr_set_aud_left + 1); + + writeGraph(addr_set_gdata0 + 1); + writeGraph(addr_set_gdata1 + 1); + writeGraph(addr_set_gdata2 + 1); + writeGraph(addr_set_gdata3 + 1); + writeGraph(addr_set_gdata4 + 1); + + writeColor(addr_set_gcol0 + 1); + writeColor(addr_set_gcol1 + 1); + writeColor(addr_set_gcol2 + 1); + writeColor(addr_set_gcol3 + 1); + writeColor(addr_set_gcol4 + 1); + + // addr_pick_line_end + // jmp right_line + // jmp end_lines + if(again) + { + writeROM(addr_pick_continue + 1, LO_JUMP_BYTE(addr_right_line)); + writeROM(addr_pick_continue + 2, HI_JUMP_BYTE(addr_right_line)); + } + else + { + writeROM(addr_pick_continue + 1, LO_JUMP_BYTE(addr_end_lines)); + writeROM(addr_pick_continue + 2, HI_JUMP_BYTE(addr_end_lines)); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void MovieCart::fill_addr_end_lines() +{ + writeAudio(addr_set_aud_endlines + 1); + + if(!myOdd) + myFirstAudioVal = myStream.readAudio(); + + // keep at overscan=29, vblank=36 + // or overscan=30, vblank=36 + 1 blank line + + if(myOdd) + { + writeROM(addr_set_overscan_size + 1, 29); + writeROM(addr_set_vblank_size + 1, 36); + + writeROM(addr_pick_extra_lines + 1, 0); + } + else + { + writeROM(addr_set_overscan_size + 1, 30); + writeROM(addr_set_vblank_size + 1, 36); + + // extra line after vblank + writeROM(addr_pick_extra_lines + 1, 1); + } + + if(!myBufferIndex) + { + writeROM(addr_pick_transport + 1, LO_JUMP_BYTE(addr_transport_direction)); + writeROM(addr_pick_transport + 2, HI_JUMP_BYTE(addr_transport_direction)); + } + else + { + writeROM(addr_pick_transport + 1, LO_JUMP_BYTE(addr_transport_buttons)); + writeROM(addr_pick_transport + 2, HI_JUMP_BYTE(addr_transport_buttons)); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void MovieCart::fill_addr_blank_lines() +{ + // version number + myStream.readVersion(); + myStream.readVersion(); + myStream.readVersion(); + myStream.readVersion(); + + // frame number + myStream.readFrame(); + myStream.readFrame(); + uInt8 v = myStream.readFrame(); + + // make sure we're in sync with frame data + myOdd = (v & 1); + + // 30 overscan + // 3 vsync + // 37 vblank + + if(myOdd) + { + writeAudioData(addr_audio_bank + 0, myFirstAudioVal); + for(uInt8 i = 1; i < (BLANK_LINE_SIZE + 1); i++) + writeAudio(addr_audio_bank + i); + } + else + { + for(uInt8 i = 0; i < (BLANK_LINE_SIZE -1); i++) + writeAudio(addr_audio_bank + i); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void MovieCart::runStateMachine() +{ + switch(myState) + { + case 1: + if(myA7) + { + if(myLines == (TIMECODE_HEIGHT-1)) + { + if(myDrawTimeCode) + { + myDrawTimeCode--; + myForceColor = COLOR_BLUE; + myStream.startTimeCode(); + } + } + + // label = 12, bars = 7 + if(myLines == 21) + { + if(myDrawLevelBars) + { + myDrawLevelBars--; + myForceColor = COLOR_BLUE; + + switch(myMode) + { + case Mode::Time: + myStream.overrideGraph(nullptr); + break; + + case Mode::Bright: + if(myOdd) + myStream.overrideGraph(brightLabelOdd); + else + myStream.overrideGraph(brightLabelEven); + break; + + case Mode::Volume: + default: + if(myOdd) + myStream.overrideGraph(volumeLabelOdd); + else + myStream.overrideGraph(volumeLabelEven); + break; + } + } + } + + if(myLines == 7) + { + if(myDrawLevelBars) + { + uInt8 levelValue = 0; + + switch(myMode) + { + case Mode::Time: + levelValue = 0; + break; + + case Mode::Bright: + levelValue = myBright; + break; + + case Mode::Volume: + default: + levelValue = myVolume; + break; + } + + if(myOdd) + myStream.overrideGraph(&levelBarsOddData[levelValue * 40]); + else + myStream.overrideGraph(&levelBarsEvenData[levelValue * 40]); + } + } + + fill_addr_right_line(); + + myLines -= 1; + myState = 2; + } + break; + + case 2: + if(!myA7) + { + if(myLines >= 1) + { + fill_addr_left_line(1); + + myLines -= 1; + myState = 1; + } + else + { + fill_addr_left_line(0); + fill_addr_end_lines(); + + myStream.swapField(myBufferIndex); + myStream.blankPartialLines(myOdd); + + myBufferIndex = !myBufferIndex; + updateTransport(); + + fill_addr_blank_lines(); + + myState = 3; + } + } + break; + + case 3: + if(myA7) + { + // hit end? rewind just before end + while (myFrameNumber >= 2 && + !myStream.readField(myFrameNumber, myBufferIndex)) + { + myFrameNumber -= 2; + myJoyRepeat = 0; + } + + myForceColor = 0; + myLines = 191; + myState = 1; + } + break; + + default: + break; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool MovieCart::process(uInt16 address) +{ + bool a12 = (address & (1 << 12)) ? 1:0; + bool a11 = (address & (1 << 11)) ? 1:0; + + // count a10 pulses + bool a10i = (address & (1 << 10)); + if(a10i && !myA10) + myA10_Count++; + myA10 = a10i; + + // latch a7 state + if(a11) // a12 + myA7 = (address & (1 << 7)); // each 128 + + switch(myTitleState) + { + case TitleState::Display: + myTitleCycles++; + if(myTitleCycles == TITLE_CYCLES) + { + stopTitleScreen(); + myTitleState = TitleState::Exiting; + myTitleCycles = 0; + } + break; + + case TitleState::Exiting: + if(myA7) + myTitleState = TitleState::Stream; + break; + + case TitleState::Stream: + runStateMachine(); + break; + } + + return a12; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool MovieCart::save(Serializer& out) const +{ + try + { + out.putByteArray(myROM, 1024); + + // title screen state + out.putInt(myTitleCycles); + out.putInt(static_cast(myTitleState)); + + // address info + out.putBool(myA7); + out.putBool(myA10); + out.putByte(myA10_Count); + + // state machine info + out.putByte(myState); + out.putBool(myPlaying); + out.putBool(myOdd); + out.putBool(myBufferIndex); + + out.putByte(myLines); + out.putInt(myFrameNumber); + + out.putByte(myMode); + out.putByte(myBright); + out.putByte(myForceColor); + + // expressed in frames + out.putByte(myDrawLevelBars); + out.putByte(myDrawTimeCode); + + if(!myStream.save(out)) throw; + if(!myInputs.save(out)) throw; + if(!myLastInputs.save(out)) throw; + + out.putByte(mySpeed); + out.putByte(myJoyRepeat); + out.putByte(myDirectionValue); + out.putByte(myButtonsValue); + + out.putByte(myVolume); + // FIXME - determine whether we need to load/save this + // const uInt8* myVolumeScale{scales[DEFAULT_LEVEL]}; + out.putByte(myFirstAudioVal); + } + catch(...) + { + return false; + } + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool MovieCart::load(Serializer& in) +{ + try + { + in.getByteArray(myROM, 1024); + + // title screen state + myTitleCycles = in.getInt(); + myTitleState = static_cast(in.getInt()); + + // address info + myA7 = in.getBool(); + myA10 = in.getBool(); + myA10_Count = in.getByte(); + + // state machine info + myState = in.getByte(); + myPlaying = in.getBool(); + myOdd = in.getBool(); + myBufferIndex = in.getBool(); + + myLines = in.getByte(); + myFrameNumber = in.getInt(); + + myMode = in.getByte(); + myBright = in.getByte(); + myForceColor = in.getByte(); + + // expressed in frames + myDrawLevelBars = in.getByte(); + myDrawTimeCode = in.getByte(); + + if(!myStream.load(in)) throw; + if(!myInputs.load(in)) throw; + if(!myLastInputs.load(in)) throw; + + mySpeed = in.getByte(); + myJoyRepeat = in.getByte(); + myDirectionValue = in.getByte(); + myButtonsValue = in.getByte(); + + myVolume = in.getByte(); + // FIXME - determine whether we need to load/save this + // const uInt8* myVolumeScale{scales[DEFAULT_LEVEL]}; + myFirstAudioVal = in.getByte(); + } + catch(...) + { + return false; + } + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +CartridgeMVC::CartridgeMVC(const string& path, size_t size, + const string& md5, const Settings& settings, + size_t bsSize) + : Cartridge(settings, md5), + mySize{bsSize}, + myPath{path} +{ + myMovie = make_unique(); + + // not used + myImage = make_unique(mySize); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +CartridgeMVC::~CartridgeMVC() +{ +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void CartridgeMVC::install(System& system) +{ + mySystem = &system; + + // Map all of the accesses to call peek and poke + System::PageAccess access(this, System::PageAccessType::READWRITE); + for(uInt16 addr = 0x1000; addr < 0x2000; addr += System::PAGE_SIZE) + mySystem->setPageAccess(addr, access); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void CartridgeMVC::reset() +{ + myMovie->init(myPath); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +const ByteBuffer& CartridgeMVC::getImage(size_t& size) const +{ + // not used + size = mySize; + return myImage; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool CartridgeMVC::patch(uInt16 address, uInt8 value) +{ + myMovie->writeROM(address, value); + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt8 CartridgeMVC::peek(uInt16 address) +{ + myMovie->process(address); + return myMovie->readROM(address); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool CartridgeMVC::poke(uInt16 address, uInt8 value) +{ + return myMovie->process(address); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool CartridgeMVC::save(Serializer& out) const +{ + try + { + if(!myMovie->save(out)) throw; + } + catch(...) + { + return false; + } + + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool CartridgeMVC::load(Serializer& in) +{ + try + { + if(!myMovie->load(in)) throw; + } + catch(...) + { + return false; + } + + return true; +} diff --git a/src/emucore/CartMVC.hxx b/src/emucore/CartMVC.hxx new file mode 100644 index 000000000..c03101b52 --- /dev/null +++ b/src/emucore/CartMVC.hxx @@ -0,0 +1,144 @@ +//============================================================================ +// +// 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-2021 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 CARTRIDGEMVC_HXX +#define CARTRIDGEMVC_HXX + +class System; +class MovieCart; + +#include "bspf.hxx" +#include "Cart.hxx" + +/** + Implementation of MovieCart. + 1K of memory is presented on the bus, but is repeated to fill the 4K image space. + Contents are dynamically altered with streaming image and audio content as specific + 128-byte regions are entered. + Original implementation: github.com/lodefmode/moviecart + + @author Rob Bairos +*/ +class CartridgeMVC : public Cartridge +{ + public: + static constexpr uInt32 + MVC_FIELD_SIZE = 2560, // round field to nearest 512 byte boundary + MVC_FIELD_PAD_SIZE = 4096; // round to nearest 4K + + public: + /** + Create a new cartridge using the specified image + + @param path Path to the ROM image file + @param size The size of the ROM image (<= 2048 bytes) + @param md5 The md5sum of the ROM image + @param settings A reference to the various settings (read-only) + @param bsSize The size specified by the bankswitching scheme + */ + CartridgeMVC(const string& path, size_t size, const string& md5, + const Settings& settings, size_t bsSize = 8_KB); + ~CartridgeMVC() override; + + /** + Reset device to its power-on state + */ + void reset() override; + + /** + Install cartridge in the specified system. Invoked by the system + when the cartridge is attached to it. + + @param system The system the device should install itself in + */ + void install(System& system) override; + + /** + Access the internal ROM image for this cartridge. + + @param size Set to the size of the internal ROM image data + @return A reference to the internal ROM image data + */ + const ByteBuffer& getImage(size_t& size) const override; + + /** + Patch the cartridge ROM. + + @param address The ROM address to patch + @param value The value to place into the address + @return Success or failure of the patch operation + */ + bool patch(uInt16 address, uInt8 value) override; + + /** + Get the byte at the specified address. + + @return The byte at the specified address + */ + uInt8 peek(uInt16 address) override; + + /** + Change the byte at the specified address to the given value + + @param address The address where the value should be stored + @param value The value to be stored at the address + @return True if the poke changed the device address space, else false + */ + bool poke(uInt16 address, uInt8 value) override; + + /** + Get a descriptor for the device name (used in error checking). + + @return The name of the object + */ + string name() const override { return "CartridgeMVC"; } + + /** + Save the current state of this cart 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 cart from the given Serializer. + + @param in The Serializer object to use + @return False on any errors, else true + */ + bool load(Serializer& in) override; + + private: + // Currently not used: + // Pointer to a dynamically allocated ROM image of the cartridge + ByteBuffer myImage{nullptr}; + size_t mySize{0}; + + unique_ptr myMovie; + string myPath; + + private: + // Following constructors and assignment operators not supported + CartridgeMVC() = delete; + CartridgeMVC(const CartridgeMVC&) = delete; + CartridgeMVC(CartridgeMVC&&) = delete; + CartridgeMVC& operator=(const CartridgeMVC&) = delete; + CartridgeMVC& operator=(CartridgeMVC&&) = delete; +}; + +#endif diff --git a/src/emucore/FSNode.cxx b/src/emucore/FSNode.cxx index b0aa64394..f240ebadc 100644 --- a/src/emucore/FSNode.cxx +++ b/src/emucore/FSNode.cxx @@ -17,6 +17,7 @@ #include "FSNodeFactory.hxx" #include "FSNode.hxx" +#include "CartDetector.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FilesystemNode::FilesystemNode(const AbstractFSNodePtr& realNode) @@ -332,7 +333,7 @@ bool FilesystemNode::rename(const string& newfile) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -size_t FilesystemNode::read(ByteBuffer& buffer) const +size_t FilesystemNode::read(ByteBuffer& buffer, size_t size) const { size_t sizeRead = 0; @@ -341,7 +342,7 @@ size_t FilesystemNode::read(ByteBuffer& buffer) const throw runtime_error("File not found/readable"); // First let the private subclass attempt to open the file - if (_realNode && (sizeRead = _realNode->read(buffer)) > 0) + if (_realNode && (sizeRead = _realNode->read(buffer, size)) > 0) return sizeRead; // Otherwise, the default behaviour is to read from a normal C++ ifstream @@ -354,6 +355,8 @@ size_t FilesystemNode::read(ByteBuffer& buffer) const if (sizeRead == 0) throw runtime_error("Zero-byte file"); + else if (size != 0) + sizeRead = size; buffer = make_unique(sizeRead); in.read(reinterpret_cast(buffer.get()), sizeRead); diff --git a/src/emucore/FSNode.hxx b/src/emucore/FSNode.hxx index d2b9fbc00..8cb84944e 100644 --- a/src/emucore/FSNode.hxx +++ b/src/emucore/FSNode.hxx @@ -244,12 +244,13 @@ class FilesystemNode * Read data (binary format) into the given buffer. * * @param buffer The buffer to contain the data (allocated in this method). + * @param size The amount of data to read (0 means read all data). * * @return The number of bytes read (0 in the case of failure) * This method can throw exceptions, and should be used inside * a try-catch block. */ - size_t read(ByteBuffer& buffer) const; + size_t read(ByteBuffer& buffer, size_t size = 0) const; /** * Read data (text format) into the given stream. @@ -438,12 +439,13 @@ class AbstractFSNode * Read data (binary format) into the given buffer. * * @param buffer The buffer to contain the data (allocated in this method). + * @param size The amount of data to read (0 means read all data). * * @return The number of bytes read (0 in the case of failure) * This method can throw exceptions, and should be used inside * a try-catch block. */ - virtual size_t read(ByteBuffer& buffer) const { return 0; } + virtual size_t read(ByteBuffer& buffer, size_t size) const { return 0; } /** * Read data (text format) into the given stream. diff --git a/src/emucore/OSystem.cxx b/src/emucore/OSystem.cxx index 679e4ed44..fdb1e8cd2 100644 --- a/src/emucore/OSystem.cxx +++ b/src/emucore/OSystem.cxx @@ -44,6 +44,7 @@ #include "MD5.hxx" #include "Cart.hxx" #include "CartCreator.hxx" +#include "CartDetector.hxx" #include "FrameBuffer.hxx" #include "TIASurface.hxx" #include "TIAConstants.hxx" @@ -689,8 +690,16 @@ ByteBuffer OSystem::openROM(const FilesystemNode& rom, string& md5, size_t& size // but also adds a properties entry if the one for the ROM doesn't // contain a valid name + // First check if this is a 'streaming' ROM (one where we only read + // a portion of the file) + size_t sizeToRead = CartDetector::isProbablyMVC(rom); + + // Next check if rom is a valid size + // TODO: We should check if ROM is < Cart::maxSize(), but only + // if it's not a ZIP file (that size should be higher; still TBD) + ByteBuffer image; - if((size = rom.read(image)) == 0) + if((size = rom.read(image, sizeToRead)) == 0) return nullptr; // If we get to this point, we know we have a valid file to open diff --git a/src/emucore/Serializer.cxx b/src/emucore/Serializer.cxx index 5f8dbc06d..57657712a 100644 --- a/src/emucore/Serializer.cxx +++ b/src/emucore/Serializer.cxx @@ -77,6 +77,14 @@ Serializer::Serializer() } } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Serializer::setPosition(size_t pos) +{ + myStream->clear(); + myStream->seekg(pos); + myStream->seekp(pos); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Serializer::rewind() { @@ -86,11 +94,15 @@ void Serializer::rewind() } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -size_t Serializer::size() const +size_t Serializer::size() { - myStream->seekp(0, std::ios::end); + std::streampos oldPos = myStream->tellp(); - return myStream->tellp(); + myStream->seekp(0, std::ios::end); + size_t s = myStream->tellp(); + setPosition(oldPos); + + return s; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/emucore/Serializer.hxx b/src/emucore/Serializer.hxx index 362977fb7..5f0b28eb1 100644 --- a/src/emucore/Serializer.hxx +++ b/src/emucore/Serializer.hxx @@ -59,15 +59,20 @@ class Serializer */ explicit operator bool() const { return myStream != nullptr; } + /** + Sets the read/write location to the given offset in the stream. + */ + void setPosition(size_t pos); + /** Resets the read/write location to the beginning of the stream. */ void rewind(); /** - Returns the current write pointer location. + Returns the current total size of the stream. */ - size_t size() const; + size_t size(); /** Reads a byte value (unsigned 8-bit) from the current input stream. @@ -220,13 +225,6 @@ class Serializer unique_ptr myStream; static constexpr uInt8 TruePattern = 0xfe, FalsePattern = 0x01; - - private: - // Following constructors and assignment operators not supported - Serializer(const Serializer&) = delete; - Serializer(Serializer&&) = delete; - Serializer& operator=(const Serializer&) = delete; - Serializer& operator=(Serializer&&) = delete; }; #endif diff --git a/src/emucore/module.mk b/src/emucore/module.mk index 906aa8dc3..0eff6a02b 100644 --- a/src/emucore/module.mk +++ b/src/emucore/module.mk @@ -48,6 +48,7 @@ MODULE_OBJS := \ src/emucore/CartFC.o \ src/emucore/CartFE.o \ src/emucore/CartMDM.o \ + src/emucore/CartMVC.o \ src/emucore/CartSB.o \ src/emucore/CartTVBoy.o \ src/emucore/CartUA.o \ diff --git a/src/libretro/FSNodeLIBRETRO.cxx b/src/libretro/FSNodeLIBRETRO.cxx index 12c377ed6..cb4d24011 100644 --- a/src/libretro/FSNodeLIBRETRO.cxx +++ b/src/libretro/FSNodeLIBRETRO.cxx @@ -92,7 +92,7 @@ AbstractFSNodePtr FilesystemNodeLIBRETRO::getParent() const } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -size_t FilesystemNodeLIBRETRO::read(ByteBuffer& image) const +size_t FilesystemNodeLIBRETRO::read(ByteBuffer& image, size_t) const { image = make_unique(Cartridge::maxSize()); diff --git a/src/libretro/FSNodeLIBRETRO.hxx b/src/libretro/FSNodeLIBRETRO.hxx index 5dd17d0d7..a7fcb211d 100644 --- a/src/libretro/FSNodeLIBRETRO.hxx +++ b/src/libretro/FSNodeLIBRETRO.hxx @@ -49,7 +49,7 @@ class FilesystemNodeLIBRETRO : public AbstractFSNode bool getChildren(AbstractFSList& list, ListMode mode) const override; AbstractFSNodePtr getParent() const override; - size_t read(ByteBuffer& image) const override; + size_t read(ByteBuffer& image, size_t) const override; protected: string _name; diff --git a/src/libretro/Makefile.common b/src/libretro/Makefile.common index 9300df604..924db2ef2 100644 --- a/src/libretro/Makefile.common +++ b/src/libretro/Makefile.common @@ -86,6 +86,7 @@ SOURCES_CXX := \ $(CORE_DIR)/emucore/CartFC.cxx \ $(CORE_DIR)/emucore/CartFE.cxx \ $(CORE_DIR)/emucore/CartMDM.cxx \ + $(CORE_DIR)/emucore/CartMVC.cxx \ $(CORE_DIR)/emucore/CartMNetwork.cxx \ $(CORE_DIR)/emucore/CartSB.cxx \ $(CORE_DIR)/emucore/CartTVBoy.cxx \ diff --git a/src/macos/stella.xcodeproj/project.pbxproj b/src/macos/stella.xcodeproj/project.pbxproj index e68ce8f32..330ac6491 100644 --- a/src/macos/stella.xcodeproj/project.pbxproj +++ b/src/macos/stella.xcodeproj/project.pbxproj @@ -455,6 +455,8 @@ DC8C1BB114B25DE7006440EE /* MindLink.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DC8C1BAB14B25DE7006440EE /* MindLink.cxx */; }; DC8C1BB214B25DE7006440EE /* MindLink.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC8C1BAC14B25DE7006440EE /* MindLink.hxx */; }; DC8CF9BD17C15A27004B533D /* ConsoleMediumFont.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC8CF9BC17C15A27004B533D /* ConsoleMediumFont.hxx */; }; + DC911C7526333B9200666AC0 /* CartMVC.cxx in Sources */ = {isa = PBXBuildFile; fileRef = DC911C7326333B9100666AC0 /* CartMVC.cxx */; }; + DC911C7626333B9200666AC0 /* CartMVC.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC911C7426333B9100666AC0 /* CartMVC.hxx */; }; DC932D440F278A5200FEFEFC /* DefProps.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC932D3F0F278A5200FEFEFC /* DefProps.hxx */; }; DC932D450F278A5200FEFEFC /* Serializable.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC932D400F278A5200FEFEFC /* Serializable.hxx */; }; DC932D460F278A5200FEFEFC /* SerialPort.hxx in Headers */ = {isa = PBXBuildFile; fileRef = DC932D410F278A5200FEFEFC /* SerialPort.hxx */; }; @@ -1266,6 +1268,8 @@ DC8C1BAB14B25DE7006440EE /* MindLink.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MindLink.cxx; sourceTree = ""; }; DC8C1BAC14B25DE7006440EE /* MindLink.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MindLink.hxx; sourceTree = ""; }; DC8CF9BC17C15A27004B533D /* ConsoleMediumFont.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ConsoleMediumFont.hxx; sourceTree = ""; }; + DC911C7326333B9100666AC0 /* CartMVC.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CartMVC.cxx; sourceTree = ""; }; + DC911C7426333B9100666AC0 /* CartMVC.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CartMVC.hxx; sourceTree = ""; }; DC932D3F0F278A5200FEFEFC /* DefProps.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = DefProps.hxx; sourceTree = ""; }; DC932D400F278A5200FEFEFC /* Serializable.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Serializable.hxx; sourceTree = ""; }; DC932D410F278A5200FEFEFC /* SerialPort.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SerialPort.hxx; sourceTree = ""; }; @@ -2056,6 +2060,8 @@ DC6A18FB19B3E67A00DEB242 /* CartMDM.hxx */, DC71EA9B1FDA06D2008827CB /* CartMNetwork.cxx */, DC71EA9C1FDA06D2008827CB /* CartMNetwork.hxx */, + DC911C7326333B9100666AC0 /* CartMVC.cxx */, + DC911C7426333B9100666AC0 /* CartMVC.hxx */, DC0984830D3985160073C852 /* CartSB.cxx */, DC0984840D3985160073C852 /* CartSB.hxx */, DC84397A247B294D00C6A4FC /* CartTVBoy.cxx */, @@ -2936,6 +2942,7 @@ DC3EE8631E2C0E6D00905161 /* inffast.h in Headers */, DC676A501729A0B000E4E73D /* CartE0Widget.hxx in Headers */, DC676A521729A0B000E4E73D /* CartE7Widget.hxx in Headers */, + DC911C7626333B9200666AC0 /* CartMVC.hxx in Headers */, DC676A541729A0B000E4E73D /* CartFA2Widget.hxx in Headers */, DC676A561729A0B000E4E73D /* CartFEWidget.hxx in Headers */, DC676A5A1729A0B000E4E73D /* CartSBWidget.hxx in Headers */, @@ -3410,6 +3417,7 @@ DCAACAFE188D631500A4D282 /* CartDFSC.cxx in Sources */, DCAACB0E188D636F00A4D282 /* Cart4KSCWidget.cxx in Sources */, DCB60AD02543100900A5C1D2 /* FBBackendSDL2.cxx in Sources */, + DC911C7526333B9200666AC0 /* CartMVC.cxx in Sources */, DCAACB10188D636F00A4D282 /* CartBFSCWidget.cxx in Sources */, DCC2FDF6255EB82500FA5E81 /* ToolTip.cxx in Sources */, DCAACB12188D636F00A4D282 /* CartBFWidget.cxx in Sources */, diff --git a/src/windows/Stella.vcxproj b/src/windows/Stella.vcxproj old mode 100644 new mode 100755 index a091cf4da..d16885fff --- a/src/windows/Stella.vcxproj +++ b/src/windows/Stella.vcxproj @@ -759,6 +759,7 @@ + @@ -1829,6 +1830,7 @@ + diff --git a/src/windows/Stella.vcxproj.filters b/src/windows/Stella.vcxproj.filters index 46033b99a..7bd3bbeca 100644 --- a/src/windows/Stella.vcxproj.filters +++ b/src/windows/Stella.vcxproj.filters @@ -1110,6 +1110,9 @@ Source Files\repository + + Source Files\emucore + Source Files\emucore @@ -2288,6 +2291,9 @@ Header Files\emucore + + Header Files\emucore + Header Files\emucore @@ -2306,4 +2312,4 @@ Resource Files - \ No newline at end of file + diff --git a/test/roms/bankswitching/MVC/20centuryfox.bin b/test/roms/bankswitching/MVC/20centuryfox.bin new file mode 100755 index 000000000..820241b3f Binary files /dev/null and b/test/roms/bankswitching/MVC/20centuryfox.bin differ diff --git a/test/roms/bankswitching/MVC/cronkite.bin b/test/roms/bankswitching/MVC/cronkite.bin new file mode 100755 index 000000000..12ee99f30 Binary files /dev/null and b/test/roms/bankswitching/MVC/cronkite.bin differ diff --git a/test/roms/bankswitching/MVC/dancer.bin b/test/roms/bankswitching/MVC/dancer.bin new file mode 100755 index 000000000..0df041e90 Binary files /dev/null and b/test/roms/bankswitching/MVC/dancer.bin differ diff --git a/test/roms/bankswitching/MVC/lion.bin b/test/roms/bankswitching/MVC/lion.bin new file mode 100755 index 000000000..5b17f6b55 Binary files /dev/null and b/test/roms/bankswitching/MVC/lion.bin differ diff --git a/test/roms/bankswitching/MVC/street.bin b/test/roms/bankswitching/MVC/street.bin new file mode 100755 index 000000000..c4354e3e1 Binary files /dev/null and b/test/roms/bankswitching/MVC/street.bin differ diff --git a/test/roms/bankswitching/MVC/testpattern.bin b/test/roms/bankswitching/MVC/testpattern.bin new file mode 100755 index 000000000..00d3a65f1 Binary files /dev/null and b/test/roms/bankswitching/MVC/testpattern.bin differ