mirror of https://github.com/stella-emu/stella.git
718 lines
22 KiB
C++
718 lines
22 KiB
C++
//============================================================================
|
|
//
|
|
// 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-2018 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 <cassert>
|
|
|
|
#include "bspf.hxx"
|
|
|
|
#include "MediaFactory.hxx"
|
|
#include "Sound.hxx"
|
|
|
|
#ifdef DEBUGGER_SUPPORT
|
|
#include "Debugger.hxx"
|
|
#endif
|
|
|
|
#ifdef CHEATCODE_SUPPORT
|
|
#include "CheatManager.hxx"
|
|
#endif
|
|
|
|
#include <chrono>
|
|
|
|
#include "FSNode.hxx"
|
|
#include "MD5.hxx"
|
|
#include "Cart.hxx"
|
|
#include "CartDetector.hxx"
|
|
#include "FrameBuffer.hxx"
|
|
#include "TIASurface.hxx"
|
|
#include "Settings.hxx"
|
|
#include "PropsSet.hxx"
|
|
#include "EventHandler.hxx"
|
|
#include "Menu.hxx"
|
|
#include "CommandMenu.hxx"
|
|
#include "Launcher.hxx"
|
|
#include "TimeMachine.hxx"
|
|
#include "PNGLibrary.hxx"
|
|
#include "Widget.hxx"
|
|
#include "Console.hxx"
|
|
#include "Random.hxx"
|
|
#include "SerialPort.hxx"
|
|
#include "StateManager.hxx"
|
|
#include "Version.hxx"
|
|
#include "TIA.hxx"
|
|
#include "DispatchResult.hxx"
|
|
|
|
#include "OSystem.hxx"
|
|
|
|
using namespace std::chrono;
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
OSystem::OSystem()
|
|
: myLauncherUsed(false),
|
|
myQuitLoop(false)
|
|
{
|
|
// Get built-in features
|
|
#ifdef SOUND_SUPPORT
|
|
myFeatures += "Sound ";
|
|
#endif
|
|
#ifdef JOYSTICK_SUPPORT
|
|
myFeatures += "Joystick ";
|
|
#endif
|
|
#ifdef DEBUGGER_SUPPORT
|
|
myFeatures += "Debugger ";
|
|
#endif
|
|
#ifdef CHEATCODE_SUPPORT
|
|
myFeatures += "Cheats";
|
|
#endif
|
|
|
|
// Get build info
|
|
ostringstream info;
|
|
SDL_version ver;
|
|
SDL_GetVersion(&ver);
|
|
|
|
info << "Build " << STELLA_BUILD << ", using SDL " << int(ver.major)
|
|
<< "." << int(ver.minor) << "."<< int(ver.patch)
|
|
<< " [" << BSPF::ARCH << "]";
|
|
myBuildInfo = info.str();
|
|
|
|
mySettings = MediaFactory::createSettings(*this);
|
|
myRandom = make_unique<Random>(*this);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
OSystem::~OSystem()
|
|
{
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
bool OSystem::create()
|
|
{
|
|
// Get updated paths for all configuration files
|
|
setConfigPaths();
|
|
ostringstream buf;
|
|
buf << "Stella " << STELLA_VERSION << endl
|
|
<< " Features: " << myFeatures << endl
|
|
<< " " << myBuildInfo << endl << endl
|
|
<< "Base directory: '"
|
|
<< FilesystemNode(myBaseDir).getShortPath() << "'" << endl
|
|
<< "Configuration file: '"
|
|
<< FilesystemNode(myConfigFile).getShortPath() << "'" << endl
|
|
<< "User game properties: '"
|
|
<< FilesystemNode(myPropertiesFile).getShortPath() << "'" << endl;
|
|
logMessage(buf.str(), 1);
|
|
|
|
// NOTE: The framebuffer MUST be created before any other object!!!
|
|
// Get relevant information about the video hardware
|
|
// This must be done before any graphics context is created, since
|
|
// it may be needed to initialize the size of graphical objects
|
|
try { myFrameBuffer = MediaFactory::createVideo(*this); }
|
|
catch(...) { return false; }
|
|
if(!myFrameBuffer->initialize())
|
|
return false;
|
|
|
|
// Create the event handler for the system
|
|
myEventHandler = MediaFactory::createEventHandler(*this);
|
|
myEventHandler->initialize();
|
|
|
|
// Create a properties set for us to use and set it up
|
|
myPropSet = make_unique<PropertiesSet>(propertiesFile());
|
|
|
|
#ifdef CHEATCODE_SUPPORT
|
|
myCheatManager = make_unique<CheatManager>(*this);
|
|
myCheatManager->loadCheatDatabase();
|
|
#endif
|
|
|
|
// Create menu and launcher GUI objects
|
|
myMenu = make_unique<Menu>(*this);
|
|
myCommandMenu = make_unique<CommandMenu>(*this);
|
|
myTimeMachine = make_unique<TimeMachine>(*this);
|
|
myLauncher = make_unique<Launcher>(*this);
|
|
myStateManager = make_unique<StateManager>(*this);
|
|
|
|
// Create the sound object; the sound subsystem isn't actually
|
|
// opened until needed, so this is non-blocking (on those systems
|
|
// that only have a single sound device (no hardware mixing)
|
|
createSound();
|
|
|
|
// Create the serial port object
|
|
// This is used by any controller that wants to directly access
|
|
// a real serial port on the system
|
|
mySerialPort = MediaFactory::createSerialPort();
|
|
|
|
// Re-initialize random seed
|
|
myRandom->initSeed();
|
|
|
|
// Create PNG handler
|
|
myPNGLib = make_unique<PNGLibrary>(*this);
|
|
|
|
return true;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::loadConfig()
|
|
{
|
|
mySettings->loadConfig();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::saveConfig()
|
|
{
|
|
// Ask all subsystems to save their settings
|
|
if(myFrameBuffer)
|
|
myFrameBuffer->tiaSurface().ntsc().saveConfig(*mySettings);
|
|
|
|
if(mySettings)
|
|
mySettings->saveConfig();
|
|
|
|
if(myPropSet)
|
|
myPropSet->save(myPropertiesFile);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::setConfigPaths()
|
|
{
|
|
// Paths are saved with special characters preserved ('~' or '.')
|
|
// We do some error checking here, so the rest of the codebase doesn't
|
|
// have to worry about it
|
|
FilesystemNode node;
|
|
string s;
|
|
|
|
validatePath(myStateDir, "statedir", myBaseDir + "state");
|
|
validatePath(mySnapshotSaveDir, "snapsavedir", defaultSaveDir());
|
|
validatePath(mySnapshotLoadDir, "snaploaddir", defaultLoadDir());
|
|
validatePath(myNVRamDir, "nvramdir", myBaseDir + "nvram");
|
|
validatePath(myCfgDir, "cfgdir", myBaseDir + "cfg");
|
|
|
|
s = mySettings->getString("cheatfile");
|
|
if(s == "") s = myBaseDir + "stella.cht";
|
|
node = FilesystemNode(s);
|
|
myCheatFile = node.getPath();
|
|
mySettings->setValue("cheatfile", node.getShortPath());
|
|
|
|
s = mySettings->getString("palettefile");
|
|
if(s == "") s = myBaseDir + "stella.pal";
|
|
node = FilesystemNode(s);
|
|
myPaletteFile = node.getPath();
|
|
mySettings->setValue("palettefile", node.getShortPath());
|
|
|
|
s = mySettings->getString("propsfile");
|
|
if(s == "") s = myBaseDir + "stella.pro";
|
|
node = FilesystemNode(s);
|
|
myPropertiesFile = node.getPath();
|
|
mySettings->setValue("propsfile", node.getShortPath());
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
PropertiesSet& OSystem::propSet(const string& md5)
|
|
{
|
|
FilesystemNode node = FilesystemNode();
|
|
|
|
return propSet(md5, node);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
PropertiesSet& OSystem::propSet(const string& md5, const FilesystemNode& node)
|
|
{
|
|
if(md5 == EmptyString)
|
|
return *myPropSet;
|
|
else if(md5 == myGamePropSetMD5)
|
|
return *myGamePropSet;
|
|
else if (!node.exists())
|
|
return *myPropSet;
|
|
|
|
// Get a valid set of game specific properties
|
|
Properties props;
|
|
string path = myBaseDir + node.getNameWithExt(".pro");
|
|
|
|
// Create a properties set based on ROM name
|
|
FilesystemNode propNode = FilesystemNode(path);
|
|
myGamePropertiesFile = propNode.getPath();
|
|
|
|
myGamePropSet = make_unique<PropertiesSet>(myGamePropertiesFile);
|
|
|
|
// Check if game specific property file exists and has matching md5
|
|
if(myGamePropSet->size() && myGamePropSet->getMD5(md5, props))
|
|
{
|
|
myGamePropSetMD5 = md5;
|
|
return *myGamePropSet;
|
|
}
|
|
else
|
|
{
|
|
myGamePropSetMD5 = "";
|
|
return *myPropSet;
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::saveGamePropSet(const string& md5)
|
|
{
|
|
if(myGamePropSet->size() && md5 == myGamePropSetMD5)
|
|
{
|
|
myGamePropSet->save(myGamePropertiesFile);
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::setBaseDir(const string& basedir)
|
|
{
|
|
FilesystemNode node(basedir);
|
|
if(!node.isDirectory())
|
|
node.makeDir();
|
|
|
|
myBaseDir = node.getPath();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::setConfigFile(const string& file)
|
|
{
|
|
myConfigFile = FilesystemNode(file).getPath();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FBInitStatus OSystem::createFrameBuffer()
|
|
{
|
|
// Re-initialize the framebuffer to current settings
|
|
FBInitStatus fbstatus = FBInitStatus::FailComplete;
|
|
switch(myEventHandler->state())
|
|
{
|
|
case EventHandlerState::EMULATION:
|
|
case EventHandlerState::PAUSE:
|
|
case EventHandlerState::OPTIONSMENU:
|
|
case EventHandlerState::CMDMENU:
|
|
case EventHandlerState::TIMEMACHINE:
|
|
if((fbstatus = myConsole->initializeVideo()) != FBInitStatus::Success)
|
|
return fbstatus;
|
|
break;
|
|
|
|
case EventHandlerState::LAUNCHER:
|
|
if((fbstatus = myLauncher->initializeVideo()) != FBInitStatus::Success)
|
|
return fbstatus;
|
|
break;
|
|
|
|
case EventHandlerState::DEBUGGER:
|
|
#ifdef DEBUGGER_SUPPORT
|
|
if((fbstatus = myDebugger->initializeVideo()) != FBInitStatus::Success)
|
|
return fbstatus;
|
|
#endif
|
|
break;
|
|
|
|
case EventHandlerState::NONE: // Should never happen
|
|
logMessage("ERROR: Unknown emulation state in createFrameBuffer()", 0);
|
|
break;
|
|
}
|
|
return fbstatus;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::createSound()
|
|
{
|
|
if(!mySound)
|
|
mySound = MediaFactory::createAudio(*this);
|
|
#ifndef SOUND_SUPPORT
|
|
mySettings->setValue("sound", false);
|
|
#endif
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
string OSystem::createConsole(const FilesystemNode& rom, const string& md5sum,
|
|
bool newrom)
|
|
{
|
|
bool showmessage = false;
|
|
|
|
// If same ROM has been given, we reload the current one (assuming one exists)
|
|
if(!newrom && rom == myRomFile)
|
|
{
|
|
showmessage = true; // we show a message if a ROM is being reloaded
|
|
}
|
|
else
|
|
{
|
|
myRomFile = rom;
|
|
myRomMD5 = md5sum;
|
|
|
|
// Each time a new console is loaded, we simulate a cart removal
|
|
// Some carts need knowledge of this, as they behave differently
|
|
// based on how many power-cycles they've been through since plugged in
|
|
mySettings->setValue("romloadcount", 0);
|
|
}
|
|
|
|
// Create an instance of the 2600 game console
|
|
ostringstream buf;
|
|
try
|
|
{
|
|
closeConsole();
|
|
myConsole = openConsole(myRomFile, myRomMD5);
|
|
}
|
|
catch(const runtime_error& e)
|
|
{
|
|
buf << "ERROR: Couldn't create console (" << e.what() << ")";
|
|
logMessage(buf.str(), 0);
|
|
return buf.str();
|
|
}
|
|
|
|
if(myConsole)
|
|
{
|
|
#ifdef DEBUGGER_SUPPORT
|
|
myDebugger = make_unique<Debugger>(*this, *myConsole);
|
|
myDebugger->initialize();
|
|
myConsole->attachDebugger(*myDebugger);
|
|
#endif
|
|
#ifdef CHEATCODE_SUPPORT
|
|
myCheatManager->loadCheats(myRomMD5);
|
|
#endif
|
|
myEventHandler->reset(EventHandlerState::EMULATION);
|
|
myEventHandler->setMouseControllerMode(mySettings->getString("usemouse"));
|
|
if(createFrameBuffer() != FBInitStatus::Success) // Takes care of initializeVideo()
|
|
{
|
|
logMessage("ERROR: Couldn't create framebuffer for console", 0);
|
|
myEventHandler->reset(EventHandlerState::LAUNCHER);
|
|
return "ERROR: Couldn't create framebuffer for console";
|
|
}
|
|
myConsole->initializeAudio();
|
|
|
|
if(showmessage)
|
|
{
|
|
const string& id = myConsole->cartridge().multiCartID();
|
|
if(id == "")
|
|
myFrameBuffer->showMessage("New console created");
|
|
else
|
|
myFrameBuffer->showMessage("Multicart " +
|
|
myConsole->cartridge().detectedType() + ", loading ROM" + id);
|
|
}
|
|
buf << "Game console created:" << endl
|
|
<< " ROM file: " << myRomFile.getShortPath() << endl << endl
|
|
<< getROMInfo(*myConsole) << endl;
|
|
logMessage(buf.str(), 1);
|
|
|
|
myFrameBuffer->setCursorState();
|
|
|
|
// Also check if certain virtual buttons should be held down
|
|
// These must be checked each time a new console is being created
|
|
myEventHandler->handleConsoleStartupEvents();
|
|
}
|
|
return EmptyString;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
bool OSystem::reloadConsole()
|
|
{
|
|
return createConsole(myRomFile, myRomMD5, false) == EmptyString;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
bool OSystem::hasConsole() const
|
|
{
|
|
return myConsole != nullptr &&
|
|
myEventHandler->state() != EventHandlerState::LAUNCHER;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
bool OSystem::createLauncher(const string& startdir)
|
|
{
|
|
closeConsole();
|
|
|
|
if(mySound)
|
|
mySound->close();
|
|
|
|
mySettings->setValue("tmpromdir", startdir);
|
|
bool status = false;
|
|
|
|
myEventHandler->reset(EventHandlerState::LAUNCHER);
|
|
if(createFrameBuffer() == FBInitStatus::Success)
|
|
{
|
|
myLauncher->reStack();
|
|
myFrameBuffer->setCursorState();
|
|
|
|
status = true;
|
|
}
|
|
else
|
|
logMessage("ERROR: Couldn't create launcher", 0);
|
|
|
|
myLauncherUsed = myLauncherUsed || status;
|
|
return status;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
string OSystem::getROMInfo(const FilesystemNode& romfile)
|
|
{
|
|
unique_ptr<Console> console;
|
|
try
|
|
{
|
|
string md5;
|
|
console = openConsole(romfile, md5);
|
|
}
|
|
catch(const runtime_error& e)
|
|
{
|
|
ostringstream buf;
|
|
buf << "ERROR: Couldn't get ROM info (" << e.what() << ")";
|
|
return buf.str();
|
|
}
|
|
|
|
return getROMInfo(*console);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::logMessage(const string& message, uInt8 level)
|
|
{
|
|
if(level == 0)
|
|
{
|
|
cout << message << endl << std::flush;
|
|
myLogMessages += message + "\n";
|
|
}
|
|
else if(level <= uInt8(mySettings->getInt("loglevel")))
|
|
{
|
|
if(mySettings->getBool("logtoconsole"))
|
|
cout << message << endl << std::flush;
|
|
myLogMessages += message + "\n";
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
unique_ptr<Console> OSystem::openConsole(const FilesystemNode& romfile, string& md5)
|
|
{
|
|
unique_ptr<Console> console;
|
|
|
|
// Open the cartridge image and read it in
|
|
BytePtr image;
|
|
uInt32 size = 0;
|
|
if((image = openROM(romfile, md5, size)) != nullptr)
|
|
{
|
|
// Get a valid set of properties, including any entered on the commandline
|
|
// For initial creation of the Cart, we're only concerned with the BS type
|
|
Properties props;
|
|
|
|
// Load and use game specific props if existing
|
|
FilesystemNode node = FilesystemNode(romfile);
|
|
|
|
string path = myBaseDir + node.getNameWithExt(".pro");
|
|
PropertiesSet& propset = propSet(md5, romfile);
|
|
propset.getMD5(md5, props);
|
|
|
|
// Local helper method
|
|
auto CMDLINE_PROPS_UPDATE = [&](const string& name, PropertyType prop)
|
|
{
|
|
const string& s = mySettings->getString(name);
|
|
if(s != "") props.set(prop, s);
|
|
};
|
|
|
|
CMDLINE_PROPS_UPDATE("bs", Cartridge_Type);
|
|
CMDLINE_PROPS_UPDATE("type", Cartridge_Type);
|
|
|
|
// Now create the cartridge
|
|
string cartmd5 = md5;
|
|
const string& type = props.get(Cartridge_Type);
|
|
unique_ptr<Cartridge> cart =
|
|
CartDetector::create(image, size, cartmd5, type, *this);
|
|
|
|
// It's possible that the cart created was from a piece of the image,
|
|
// and that the md5 (and hence the cart) has changed
|
|
if(props.get(Cartridge_MD5) != cartmd5)
|
|
{
|
|
if(!propset.getMD5(cartmd5, props))
|
|
{
|
|
// Cart md5 wasn't found, so we create a new props for it
|
|
props.set(Cartridge_MD5, cartmd5);
|
|
props.set(Cartridge_Name, props.get(Cartridge_Name)+cart->multiCartID());
|
|
propset.insert(props, false);
|
|
}
|
|
}
|
|
|
|
CMDLINE_PROPS_UPDATE("channels", Cartridge_Sound);
|
|
CMDLINE_PROPS_UPDATE("ld", Console_LeftDifficulty);
|
|
CMDLINE_PROPS_UPDATE("rd", Console_RightDifficulty);
|
|
CMDLINE_PROPS_UPDATE("tv", Console_TelevisionType);
|
|
CMDLINE_PROPS_UPDATE("sp", Console_SwapPorts);
|
|
CMDLINE_PROPS_UPDATE("lc", Controller_Left);
|
|
CMDLINE_PROPS_UPDATE("rc", Controller_Right);
|
|
const string& s = mySettings->getString("bc");
|
|
if(s != "") { props.set(Controller_Left, s); props.set(Controller_Right, s); }
|
|
CMDLINE_PROPS_UPDATE("cp", Controller_SwapPaddles);
|
|
CMDLINE_PROPS_UPDATE("ma", Controller_MouseAxis);
|
|
CMDLINE_PROPS_UPDATE("format", Display_Format);
|
|
CMDLINE_PROPS_UPDATE("ystart", Display_YStart);
|
|
CMDLINE_PROPS_UPDATE("height", Display_Height);
|
|
CMDLINE_PROPS_UPDATE("pp", Display_Phosphor);
|
|
CMDLINE_PROPS_UPDATE("ppblend", Display_PPBlend);
|
|
|
|
// Finally, create the cart with the correct properties
|
|
if(cart)
|
|
console = make_unique<Console>(*this, cart, props);
|
|
}
|
|
|
|
return console;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::closeConsole()
|
|
{
|
|
if(myConsole)
|
|
{
|
|
#ifdef CHEATCODE_SUPPORT
|
|
// If a previous console existed, save cheats before creating a new one
|
|
myCheatManager->saveCheats(myConsole->properties().get(Cartridge_MD5));
|
|
#endif
|
|
myConsole.reset();
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
BytePtr OSystem::openROM(const FilesystemNode& rom, string& md5, uInt32& size)
|
|
{
|
|
// This method has a documented side-effect:
|
|
// It not only loads a ROM and creates an array with its contents,
|
|
// but also adds a properties entry if the one for the ROM doesn't
|
|
// contain a valid name
|
|
|
|
BytePtr image;
|
|
if((size = rom.read(image)) == 0)
|
|
return nullptr;
|
|
|
|
// If we get to this point, we know we have a valid file to open
|
|
// Now we make sure that the file has a valid properties entry
|
|
// To save time, only generate an MD5 if we really need one
|
|
if(md5 == "")
|
|
md5 = MD5::hash(image, size);
|
|
|
|
// Some games may not have a name, since there may not
|
|
// be an entry in stella.pro. In that case, we use the rom name
|
|
// and reinsert the properties object
|
|
Properties props;
|
|
myPropSet->getMD5WithInsert(rom, md5, props);
|
|
|
|
return image;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
string OSystem::getROMInfo(const Console& console)
|
|
{
|
|
const ConsoleInfo& info = console.about();
|
|
ostringstream buf;
|
|
|
|
buf << " Cart Name: " << info.CartName << endl
|
|
<< " Cart MD5: " << info.CartMD5 << endl
|
|
<< " Controller 0: " << info.Control0 << endl
|
|
<< " Controller 1: " << info.Control1 << endl
|
|
<< " Display Format: " << info.DisplayFormat << endl
|
|
<< " Bankswitch Type: " << info.BankSwitch << endl;
|
|
|
|
return buf.str();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::validatePath(string& path, const string& setting,
|
|
const string& defaultpath)
|
|
{
|
|
const string& s = mySettings->getString(setting) == "" ? defaultpath :
|
|
mySettings->getString(setting);
|
|
FilesystemNode node(s);
|
|
if(!node.isDirectory())
|
|
node.makeDir();
|
|
|
|
path = node.getPath();
|
|
mySettings->setValue(setting, node.getShortPath());
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
uInt64 OSystem::getTicks() const
|
|
{
|
|
return duration_cast<duration<uInt64, std::ratio<1, 1000000> > >(system_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
float OSystem::frameRate() const
|
|
{
|
|
return myConsole ? myConsole->getFramerate() : 0;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
double OSystem::dispatchEmulation(uInt32 cyclesPerSecond)
|
|
{
|
|
if (!myConsole) return 0.;
|
|
|
|
Int64 totalCycles = 0;
|
|
const Int64 minCycles = myConsole->emulationTiming().minCyclesPerTimeslice();
|
|
const Int64 maxCycles = myConsole->emulationTiming().maxCyclesPerTimeslice();
|
|
DispatchResult dispatchResult;
|
|
|
|
do {
|
|
myConsole->tia().update(dispatchResult, totalCycles > 0 ? minCycles - totalCycles : maxCycles);
|
|
|
|
totalCycles += dispatchResult.getCycles();
|
|
} while (totalCycles < minCycles && dispatchResult.getStatus() == DispatchResult::Status::ok);
|
|
|
|
if (dispatchResult.getStatus() == DispatchResult::Status::debugger) myDebugger->start();
|
|
|
|
if (dispatchResult.getStatus() == DispatchResult::Status::ok && myEventHandler->frying())
|
|
myConsole->fry();
|
|
|
|
return static_cast<double>(totalCycles) / static_cast<double>(cyclesPerSecond);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::mainLoop()
|
|
{
|
|
// Sleep-based wait: good for CPU, bad for graphical sync
|
|
bool busyWait = mySettings->getString("timing") != "sleep";
|
|
time_point<high_resolution_clock> virtualTime = high_resolution_clock::now();
|
|
|
|
for(;;)
|
|
{
|
|
myEventHandler->poll(getTicks());
|
|
if(myQuitLoop) break; // Exit if the user wants to quit
|
|
|
|
double timesliceSeconds;
|
|
|
|
if (myEventHandler->state() == EventHandlerState::EMULATION) {
|
|
timesliceSeconds = dispatchEmulation(myConsole ? myConsole->emulationTiming().cyclesPerSecond() : 1);
|
|
|
|
if (myConsole && myConsole->tia().newFramePending()) {
|
|
myConsole->tia().renderToFrameBuffer();
|
|
myFrameBuffer->updateInEmulationMode();
|
|
}
|
|
} else {
|
|
timesliceSeconds = 1. / 30.;
|
|
myFrameBuffer->update();
|
|
}
|
|
|
|
duration<double> timeslice(timesliceSeconds);
|
|
|
|
virtualTime += duration_cast<high_resolution_clock::duration>(timeslice);
|
|
time_point<high_resolution_clock> now = high_resolution_clock::now();
|
|
double maxLag = myConsole
|
|
? (
|
|
static_cast<double>(myConsole->emulationTiming().cyclesPerFrame()) /
|
|
static_cast<double>(myConsole->emulationTiming().cyclesPerSecond())
|
|
)
|
|
: 0;
|
|
|
|
if (duration_cast<duration<double>>(now - virtualTime).count() > maxLag)
|
|
virtualTime = now;
|
|
else if (virtualTime > now) {
|
|
if (busyWait && myEventHandler->state() == EventHandlerState::EMULATION) {
|
|
while (high_resolution_clock::now() < virtualTime);
|
|
}
|
|
else std::this_thread::sleep_until(virtualTime);
|
|
}
|
|
}
|
|
|
|
// Cleanup time
|
|
#ifdef CHEATCODE_SUPPORT
|
|
if(myConsole)
|
|
myCheatManager->saveCheats(myConsole->properties().get(Cartridge_MD5));
|
|
|
|
myCheatManager->saveCheatDatabase();
|
|
#endif
|
|
}
|