quickerNES/source/tester.cpp

174 lines
6.6 KiB
C++
Raw Normal View History

2024-01-12 18:14:57 +00:00
#include <sstream>
#include <chrono>
#include "argparse/argparse.hpp"
#include "sha1/sha1.hpp"
#include "utils.hpp"
#include "nlohmann/json.hpp"
#ifdef _USE_QUICKNES
#include "quickNESInstance.hpp"
#endif
#ifdef _USE_QUICKERNES
#include "quickerNESInstance.hpp"
#endif
2024-01-12 18:14:57 +00:00
int main(int argc, char *argv[])
{
// Parsing command line arguments
argparse::ArgumentParser program("tester", "1.0");
2024-01-12 18:14:57 +00:00
program.add_argument("scriptFile")
.help("Path to the test script file to run.")
.required();
program.add_argument("--fullCycle")
.help("Performs the full load / advance frame / save cycle, as opposed to just advance frame")
.default_value(false)
.implicit_value(true);
program.add_argument("--hashOutputFile")
.help("Path to write the hash output to.")
.default_value(std::string(""));
2024-01-12 18:14:57 +00:00
// Try to parse arguments
try { program.parse_args(argc, argv); }
catch (const std::runtime_error &err) { EXIT_WITH_ERROR("%s\n%s", err.what(), program.help().str().c_str()); }
// Getting test script file path
std::string scriptFilePath = program.get<std::string>("scriptFile");
// Getting path where to save the hash output (if any)
std::string hashOutputFile = program.get<std::string>("--hashOutputFile");
// Getting reproduce flag
bool isFullCycle = program.get<bool>("--fullCycle");
2024-01-12 18:14:57 +00:00
// Loading script file
std::string scriptJsonRaw;
if (loadStringFromFile(scriptJsonRaw, scriptFilePath) == false) EXIT_WITH_ERROR("Could not find/read script file: %s\n", scriptFilePath.c_str());
2024-01-12 18:14:57 +00:00
// Parsing script
const auto scriptJson = nlohmann::json::parse(scriptJsonRaw);
// Getting rom file path
if (scriptJson.contains("Rom File") == false) EXIT_WITH_ERROR("Script file missing 'Rom File' entry\n");
if (scriptJson["Rom File"].is_string() == false) EXIT_WITH_ERROR("Script file 'Rom File' entry is not a string\n");
std::string romFilePath = scriptJson["Rom File"].get<std::string>();
// Getting initial state file path
if (scriptJson.contains("Initial State File") == false) EXIT_WITH_ERROR("Script file missing 'Initial State File' entry\n");
if (scriptJson["Initial State File"].is_string() == false) EXIT_WITH_ERROR("Script file 'Initial State File' entry is not a string\n");
std::string initialStateFilePath = scriptJson["Initial State File"].get<std::string>();
// Getting sequence file path
if (scriptJson.contains("Sequence File") == false) EXIT_WITH_ERROR("Script file missing 'Sequence File' entry\n");
if (scriptJson["Sequence File"].is_string() == false) EXIT_WITH_ERROR("Script file 'Sequence File' entry is not a string\n");
std::string sequenceFilePath = scriptJson["Sequence File"].get<std::string>();
// Getting expected ROM SHA1 hash
if (scriptJson.contains("Expected ROM SHA1") == false) EXIT_WITH_ERROR("Script file missing 'Expected ROM SHA1' entry\n");
if (scriptJson["Expected ROM SHA1"].is_string() == false) EXIT_WITH_ERROR("Script file 'Expected ROM SHA1' entry is not a string\n");
std::string expectedROMSHA1 = scriptJson["Expected ROM SHA1"].get<std::string>();
// Creating emulator instance
#ifdef _USE_QUICKNES
auto e = QuickNESInstance();
#endif
#ifdef _USE_QUICKERNES
auto e = QuickerNESInstance();
#endif
// Loading ROM File
e.loadROMFile(romFilePath);
// If an initial state is provided, load it now
if (initialStateFilePath != "") e.loadStateFile(initialStateFilePath);
2024-01-12 20:13:51 +00:00
// Disable rendering
2024-01-14 05:12:32 +00:00
e.disableRendering();
// Getting initial hash
2024-01-12 20:13:51 +00:00
auto initialHash = e.getStateHash();
// Getting full state size
2024-01-12 20:13:51 +00:00
const auto stateSize = e.getStateSize();
2024-01-12 18:14:57 +00:00
// Getting lite state size
const auto liteStateSize = e.getLiteStateSize();
2024-01-12 18:14:57 +00:00
// Getting actual ROM SHA1
auto romSHA1 = e.getRomSHA1();
// Checking with the expected SHA1 hash
if (romSHA1 != expectedROMSHA1) EXIT_WITH_ERROR("Wrong ROM SHA1. Found: '%s', Expected: '%s'\n", romSHA1.c_str(), expectedROMSHA1.c_str());
// Loading sequence file
std::string sequenceRaw;
2024-01-12 20:13:51 +00:00
if (loadStringFromFile(sequenceRaw, sequenceFilePath) == false) EXIT_WITH_ERROR("[ERROR] Could not find or read from input sequence file: %s\n", sequenceFilePath.c_str());
2024-01-12 18:14:57 +00:00
// Building sequence information
const auto sequence = split(sequenceRaw, ' ');
// Getting sequence lenght
const auto sequenceLength = sequence.size();
// Getting emulation core name
std::string emulationCoreName = e.getCoreName();
2024-01-12 18:14:57 +00:00
// Printing test information
2024-01-13 12:35:54 +00:00
printf("[] -----------------------------------------\n");
2024-01-12 20:13:51 +00:00
printf("[] Running Script: '%s'\n", scriptFilePath.c_str());
2024-01-17 17:21:44 +00:00
printf("[] Cycle Type: '%s'\n", isFullCycle ? "Advance / Load / Advance / Save" : "Advance Only");
printf("[] Emulation Core: '%s'\n", emulationCoreName.c_str());
2024-01-12 20:13:51 +00:00
printf("[] ROM File: '%s'\n", romFilePath.c_str());
printf("[] ROM SHA1: '%s'\n", romSHA1.c_str());
printf("[] Sequence File: '%s'\n", sequenceFilePath.c_str());
printf("[] Sequence Length: %lu\n", sequenceLength);
printf("[] Initial State Hash: 0x%lX%lX\n", initialHash.first, initialHash.second);
printf("[] Full State Size: %lu bytes\n", stateSize);
printf("[] Lite State Size: %lu bytes\n", liteStateSize);
2024-01-13 12:35:54 +00:00
printf("[] ********** Running Test **********\n");
2024-01-12 20:13:51 +00:00
2024-01-12 18:14:57 +00:00
fflush(stdout);
// Serializing initial state
2024-01-17 06:09:34 +00:00
uint8_t* currentState = (uint8_t*) malloc (stateSize);
e.serializeState(currentState);
2024-01-12 18:14:57 +00:00
// Actually running the sequence
auto t0 = std::chrono::high_resolution_clock::now();
for (const std::string& input : sequence)
{
2024-01-17 17:21:44 +00:00
if (isFullCycle == true) e.advanceState(input);
if (isFullCycle == true) e.deserializeState(currentState);
e.advanceState(input);
if (isFullCycle == true) e.serializeState(currentState);
}
2024-01-12 18:14:57 +00:00
auto tf = std::chrono::high_resolution_clock::now();
// Calculating running time
auto dt = std::chrono::duration_cast<std::chrono::nanoseconds>(tf - t0).count();
double elapsedTimeSeconds = (double)dt * 1.0e-9;
2024-01-12 20:13:51 +00:00
// Calculating final state hash
const auto finalStateHash = e.getStateHash();
// Creating hash string
char hashStringBuffer[256];
sprintf(hashStringBuffer, "0x%lX%lX", finalStateHash.first, finalStateHash.second);
2024-01-12 20:13:51 +00:00
// Printing time information
printf("[] Elapsed time: %3.3fs\n", (double)dt * 1.0e-9);
2024-01-17 17:21:44 +00:00
printf("[] Performance: %.3f inputs / s\n", (double)sequenceLength / elapsedTimeSeconds);
printf("[] Final State Hash: %s\n", hashStringBuffer);
// If saving hash, do it now
if (hashOutputFile != "") saveStringToFile(std::string(hashStringBuffer), hashOutputFile.c_str());
2024-01-12 20:13:51 +00:00
// If reached this point, everything ran ok
return 0;
2024-01-12 18:14:57 +00:00
}