/* main.cpp * * The big, magical entry point. */ #include #include #include #include "cxxopts.hpp" #include "PlipInstance.h" #include "Config.h" #include "SDL/SdlAudio.h" #include "SDL/SdlEvent.h" #include "SDL/SdlWindow.h" #ifdef UNIX #include "Timer/TimerPosix.h" #else #include "Timer/TimerSdl.h" #endif std::vector> defaultConfig = { { "video", "scale" , "1" }, { "video", "targetFps", "60" } }; std::vector> intParamMapping = { { "scale", "video", "scale" }, { "fps" , "video", "targetFps" } }; void gameLoop(Plip::PlipInstance *plip, PlipSdl::Config *config, PlipSdl::SdlEvent *event, PlipSdl::Timer *timer) { auto audio = plip->GetAudio(); auto video = plip->GetVideo(); auto targetFps = config->GetValue("video", "targetFps"); auto frameTime = 1000000000 / targetFps; auto running = true; while(running) { timer->StopwatchStart(); if(event->ProcessEvents() == PlipSdl::SdlUiEvent::Quit) running = false; // As implemented, this will not be able to compensate for the host being // unable to keep up with the emulation core. // TODO: Fix this so that it will skip frames where appropriate. plip->Run(frameTime); auto time = timer->StopwatchStop(); auto delay = frameTime - time; while(delay < 0) delay += frameTime; timer->Nanosleep(delay); } audio->DequeueAll(); } cxxopts::ParseResult parseCmdLine(int argc, char **argv) { try { cxxopts::Options options(argv[0]); options.positional_help("CORE FILENAME") .show_positional_help(); options.add_options(cxxopts::hidden_group) ("core", "the core that should be used", cxxopts::value()) ("filename", "the path to the ROM", cxxopts::value()) ("positional", "", cxxopts::value>()) ; options.add_options() ("h,help", "shows this help screen and exits") ("c,config", "specifies a config file", cxxopts::value()) ("l,list-cores", "shows a list of all supported cores and exits") ("V,version", "displays the version information and exits") ; options.add_options("Video") ( "f,fps", "sets the target frame rate", cxxopts::value()->default_value("60")) ( "s,scale", "sets the default window scaling", cxxopts::value()->default_value("1")) ; options.parse_positional({"core", "filename", "positional"}); auto result = options.parse(argc, argv); if(result.count("help")) { std::cout << options.help() << std::endl; exit(0); } return result; } catch(cxxopts::argument_incorrect_type &ex) { std::cerr << "Invalid argument type (" << ex.what() << ")" << std::endl; exit(1); } catch(cxxopts::missing_argument_exception &ex) { std::cerr << ex.what() << std::endl; exit(1); } catch(cxxopts::OptionException &ex) { std::cerr << "Error parsing arguments (" << ex.what() << ")" << std::endl; exit(1); } } int main(int argc, char **argv) { auto opts = parseCmdLine(argc, argv); auto version = Plip::PlipInstance::GetVersion(); if(opts.count("version")) { std::cout << version << std::endl; return 0; } auto coreList = Plip::PlipInstance::GetSupportedCores(); if(opts["list-cores"].count()) { std::cout << "Supported cores:\n\n"; std::cout << " Name | Description\n"; std::cout << "--------------------------------\n"; for(auto core : coreList) { std::string name = core.name; name.append(10 - name.length(), ' '); // Pad string. std::cout << " " << name << " | " << core.description << '\n'; } std::cout << std::endl; return 0; } if(!opts.count("core") || !opts.count("filename")) { std::cerr << "The name of the core and the filename must be specified!\n\n" << "Please see the usage information (" << argv[0] << " -h) for more information." << std::endl; return 1; } SDL_Init(0); auto config = new PlipSdl::Config(); for(auto opt : defaultConfig) config->SetValue(opt[0], opt[1], opt[2]); if(opts["config"].count()) { auto configFile = opts["config"].as(); if(!config->LoadFile(configFile)) std::cerr << "Error opening config file: " << configFile << std::endl; } // Check for core name validity. auto found = false; auto coreName = opts["core"].as(); auto filename = opts["filename"].as(); Plip::PlipValidCore coreTag; for(auto core : coreList) { if(core.name != coreName) continue; found = true; coreTag = core.descriptor; break; } if(!found) { std::cout << "Invalid core specified (" << coreName << ").\n\n"; std::cout << "Please check the core list (" << argv[0] << " -l) for valid entries." << std::endl; return 1; } for(auto param : intParamMapping) { if(opts[param[0]].count()) { config->SetValue( param[1], param[2], std::to_string(opts[param[0]].as()) ); } } auto videoScale = config->GetValue("video", "scale"); auto wnd = new PlipSdl::SdlWindow(videoScale, version); auto audio = new PlipSdl::SdlAudio(); auto plip = new Plip::PlipInstance(wnd, audio); #ifdef UNIX auto timer = new PlipSdl::TimerPosix(); #else auto timer = new PlipSdl::TimerSdl(); #endif auto result = plip->Load(coreTag, filename); switch(result) { case Plip::PlipError::FileNotFound: std::cout << "File not found (" << filename << ")!\n" << std::endl; return 1; case Plip::PlipError::InvalidCore: std::cout << "BUG: Core implemented improperly!\n" << std::endl; return 1; default: break; } auto input = plip->GetInput(); auto event = new PlipSdl::SdlEvent(input); // Load inputs for the active core. std::string section = "input." + coreName; for(const auto& coreInput : input->GetInputList()) { auto key = config->GetValue(section, coreInput.second.GetDescription()); if(key == config->empty) continue; event->AddDigitalBinding(coreInput.first, key); } gameLoop(plip, config, event, timer); SDL_Quit(); return 0; }