quickerNES/source/player.cpp

242 lines
8.2 KiB
C++
Raw Normal View History

2024-03-06 15:21:04 +00:00
#include "argparse/argparse.hpp"
#include "jaffarCommon/deserializers/contiguous.hpp"
#include "jaffarCommon/file.hpp"
#include "jaffarCommon/logger.hpp"
2024-07-28 14:52:05 +00:00
#include "jaffarCommon/serializers/contiguous.hpp"
2024-03-06 15:21:04 +00:00
#include "jaffarCommon/string.hpp"
#include "nesInstance.hpp"
2024-01-10 19:11:49 +00:00
#include "playbackInstance.hpp"
2024-07-28 14:52:05 +00:00
#include <cstdlib>
2024-03-22 11:50:46 +00:00
SDL_Window *launchOutputWindow()
{
// Opening rendering window
SDL_SetMainReady();
// We can only call SDL_InitSubSystem once
if (!SDL_WasInit(SDL_INIT_VIDEO))
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) JAFFAR_THROW_LOGIC("Failed to initialize video: %s", SDL_GetError());
auto window = SDL_CreateWindow("JaffarPlus", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 100, 100, SDL_WINDOW_RESIZABLE);
if (window == nullptr) JAFFAR_THROW_LOGIC("Coult not open SDL window");
return window;
}
void closeOutputWindow(SDL_Window *window) { SDL_DestroyWindow(window); }
int main(int argc, char *argv[])
{
// Parsing command line arguments
argparse::ArgumentParser program("player", "1.0");
program.add_argument("romFile")
.help("Path to the rom file to run.")
.required();
program.add_argument("sequenceFile")
.help("Path to the input sequence file (.sol) to reproduce.")
.required();
program.add_argument("stateFile")
2024-01-20 10:21:34 +00:00
.help("(Optional) Path to the initial state file to load.")
.default_value(std::string(""));
program.add_argument("--reproduce")
.help("Plays the entire sequence without interruptions and exit at the end.")
.default_value(false)
.implicit_value(true);
program.add_argument("--disableRender")
.help("Do not render game window.")
.default_value(false)
.implicit_value(true);
program.add_argument("--controller1")
.help("Specifies the controller 1 type.")
.default_value(std::string("Joypad"));
program.add_argument("--controller2")
.help("Specifies the controller 2 type.")
.default_value(std::string("None"));
// Try to parse arguments
2024-01-20 10:21:34 +00:00
try
{
program.parse_args(argc, argv);
}
catch (const std::runtime_error &err)
{
2024-03-06 15:21:04 +00:00
JAFFAR_THROW_LOGIC("%s\n%s", err.what(), program.help().str().c_str());
2024-01-20 10:21:34 +00:00
}
// Getting ROM file path
std::string romFilePath = program.get<std::string>("romFile");
// Getting sequence file path
2024-01-10 19:11:49 +00:00
std::string sequenceFilePath = program.get<std::string>("sequenceFile");
// If initial state file is specified, load it
2024-01-10 19:11:49 +00:00
std::string stateFilePath = program.get<std::string>("stateFile");
// Getting reproduce flag
bool isReproduce = program.get<bool>("--reproduce");
// Getting reproduce flag
bool disableRender = program.get<bool>("--disableRender");
2024-01-10 19:11:49 +00:00
// Getting controller 1 Type
std::string controller1Type = program.get<std::string>("--controller1");
// Getting controller 2 Type
std::string controller2Type = program.get<std::string>("--controller2");
2024-01-11 18:44:54 +00:00
// Loading sequence file
std::string inputSequence;
2024-03-06 15:21:04 +00:00
auto status = jaffarCommon::file::loadStringFromFile(inputSequence, sequenceFilePath.c_str());
if (status == false) JAFFAR_THROW_LOGIC("[ERROR] Could not find or read from sequence file: %s\n", sequenceFilePath.c_str());
2024-01-11 18:44:54 +00:00
2024-01-11 19:23:32 +00:00
// Building sequence information
2024-08-07 19:11:24 +00:00
const auto sequence = jaffarCommon::string::split(inputSequence, '\n');
2024-01-11 19:23:32 +00:00
2024-01-11 18:44:54 +00:00
// Initializing terminal
2024-03-06 15:21:04 +00:00
jaffarCommon::logger::initializeTerminal();
2024-01-11 18:44:54 +00:00
2024-01-10 19:11:49 +00:00
// Printing provided parameters
2024-03-22 11:50:46 +00:00
jaffarCommon::logger::log("[] Rom File Path: '%s'\n", romFilePath.c_str());
jaffarCommon::logger::log("[] Sequence File Path: '%s'\n", sequenceFilePath.c_str());
jaffarCommon::logger::log("[] Sequence Length: %lu\n", sequence.size());
jaffarCommon::logger::log("[] State File Path: '%s'\n", stateFilePath.empty() ? "<Boot Start>" : stateFilePath.c_str());
jaffarCommon::logger::log("[] Generating Sequence...\n");
2024-01-11 18:44:54 +00:00
2024-03-06 15:21:04 +00:00
jaffarCommon::logger::refreshTerminal();
2024-01-10 19:11:49 +00:00
2024-01-22 17:50:52 +00:00
// Creating emulator instance
2024-07-28 14:43:09 +00:00
nlohmann::json emulatorConfig;
emulatorConfig["Controller 1 Type"] = controller1Type;
emulatorConfig["Controller 2 Type"] = controller2Type;
2024-07-28 15:07:38 +00:00
NESInstance e(emulatorConfig);
2024-01-10 19:11:49 +00:00
// Loading ROM File
std::string romFileData;
2024-03-06 15:21:04 +00:00
if (jaffarCommon::file::loadStringFromFile(romFileData, romFilePath) == false) JAFFAR_THROW_LOGIC("Could not rom file: %s\n", romFilePath.c_str());
2024-07-28 14:52:05 +00:00
e.loadROM((uint8_t *)romFileData.data(), romFileData.size());
// If an initial state is provided, load it now
if (stateFilePath != "")
{
std::string stateFileData;
2024-03-06 15:21:04 +00:00
if (jaffarCommon::file::loadStringFromFile(stateFileData, stateFilePath) == false) JAFFAR_THROW_LOGIC("Could not initial state file: %s\n", stateFilePath.c_str());
2024-02-09 19:43:42 +00:00
jaffarCommon::deserializer::Contiguous deserializer(stateFileData.data());
e.deserializeState(deserializer);
}
2024-01-10 19:11:49 +00:00
// Creating playback instance
2024-03-22 11:50:46 +00:00
auto p = PlaybackInstance(&e);
// If render is enabled then, create window now
2024-07-28 14:52:05 +00:00
SDL_Window *window = nullptr;
2024-03-22 11:50:46 +00:00
if (disableRender == false)
{
window = launchOutputWindow();
p.enableRendering(window);
}
// Initializing playback instance
p.initialize(sequence);
2024-01-11 18:44:54 +00:00
2024-01-12 20:13:51 +00:00
// Getting state size
2024-02-09 19:43:42 +00:00
auto stateSize = e.getFullStateSize();
2024-01-12 20:13:51 +00:00
2024-01-11 18:44:54 +00:00
// Flag to continue running playback
bool continueRunning = true;
// Variable for current step in view
2024-01-12 20:13:51 +00:00
ssize_t sequenceLength = p.getSequenceLength();
2024-01-11 18:44:54 +00:00
ssize_t currentStep = 0;
// Flag to display frame information
bool showFrameInfo = true;
// Interactive section
2024-01-20 10:21:34 +00:00
while (continueRunning)
2024-01-11 18:44:54 +00:00
{
// Updating display
if (disableRender == false) p.renderFrame(currentStep);
// Getting input
2024-07-28 15:07:38 +00:00
const auto &inputString = p.getInputString(currentStep);
2024-01-12 20:13:51 +00:00
// Getting state hash
const auto hash = p.getStateHash(currentStep);
2024-01-11 18:44:54 +00:00
// Getting state data
2024-01-12 20:13:51 +00:00
const auto stateData = p.getStateData(currentStep);
2024-01-11 18:44:54 +00:00
// Printing data and commands
if (showFrameInfo)
{
2024-03-06 15:21:04 +00:00
jaffarCommon::logger::clearTerminal();
2024-01-11 18:44:54 +00:00
2024-03-22 11:50:46 +00:00
jaffarCommon::logger::log("[] ----------------------------------------------------------------\n");
jaffarCommon::logger::log("[] Current Step #: %lu / %lu\n", currentStep + 1, sequenceLength);
2024-07-28 15:07:38 +00:00
jaffarCommon::logger::log("[] Input: %s\n", inputString.c_str());
2024-03-22 11:50:46 +00:00
jaffarCommon::logger::log("[] State Hash: 0x%lX%lX\n", hash.first, hash.second);
2024-08-07 19:11:24 +00:00
jaffarCommon::logger::log("[] Paddle X: %u\n", e.getLowMem()[0x11A]);
2024-01-11 18:44:54 +00:00
// Only print commands if not in reproduce mode
2024-03-22 11:50:46 +00:00
if (isReproduce == false) jaffarCommon::logger::log("[] Commands: n: -1 m: +1 | h: -10 | j: +10 | y: -100 | u: +100 | k: -1000 | i: +1000 | s: quicksave | p: play | q: quit\n");
2024-01-11 18:44:54 +00:00
2024-03-06 15:21:04 +00:00
jaffarCommon::logger::refreshTerminal();
2024-01-11 18:44:54 +00:00
}
// Resetting show frame info flag
showFrameInfo = true;
// Get command
2024-03-06 15:21:04 +00:00
auto command = jaffarCommon::logger::waitForKeyPress();
2024-01-11 18:44:54 +00:00
// Advance/Rewind commands
if (command == 'n') currentStep = currentStep - 1;
if (command == 'm') currentStep = currentStep + 1;
if (command == 'h') currentStep = currentStep - 10;
if (command == 'j') currentStep = currentStep + 10;
if (command == 'y') currentStep = currentStep - 100;
if (command == 'u') currentStep = currentStep + 100;
if (command == 'k') currentStep = currentStep - 1000;
if (command == 'i') currentStep = currentStep + 1000;
// Correct current step if requested more than possible
if (currentStep < 0) currentStep = 0;
2024-01-20 10:21:34 +00:00
if (currentStep >= sequenceLength) currentStep = sequenceLength - 1;
2024-01-11 18:44:54 +00:00
// Quicksave creation command
if (command == 's')
{
2024-01-20 10:21:34 +00:00
// Storing state file
2024-01-11 18:44:54 +00:00
std::string saveFileName = "quicksave.state";
2024-01-12 20:13:51 +00:00
std::string saveData;
saveData.resize(stateSize);
memcpy(saveData.data(), stateData, stateSize);
2024-03-06 15:21:04 +00:00
if (jaffarCommon::file::saveStringToFile(saveData, saveFileName.c_str()) == false) JAFFAR_THROW_LOGIC("[ERROR] Could not save state file: %s\n", saveFileName.c_str());
2024-03-22 11:50:46 +00:00
jaffarCommon::logger::log("[] Saved state to %s\n", saveFileName.c_str());
2024-01-11 18:44:54 +00:00
// Do no show frame info again after this action
showFrameInfo = false;
}
// Start playback from current point
if (command == 'p') isReproduce = true;
// Start playback from current point
if (command == 'q') continueRunning = false;
}
2024-03-22 11:50:46 +00:00
// If render is enabled then, close window now
if (disableRender == false) closeOutputWindow(window);
2024-01-11 18:44:54 +00:00
// Ending ncurses window
2024-03-06 15:21:04 +00:00
jaffarCommon::logger::finalizeTerminal();
}