mirror of https://github.com/stella-emu/stella.git
726 lines
22 KiB
C++
726 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-2014 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.
|
|
//
|
|
// $Id$
|
|
//============================================================================
|
|
|
|
#include <cassert>
|
|
#include <sstream>
|
|
#include <fstream>
|
|
|
|
#include <ctime>
|
|
#ifdef HAVE_GETTIMEOFDAY
|
|
#include <sys/time.h>
|
|
#endif
|
|
|
|
#include "bspf.hxx"
|
|
|
|
#include "MediaFactory.hxx"
|
|
#include "Sound.hxx"
|
|
|
|
#ifdef DEBUGGER_SUPPORT
|
|
#include "Debugger.hxx"
|
|
#endif
|
|
|
|
#ifdef CHEATCODE_SUPPORT
|
|
#include "CheatManager.hxx"
|
|
#endif
|
|
|
|
#include "FSNode.hxx"
|
|
#include "MD5.hxx"
|
|
#include "Cart.hxx"
|
|
#include "Settings.hxx"
|
|
#include "PropsSet.hxx"
|
|
#include "EventHandler.hxx"
|
|
#include "Menu.hxx"
|
|
#include "CommandMenu.hxx"
|
|
#include "Launcher.hxx"
|
|
#include "Widget.hxx"
|
|
#include "Console.hxx"
|
|
#include "Random.hxx"
|
|
#include "SerialPort.hxx"
|
|
#include "StateManager.hxx"
|
|
#include "Version.hxx"
|
|
|
|
#include "OSystem.hxx"
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
OSystem::OSystem()
|
|
: myConsole(nullptr),
|
|
myLauncherUsed(false),
|
|
myDebugger(nullptr),
|
|
myQuitLoop(false),
|
|
myRomFile(""),
|
|
myRomMD5(""),
|
|
myFeatures(""),
|
|
myBuildInfo("")
|
|
{
|
|
// Calculate startup time
|
|
myMillisAtStart = (uInt32)(time(NULL) * 1000);
|
|
|
|
// 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_ptr<Random>(*this);
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
OSystem::~OSystem()
|
|
{
|
|
// Remove any game console that is currently attached
|
|
deleteConsole();
|
|
#ifdef DEBUGGER_SUPPORT
|
|
delete myDebugger;
|
|
#endif
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
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
|
|
myFrameBuffer = MediaFactory::createVideo(*this);
|
|
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_ptr<PropertiesSet>(propertiesFile());
|
|
|
|
#ifdef CHEATCODE_SUPPORT
|
|
myCheatManager = make_ptr<CheatManager>(*this);
|
|
myCheatManager->loadCheatDatabase();
|
|
#endif
|
|
|
|
// Create menu and launcher GUI objects
|
|
myMenu = make_ptr<Menu>(*this);
|
|
myCommandMenu = make_ptr<CommandMenu>(*this);
|
|
myLauncher = make_ptr<Launcher>(*this);
|
|
myStateManager = make_ptr<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_ptr<PNGLibrary>(*myFrameBuffer);
|
|
|
|
// Create ZIP handler
|
|
myZipHandler = make_ptr<ZipHandler>();
|
|
|
|
return true;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::loadConfig()
|
|
{
|
|
mySettings->loadConfig();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::saveConfig()
|
|
{
|
|
// Ask all subsystems to save their settings
|
|
if(myFrameBuffer)
|
|
myFrameBuffer->tiaSurface().ntsc().saveConfig(*mySettings);
|
|
|
|
mySettings->saveConfig();
|
|
}
|
|
|
|
#ifdef DEBUGGER_SUPPORT
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::createDebugger(Console& console)
|
|
{
|
|
delete myDebugger; myDebugger = NULL;
|
|
myDebugger = new Debugger(*this, console);
|
|
myDebugger->initialize();
|
|
}
|
|
#endif
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
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", defaultSnapSaveDir());
|
|
validatePath(mySnapshotLoadDir, "snaploaddir", defaultSnapLoadDir());
|
|
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());
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::setBaseDir(const string& basedir)
|
|
{
|
|
FilesystemNode node(basedir);
|
|
if(!node.isDirectory())
|
|
node.makeDir();
|
|
|
|
myBaseDir = node.getPath();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::setConfigFile(const string& file)
|
|
{
|
|
FilesystemNode node(file);
|
|
myConfigFile = node.getPath();
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::setFramerate(float framerate)
|
|
{
|
|
if(framerate > 0.0)
|
|
{
|
|
myDisplayFrameRate = framerate;
|
|
myTimePerFrame = (uInt32)(1000000.0 / myDisplayFrameRate);
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
FBInitStatus OSystem::createFrameBuffer()
|
|
{
|
|
// Re-initialize the framebuffer to current settings
|
|
FBInitStatus fbstatus = kFailComplete;
|
|
switch(myEventHandler->state())
|
|
{
|
|
case EventHandler::S_EMULATE:
|
|
case EventHandler::S_PAUSE:
|
|
case EventHandler::S_MENU:
|
|
case EventHandler::S_CMDMENU:
|
|
if((fbstatus = myConsole->initializeVideo()) != kSuccess)
|
|
return fbstatus;
|
|
break; // S_EMULATE, S_PAUSE, S_MENU, S_CMDMENU
|
|
|
|
case EventHandler::S_LAUNCHER:
|
|
if((fbstatus = myLauncher->initializeVideo()) != kSuccess)
|
|
return fbstatus;
|
|
break; // S_LAUNCHER
|
|
|
|
#ifdef DEBUGGER_SUPPORT
|
|
case EventHandler::S_DEBUGGER:
|
|
if((fbstatus = myDebugger->initializeVideo()) != kSuccess)
|
|
return fbstatus;
|
|
break; // S_DEBUGGER
|
|
#endif
|
|
|
|
default: // 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)
|
|
{
|
|
// Do a little error checking; it shouldn't be necessary
|
|
if(myConsole) deleteConsole();
|
|
|
|
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;
|
|
string type, id;
|
|
try
|
|
{
|
|
myConsole = openConsole(myRomFile, myRomMD5, type, id);
|
|
}
|
|
catch(const char* err_msg)
|
|
{
|
|
myConsole = 0;
|
|
buf << "ERROR: Couldn't create console (" << err_msg << ")";
|
|
logMessage(buf.str(), 0);
|
|
return buf.str();
|
|
}
|
|
|
|
if(myConsole)
|
|
{
|
|
#ifdef DEBUGGER_SUPPORT
|
|
myConsole->addDebugger();
|
|
#endif
|
|
#ifdef CHEATCODE_SUPPORT
|
|
myCheatManager->loadCheats(myRomMD5);
|
|
#endif
|
|
myEventHandler->reset(EventHandler::S_EMULATE);
|
|
myEventHandler->setMouseControllerMode(mySettings->getString("usemouse"));
|
|
if(createFrameBuffer() != kSuccess) // Takes care of initializeVideo()
|
|
{
|
|
logMessage("ERROR: Couldn't create framebuffer for console", 0);
|
|
myEventHandler->reset(EventHandler::S_LAUNCHER);
|
|
return "ERROR: Couldn't create framebuffer for console";
|
|
}
|
|
myConsole->initializeAudio();
|
|
|
|
if(showmessage)
|
|
{
|
|
if(id == "")
|
|
myFrameBuffer->showMessage("New console created");
|
|
else
|
|
myFrameBuffer->showMessage("Multicart " + type + ", loading ROM" + id);
|
|
}
|
|
buf << "Game console created:" << endl
|
|
<< " ROM file: " << myRomFile.getShortPath() << endl << endl
|
|
<< getROMInfo(myConsole) << endl;
|
|
logMessage(buf.str(), 1);
|
|
|
|
// Update the timing info for a new console run
|
|
resetLoopTiming();
|
|
|
|
myFrameBuffer->setCursorState();
|
|
|
|
// Also check if certain virtual buttons should be held down
|
|
// These must be checked each time a new console is being created
|
|
if(mySettings->getBool("holdreset"))
|
|
myEventHandler->handleEvent(Event::ConsoleReset, 1);
|
|
if(mySettings->getBool("holdselect"))
|
|
myEventHandler->handleEvent(Event::ConsoleSelect, 1);
|
|
|
|
const string& holdjoy0 = mySettings->getString("holdjoy0");
|
|
if(BSPF_containsIgnoreCase(holdjoy0, "U"))
|
|
myEventHandler->handleEvent(Event::JoystickZeroUp, 1);
|
|
if(BSPF_containsIgnoreCase(holdjoy0, "D"))
|
|
myEventHandler->handleEvent(Event::JoystickZeroDown, 1);
|
|
if(BSPF_containsIgnoreCase(holdjoy0, "L"))
|
|
myEventHandler->handleEvent(Event::JoystickZeroLeft, 1);
|
|
if(BSPF_containsIgnoreCase(holdjoy0, "R"))
|
|
myEventHandler->handleEvent(Event::JoystickZeroRight, 1);
|
|
if(BSPF_containsIgnoreCase(holdjoy0, "F"))
|
|
myEventHandler->handleEvent(Event::JoystickZeroFire, 1);
|
|
|
|
const string& holdjoy1 = mySettings->getString("holdjoy1");
|
|
if(BSPF_containsIgnoreCase(holdjoy1, "U"))
|
|
myEventHandler->handleEvent(Event::JoystickOneUp, 1);
|
|
if(BSPF_containsIgnoreCase(holdjoy1, "D"))
|
|
myEventHandler->handleEvent(Event::JoystickOneDown, 1);
|
|
if(BSPF_containsIgnoreCase(holdjoy1, "L"))
|
|
myEventHandler->handleEvent(Event::JoystickOneLeft, 1);
|
|
if(BSPF_containsIgnoreCase(holdjoy1, "R"))
|
|
myEventHandler->handleEvent(Event::JoystickOneRight, 1);
|
|
if(BSPF_containsIgnoreCase(holdjoy1, "F"))
|
|
myEventHandler->handleEvent(Event::JoystickOneFire, 1);
|
|
#ifdef DEBUGGER_SUPPORT
|
|
if(mySettings->getBool("debug"))
|
|
myEventHandler->enterDebugMode();
|
|
#endif
|
|
}
|
|
return EmptyString;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::deleteConsole()
|
|
{
|
|
if(myConsole)
|
|
{
|
|
mySound->close();
|
|
#ifdef CHEATCODE_SUPPORT
|
|
myCheatManager->saveCheats(myConsole->properties().get(Cartridge_MD5));
|
|
#endif
|
|
ostringstream buf;
|
|
double executionTime = (double) myTimingInfo.totalTime / 1000000.0;
|
|
double framesPerSecond = (double) myTimingInfo.totalFrames / executionTime;
|
|
buf << "Game console stats:" << endl
|
|
<< " Total frames drawn: " << myTimingInfo.totalFrames << endl
|
|
<< " Total time (sec): " << executionTime << endl
|
|
<< " Frames per second: " << framesPerSecond << endl
|
|
<< endl;
|
|
logMessage(buf.str(), 1);
|
|
|
|
delete myConsole; myConsole = NULL;
|
|
#ifdef DEBUGGER_SUPPORT
|
|
delete myDebugger; myDebugger = NULL;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
bool OSystem::reloadConsole()
|
|
{
|
|
deleteConsole();
|
|
return createConsole(myRomFile, myRomMD5, false) == EmptyString;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
bool OSystem::createLauncher(const string& startdir)
|
|
{
|
|
mySettings->setValue("tmpromdir", startdir);
|
|
bool status = false;
|
|
|
|
myEventHandler->reset(EventHandler::S_LAUNCHER);
|
|
if(createFrameBuffer() == kSuccess)
|
|
{
|
|
myLauncher->reStack();
|
|
myFrameBuffer->setCursorState();
|
|
myFrameBuffer->refresh();
|
|
|
|
setFramerate(60);
|
|
resetLoopTiming();
|
|
status = true;
|
|
}
|
|
else
|
|
logMessage("ERROR: Couldn't create launcher", 0);
|
|
|
|
myLauncherUsed = myLauncherUsed || status;
|
|
return status;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
string OSystem::getROMInfo(const FilesystemNode& romfile)
|
|
{
|
|
string md5, type, id, result = "";
|
|
Console* console = 0;
|
|
try
|
|
{
|
|
console = openConsole(romfile, md5, type, id);
|
|
}
|
|
catch(const char* err_msg)
|
|
{
|
|
ostringstream buf;
|
|
buf << "ERROR: Couldn't get ROM info (" << err_msg << ")";
|
|
return buf.str();
|
|
}
|
|
|
|
result = getROMInfo(console);
|
|
delete console;
|
|
return result;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::logMessage(const string& message, uInt8 level)
|
|
{
|
|
if(level == 0)
|
|
{
|
|
cout << message << endl << flush;
|
|
myLogMessages += message + "\n";
|
|
}
|
|
else if(level <= (uInt8)mySettings->getInt("loglevel"))
|
|
{
|
|
if(mySettings->getBool("logtoconsole"))
|
|
cout << message << endl << flush;
|
|
myLogMessages += message + "\n";
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
Console* OSystem::openConsole(const FilesystemNode& romfile, string& md5,
|
|
string& type, string& id)
|
|
{
|
|
#define CMDLINE_PROPS_UPDATE(cl_name, prop_name) \
|
|
s = mySettings->getString(cl_name); \
|
|
if(s != "") props.set(prop_name, s);
|
|
|
|
Console* console = (Console*) NULL;
|
|
|
|
// Open the cartridge image and read it in
|
|
uInt8* image = 0;
|
|
uInt32 size = 0;
|
|
if((image = openROM(romfile, md5, size)) != 0)
|
|
{
|
|
// 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;
|
|
myPropSet->getMD5(md5, props);
|
|
string s;
|
|
CMDLINE_PROPS_UPDATE("bs", Cartridge_Type);
|
|
CMDLINE_PROPS_UPDATE("type", Cartridge_Type);
|
|
|
|
// Now create the cartridge
|
|
string cartmd5 = md5;
|
|
type = props.get(Cartridge_Type);
|
|
Cartridge* cart =
|
|
Cartridge::create(image, size, cartmd5, type, id, *this, *mySettings);
|
|
|
|
// 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(!myPropSet->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)+id);
|
|
myPropSet->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);
|
|
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 = new Console(*this, cart, props);
|
|
}
|
|
|
|
// Free the image since we don't need it any longer
|
|
if(image != 0 && size > 0)
|
|
delete[] image;
|
|
|
|
return console;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
uInt8* 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
|
|
|
|
uInt8* image = 0;
|
|
if((size = rom.read(image)) == 0)
|
|
{
|
|
delete[] image;
|
|
return (uInt8*) 0;
|
|
}
|
|
|
|
// 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(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::resetLoopTiming()
|
|
{
|
|
myTimingInfo.start = myTimingInfo.virt = getTicks();
|
|
myTimingInfo.current = 0;
|
|
myTimingInfo.totalTime = 0;
|
|
myTimingInfo.totalFrames = 0;
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
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
|
|
{
|
|
#ifdef HAVE_GETTIMEOFDAY
|
|
// Gettimeofday natively refers to the UNIX epoch (a set time in the past)
|
|
timeval now;
|
|
gettimeofday(&now, 0);
|
|
|
|
return uInt64(now.tv_sec) * 1000000 + now.tv_usec;
|
|
#else
|
|
// We use SDL_GetTicks, but add in the time when the application was
|
|
// initialized. This is necessary, since SDL_GetTicks only measures how
|
|
// long SDL has been running, which can be the same between multiple runs
|
|
// of the application.
|
|
return uInt64(SDL_GetTicks() + myMillisAtStart) * 1000;
|
|
#endif
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
void OSystem::mainLoop()
|
|
{
|
|
if(mySettings->getString("timing") == "sleep")
|
|
{
|
|
// Sleep-based wait: good for CPU, bad for graphical sync
|
|
for(;;)
|
|
{
|
|
myTimingInfo.start = getTicks();
|
|
myEventHandler->poll(myTimingInfo.start);
|
|
if(myQuitLoop) break; // Exit if the user wants to quit
|
|
myFrameBuffer->update();
|
|
myTimingInfo.current = getTicks();
|
|
myTimingInfo.virt += myTimePerFrame;
|
|
|
|
// Timestamps may periodically go out of sync, particularly on systems
|
|
// that can have 'negative time' (ie, when the time seems to go backwards)
|
|
// This normally results in having a very large delay time, so we check
|
|
// for that and reset the timers when appropriate
|
|
if((myTimingInfo.virt - myTimingInfo.current) > (myTimePerFrame << 1))
|
|
{
|
|
myTimingInfo.start = myTimingInfo.current = myTimingInfo.virt = getTicks();
|
|
}
|
|
|
|
if(myTimingInfo.current < myTimingInfo.virt)
|
|
SDL_Delay(uInt32(myTimingInfo.virt - myTimingInfo.current) / 1000);
|
|
|
|
myTimingInfo.totalTime += (getTicks() - myTimingInfo.start);
|
|
myTimingInfo.totalFrames++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Busy-wait: bad for CPU, good for graphical sync
|
|
for(;;)
|
|
{
|
|
myTimingInfo.start = getTicks();
|
|
myEventHandler->poll(myTimingInfo.start);
|
|
if(myQuitLoop) break; // Exit if the user wants to quit
|
|
myFrameBuffer->update();
|
|
myTimingInfo.virt += myTimePerFrame;
|
|
|
|
while(getTicks() < myTimingInfo.virt)
|
|
; // busy-wait
|
|
|
|
myTimingInfo.totalTime += (getTicks() - myTimingInfo.start);
|
|
myTimingInfo.totalFrames++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
unique_ptr<ZipHandler> OSystem::myZipHandler = unique_ptr<ZipHandler>();
|