Adding verification for tests
This commit is contained in:
parent
147d0d4f52
commit
1952f8a622
|
@ -35,7 +35,7 @@ quickerNESCoreSrc = [
|
|||
'source/core/Multi_Buffer.cpp',
|
||||
'source/core/Nes_Emu.cpp',
|
||||
'source/core/nes_ntsc.cpp',
|
||||
'source/core/Nes_Vrc7.cpp'
|
||||
'source/core/Nes_Vrc7.cpp',
|
||||
]
|
||||
|
||||
# quickerNES Core Configuration
|
||||
|
@ -43,7 +43,7 @@ quickerNESCoreSrc = [
|
|||
quickerNESCoreDependency = declare_dependency(
|
||||
compile_args : [ '-Wfatal-errors','-Wall' ],
|
||||
include_directories : include_directories(['source', 'source/core', 'extern']),
|
||||
sources : quickerNESCoreSrc,
|
||||
sources : [ quickerNESCoreSrc, 'extern/metrohash128/metrohash128.cpp' ]
|
||||
)
|
||||
|
||||
# Building playback tool
|
||||
|
@ -53,7 +53,7 @@ quickerNESPlayerSrc = [
|
|||
'extern/hqn/hqn_gui_controller.cpp',
|
||||
'extern/hqn/hqn_surface.cpp',
|
||||
'extern/hqn/hqn_util.cpp',
|
||||
'extern/hqn/options.cpp'
|
||||
'extern/hqn/options.cpp',
|
||||
]
|
||||
|
||||
executable('player',
|
||||
|
|
|
@ -12,12 +12,6 @@
|
|||
#include "Nes_Core.h"
|
||||
class Nes_State;
|
||||
|
||||
// Register optional mappers included with Nes_Emu
|
||||
void register_optional_mappers();
|
||||
void register_extra_mappers();
|
||||
|
||||
extern const char unsupported_mapper []; // returned when cartridge uses unsupported mapper
|
||||
|
||||
class Nes_Emu {
|
||||
public:
|
||||
Nes_Emu();
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#define _LOW_MEM_SIZE 0x800
|
||||
#define _HIGH_MEM_SIZE 0x2000
|
||||
#define _NAMETABLES_MEM_SIZE 0x1000
|
||||
|
||||
class EmuInstance
|
||||
{
|
||||
|
@ -49,6 +50,9 @@ class EmuInstance
|
|||
uint8_t* getLowMem() { return _nes->low_mem(); };
|
||||
uint8_t* getNametableMem() { return _nes->nametable_mem(); };
|
||||
uint8_t* getHighMem() { return _nes->high_mem();};
|
||||
const uint8_t* getChrMem() { return _nes->chr_mem();};
|
||||
size_t getChrMemSize() { return _nes->chr_size();};
|
||||
|
||||
const std::string getRomSHA1() const { return _romSHA1String; };
|
||||
|
||||
void loadStateFile(const std::string& stateFilePath)
|
||||
|
@ -70,6 +74,23 @@ class EmuInstance
|
|||
|
||||
inline size_t getStateSize() const { return _stateSize; }
|
||||
|
||||
inline hash_t getStateHash()
|
||||
{
|
||||
MetroHash128 hash;
|
||||
|
||||
uint8_t stateData[_stateSize];
|
||||
serializeState(stateData);
|
||||
|
||||
hash.Update(getLowMem(), _LOW_MEM_SIZE);
|
||||
hash.Update(getHighMem(), _HIGH_MEM_SIZE);
|
||||
hash.Update(getNametableMem(), _NAMETABLES_MEM_SIZE);
|
||||
hash.Update(getChrMem(), getChrMemSize());
|
||||
|
||||
hash_t result;
|
||||
hash.Finalize(reinterpret_cast<uint8_t *>(&result));
|
||||
return result;
|
||||
}
|
||||
|
||||
void saveStateFile(const std::string& stateFilePath) const
|
||||
{
|
||||
std::string stateData;
|
||||
|
|
|
@ -16,6 +16,7 @@ struct stepData_t
|
|||
std::string input;
|
||||
uint8_t* stateData;
|
||||
int32_t curBlit[BLIT_SIZE];
|
||||
hash_t hash;
|
||||
};
|
||||
|
||||
class PlaybackInstance
|
||||
|
@ -28,6 +29,7 @@ class PlaybackInstance
|
|||
step.input = input;
|
||||
step.stateData = (uint8_t*) malloc(_emu->getStateSize());
|
||||
_emu->serializeState(step.stateData);
|
||||
step.hash = _emu->getStateHash();
|
||||
saveBlit(_emu->getInternalEmulator(), step.curBlit, hqn::HQNState::NES_VIDEO_PALETTE, 0, 0, 0, 0);
|
||||
|
||||
// Adding the step into the sequence
|
||||
|
@ -179,6 +181,30 @@ class PlaybackInstance
|
|||
return step.stateData;
|
||||
}
|
||||
|
||||
const hash_t getStateHash(const size_t stepId) const
|
||||
{
|
||||
// Checking the required step id does not exceed contents of the sequence
|
||||
if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence");
|
||||
|
||||
// Getting step information
|
||||
const auto& step = _stepSequence[stepId];
|
||||
|
||||
// Returning step input
|
||||
return step.hash;
|
||||
}
|
||||
|
||||
const std::string getStateInput(const size_t stepId) const
|
||||
{
|
||||
// Checking the required step id does not exceed contents of the sequence
|
||||
if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence");
|
||||
|
||||
// Getting step information
|
||||
const auto& step = _stepSequence[stepId];
|
||||
|
||||
// Returning step input
|
||||
return step.input;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -72,14 +72,19 @@ int main(int argc, char *argv[])
|
|||
// Creating emulator instance
|
||||
auto e = EmuInstance(romFilePath, stateFilePath);
|
||||
|
||||
auto stateHash = e.getStateHash();
|
||||
|
||||
// Creating playback instance
|
||||
auto p = PlaybackInstance(&e, sequence);
|
||||
|
||||
// Getting state size
|
||||
auto stateSize = e.getStateSize();
|
||||
|
||||
// Flag to continue running playback
|
||||
bool continueRunning = true;
|
||||
|
||||
// Variable for current step in view
|
||||
ssize_t sequenceLength = sequence.size();
|
||||
ssize_t sequenceLength = p.getSequenceLength();
|
||||
ssize_t currentStep = 0;
|
||||
|
||||
// Flag to display frame information
|
||||
|
@ -92,10 +97,13 @@ int main(int argc, char *argv[])
|
|||
if (disableRender == false) p.renderFrame(currentStep);
|
||||
|
||||
// Getting input
|
||||
const auto& input = sequence[currentStep];
|
||||
const auto& input = p.getStateInput(currentStep);
|
||||
|
||||
// Getting state hash
|
||||
const auto hash = p.getStateHash(currentStep);
|
||||
|
||||
// Getting state data
|
||||
//const auto stateData = p.getStateData(currentStep);
|
||||
const auto stateData = p.getStateData(currentStep);
|
||||
|
||||
// Printing data and commands
|
||||
if (showFrameInfo)
|
||||
|
@ -105,6 +113,7 @@ int main(int argc, char *argv[])
|
|||
printw("[] ----------------------------------------------------------------\n");
|
||||
printw("[] Current Step #: %lu / %lu\n", currentStep + 1, sequenceLength);
|
||||
printw("[] Input: %s\n", input.c_str());
|
||||
printw("[] State Hash: 0x%lX%lX\n", hash.first, hash.second);
|
||||
|
||||
// Only print commands if not in reproduce mode
|
||||
if (isReproduce == false) printw("[] Commands: n: -1 m: +1 | h: -10 | j: +10 | y: -100 | u: +100 | k: -1000 | i: +1000 | s: quicksave | p: play | q: quit\n");
|
||||
|
@ -137,7 +146,11 @@ int main(int argc, char *argv[])
|
|||
{
|
||||
// Storing state file
|
||||
std::string saveFileName = "quicksave.state";
|
||||
e.saveStateFile(saveFileName);
|
||||
|
||||
std::string saveData;
|
||||
saveData.resize(stateSize);
|
||||
memcpy(saveData.data(), stateData, stateSize);
|
||||
if (saveStringToFile(saveData, saveFileName.c_str()) == false) EXIT_WITH_ERROR("[ERROR] Could not save state file: %s\n", saveFileName.c_str());
|
||||
printw("[] Saved state to %s\n", saveFileName.c_str());
|
||||
|
||||
// Do no show frame info again after this action
|
||||
|
|
|
@ -39,6 +39,11 @@ int main(int argc, char *argv[])
|
|||
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 verification state file path
|
||||
if (scriptJson.contains("Verification State File") == false) EXIT_WITH_ERROR("Script file missing 'Verification State File' entry\n");
|
||||
if (scriptJson["Verification State File"].is_string() == false) EXIT_WITH_ERROR("Script file 'Verification State File' entry is not a string\n");
|
||||
std::string verificationStateFilePath = scriptJson["Verification 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");
|
||||
|
@ -52,15 +57,33 @@ int main(int argc, char *argv[])
|
|||
// Creating emulator instance
|
||||
auto e = EmuInstance(romFilePath, initialStateFilePath);
|
||||
|
||||
auto initialHash = e.getStateHash();
|
||||
printf("[] Initial State Hash: 0x%lX%lX\n", initialHash.first, initialHash.second);
|
||||
|
||||
// Getting state size
|
||||
const auto stateSize = e.getStateSize();
|
||||
|
||||
// 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 state file for verication, if provided
|
||||
std::string verificationState;
|
||||
if (verificationStateFilePath != "")
|
||||
{
|
||||
// Loading file
|
||||
if (loadStringFromFile(verificationState, verificationStateFilePath) == false)
|
||||
EXIT_WITH_ERROR("[ERROR] Could not find or read from verification state file: %s\n", verificationStateFilePath.c_str());
|
||||
|
||||
// Checking size
|
||||
if (verificationState.length() != stateSize) EXIT_WITH_ERROR("[ERROR] The provided verification state has different size from expected: %lu vs %lu\n", verificationState.length(), stateSize);
|
||||
}
|
||||
|
||||
// Loading sequence file
|
||||
std::string sequenceRaw;
|
||||
if (loadStringFromFile(sequenceRaw, sequenceFilePath.c_str()) == false) EXIT_WITH_ERROR("[ERROR] Could not find or read from input sequence file: %s\n", sequenceFilePath.c_str());
|
||||
if (loadStringFromFile(sequenceRaw, sequenceFilePath) == false) EXIT_WITH_ERROR("[ERROR] Could not find or read from input sequence file: %s\n", sequenceFilePath.c_str());
|
||||
|
||||
// Building sequence information
|
||||
const auto sequence = split(sequenceRaw, ' ');
|
||||
|
@ -72,8 +95,11 @@ int main(int argc, char *argv[])
|
|||
printf("[] Running Script: '%s'\n", scriptFilePath.c_str());
|
||||
printf("[] ROM File: '%s'\n", romFilePath.c_str());
|
||||
printf("[] ROM SHA1: '%s'\n", romSHA1.c_str());
|
||||
printf("[] Verification State File: '%s'\n", verificationStateFilePath.c_str());
|
||||
printf("[] Sequence File: '%s'\n", sequenceFilePath.c_str());
|
||||
printf("[] Sequence Length: %lu\n", sequenceLength);
|
||||
printf("[] State Size: %lu bytes\n", stateSize);
|
||||
|
||||
fflush(stdout);
|
||||
|
||||
// Actually running the sequence
|
||||
|
@ -85,10 +111,33 @@ int main(int argc, char *argv[])
|
|||
auto dt = std::chrono::duration_cast<std::chrono::nanoseconds>(tf - t0).count();
|
||||
double elapsedTimeSeconds = (double)dt * 1.0e-9;
|
||||
|
||||
// Printing time information
|
||||
printf("[] Elapsed time: %3.3fs\n", (double)dt * 1.0e-9);
|
||||
printf("[] Performance: %.3f steps / s\n", (double)sequenceLength / elapsedTimeSeconds);
|
||||
// Calculating final state hash
|
||||
const auto finalStateHash = e.getStateHash();
|
||||
|
||||
return 0;
|
||||
// If provided, verify result against the passed verification movie
|
||||
bool verificationPassed = true;
|
||||
hash_t verificationStateHash;
|
||||
if (verificationStateFilePath != "")
|
||||
{
|
||||
// Loading verification state into the emulator
|
||||
e.deserializeState((uint8_t*)verificationState.data());
|
||||
|
||||
// Calculating hash for the verification state
|
||||
verificationStateHash = e.getStateHash();
|
||||
|
||||
// Comparing hashes
|
||||
if (verificationStateHash != finalStateHash) verificationPassed = false;
|
||||
}
|
||||
|
||||
// Printing time information
|
||||
printf("[] -----------------------------------------\n");
|
||||
printf("[] Elapsed time: %3.3fs\n", (double)dt * 1.0e-9);
|
||||
printf("[] Performance: %.3f steps / s\n", (double)sequenceLength / elapsedTimeSeconds);
|
||||
printf("[] Final State Hash: 0x%lX%lX\n", finalStateHash.first, finalStateHash.second);
|
||||
if (verificationStateFilePath != "")
|
||||
printf("[] Verification Hash: 0x%lX%lX (%s)\n", verificationStateHash.first, verificationStateHash.second, verificationPassed ? "Passed" : "Failed");
|
||||
|
||||
// Fail if verification didn't pass
|
||||
return verificationPassed ? 0 : -1;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <algorithm>
|
||||
#include <metrohash128/metrohash128.h>
|
||||
|
||||
// If we use NCurses, we need to use the appropriate printing function
|
||||
#ifdef NCURSES
|
||||
|
@ -85,6 +86,17 @@ void refreshTerminal()
|
|||
|
||||
#endif // NCURSES
|
||||
|
||||
|
||||
typedef _uint128_t hash_t;
|
||||
inline hash_t calculateMetroHash(uint8_t* data, size_t size)
|
||||
{
|
||||
MetroHash128 hash;
|
||||
hash.Update(data, size);
|
||||
hash_t result;
|
||||
hash.Finalize(reinterpret_cast<uint8_t *>(&result));
|
||||
return result;
|
||||
}
|
||||
|
||||
// Function to split a string into a sub-strings delimited by a character
|
||||
// Taken from stack overflow answer to https://stackoverflow.com/questions/236129/how-do-i-iterate-over-the-words-of-a-string
|
||||
// By Evan Teran
|
||||
|
@ -137,9 +149,9 @@ inline void exitWithError [[noreturn]] (const char *fileName, const int lineNum
|
|||
}
|
||||
|
||||
// Loads a string from a given file
|
||||
inline bool loadStringFromFile(std::string &dst, const char *fileName)
|
||||
inline bool loadStringFromFile(std::string &dst, std::string path)
|
||||
{
|
||||
std::ifstream fi(fileName);
|
||||
std::ifstream fi(path);
|
||||
|
||||
// If file not found or open, return false
|
||||
if (fi.good() == false) return false;
|
||||
|
|
Binary file not shown.
|
@ -2,6 +2,6 @@
|
|||
"Rom File": "Castlevania (U) (PRG0) [!].nes",
|
||||
"Expected ROM SHA1": "A31B8BD5B370A9103343C866F3C2B2998E889341",
|
||||
"Initial State File": "",
|
||||
"Final State File": "anyPercent.final.state",
|
||||
"Verification State File": "anyPercent.final.state",
|
||||
"Sequence File": "anyPercent.sol"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue