You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
223 lines
6.7 KiB
223 lines
6.7 KiB
/* main.cpp |
|
* |
|
* The big, magical entry point. |
|
*/ |
|
|
|
#include <iostream> |
|
#include <string> |
|
#include <vector> |
|
|
|
#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<std::vector<std::string>> defaultConfig = { |
|
{ "video", "scale" , "1" }, |
|
{ "video", "targetFps", "60" } |
|
}; |
|
|
|
std::vector<std::vector<std::string>> 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<int>("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<std::string>()) |
|
("filename", "the path to the ROM", cxxopts::value<std::string>()) |
|
("positional", "", cxxopts::value<std::vector<std::string>>()) |
|
; |
|
|
|
options.add_options() |
|
("h,help", "shows this help screen and exits") |
|
("c,config", "specifies a config file", cxxopts::value<std::string>()) |
|
("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<int>()->default_value("60")) |
|
( "s,scale", "sets the default window scaling", cxxopts::value<int>()->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<std::string>(); |
|
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<std::string>(); |
|
auto filename = opts["filename"].as<std::string>(); |
|
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<int>()) |
|
); |
|
} |
|
} |
|
|
|
auto videoScale = config->GetValue<int>("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; |
|
}
|
|
|