diff --git a/extern/hqn/hqn.h b/extern/hqn/hqn.h index 32dce68..11ce4ec 100644 --- a/extern/hqn/hqn.h +++ b/extern/hqn/hqn.h @@ -3,7 +3,7 @@ #include #include -#include +#include #define BLIT_SIZE 65536 diff --git a/source/logger.hpp b/source/logger.hpp new file mode 100644 index 0000000..3c19646 --- /dev/null +++ b/source/logger.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +// If we use NCurses, we need to use the appropriate printing function +#ifdef NCURSES + #include + #define LOG printw +#else + #define LOG printf +#endif + +#define EXIT_WITH_ERROR(...) exitWithError(__FILE__, __LINE__, __VA_ARGS__) +inline void exitWithError [[noreturn]] (const char *fileName, const int lineNumber, const char *format, ...) +{ + char *outstr = 0; + va_list ap; + va_start(ap, format); + int ret = vasprintf(&outstr, format, ap); + if (ret < 0) exit(-1); + + std::string outString = outstr; + free(outstr); + + char info[1024]; + + snprintf(info, sizeof(info) - 1, " + From %s:%d\n", fileName, lineNumber); + outString += info; + + throw std::runtime_error(outString.c_str()); +} + diff --git a/source/emuInstanceBase.hpp b/source/nesInstanceBase.hpp similarity index 68% rename from source/emuInstanceBase.hpp rename to source/nesInstanceBase.hpp index 713af0c..b071089 100644 --- a/source/emuInstanceBase.hpp +++ b/source/nesInstanceBase.hpp @@ -1,8 +1,7 @@ #pragma once -#include "sha1/sha1.hpp" -#include -#include +#include "logger.hpp" +#include "controller.hpp" #define _LOW_MEM_SIZE 0x800 #define _HIGH_MEM_SIZE 0x2000 @@ -12,12 +11,12 @@ static const uint16_t image_width = 256; static const uint16_t image_height = 240; -class EmuInstanceBase +class NESInstanceBase { public: - EmuInstanceBase() = default; - virtual ~EmuInstanceBase() = default; + NESInstanceBase() = default; + virtual ~NESInstanceBase() = default; inline void advanceState(const std::string &move) { @@ -61,56 +60,19 @@ class EmuInstanceBase if (isTypeRecognized == false) EXIT_WITH_ERROR("Input type not recognized: '%s'\n", type.c_str()); } - inline std::string getRomSHA1() const { return _romSHA1String; } - - inline hash_t getStateHash() const - { - MetroHash128 hash; - - 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(&result)); - return result; - } - inline void enableRendering() { _doRendering = true; }; inline void disableRendering() { _doRendering = false; }; - inline void loadStateFile(const std::string &stateFilePath) + inline bool loadROM(const uint8_t* romData, const size_t romSize) { - std::string stateData; - bool status = loadStringFromFile(stateData, stateFilePath); - if (status == false) EXIT_WITH_ERROR("Could not find/read state file: %s\n", stateFilePath.c_str()); - deserializeState((uint8_t *)stateData.data()); - } - - inline void saveStateFile(const std::string &stateFilePath) const - { - std::string stateData; - stateData.resize(_stateSize); - serializeState((uint8_t *)stateData.data()); - saveStringToFile(stateData, stateFilePath.c_str()); - } - - inline void loadROMFile(const std::string &romFilePath) - { - // Loading ROM data - bool status = loadStringFromFile(_romData, romFilePath); - if (status == false) EXIT_WITH_ERROR("Could not find/read ROM file: %s\n", romFilePath.c_str()); - - // Calculating ROM hash value - _romSHA1String = SHA1::GetHash((uint8_t *)_romData.data(), _romData.size()); - // Actually loading rom file - status = loadROMFileImpl(_romData); - if (status == false) EXIT_WITH_ERROR("Could not process ROM file: %s\n", romFilePath.c_str()); + auto status = loadROMImpl(romData, romSize); // Detecting full state size _stateSize = getStateSize(); + + // Returning status + return status; } void enableStateBlock(const std::string& block) @@ -151,7 +113,7 @@ class EmuInstanceBase virtual void enableStateBlockImpl(const std::string& block) = 0; virtual void disableStateBlockImpl(const std::string& block) = 0; - virtual bool loadROMFileImpl(const std::string &romFilePath) = 0; + virtual bool loadROMImpl(const uint8_t* romData, const size_t romSize) = 0; virtual void advanceStateImpl(const Controller::port_t controller1, const Controller::port_t controller2) = 0; // Storage for the light state size @@ -161,11 +123,6 @@ class EmuInstanceBase bool _doRendering = true; private: - // Storage for the ROM data - std::string _romData; - - // SHA1 rom hash - std::string _romSHA1String; // Controller class for input parsing Controller _controller; diff --git a/source/playbackInstance.hpp b/source/playbackInstance.hpp index 70c1344..b7f1ec8 100644 --- a/source/playbackInstance.hpp +++ b/source/playbackInstance.hpp @@ -1,6 +1,6 @@ #pragma once -#include "emuInstance.hpp" +#include "nesInstance.hpp" #include #include #include @@ -30,14 +30,14 @@ class PlaybackInstance step.input = input; step.stateData = (uint8_t *)malloc(_emu->getStateSize()); _emu->serializeState(step.stateData); - step.hash = _emu->getStateHash(); + step.hash = calculateStateHash(_emu); // Adding the step into the sequence _stepSequence.push_back(step); } // Initializes the playback module instance - PlaybackInstance(EmuInstance *emu, const std::vector &sequence, const std::string &overlayPath = "") : _emu(emu) + PlaybackInstance(NESInstance *emu, const std::vector &sequence, const std::string &overlayPath = "") : _emu(emu) { // Enabling emulation rendering _emu->enableRendering(); @@ -234,7 +234,7 @@ class PlaybackInstance hqn::GUIController *_hqnGUI; // Pointer to the contained emulator instance - EmuInstance *const _emu; + NESInstance *const _emu; // Flag to store whether to use the button overlay bool _useOverlay = false; diff --git a/source/player.cpp b/source/player.cpp index ec1425d..970edeb 100644 --- a/source/player.cpp +++ b/source/player.cpp @@ -1,5 +1,5 @@ #include "argparse/argparse.hpp" -#include "emuInstance.hpp" +#include "nesInstance.hpp" #include "playbackInstance.hpp" #include "utils.hpp" #include @@ -91,17 +91,24 @@ int main(int argc, char *argv[]) refreshTerminal(); // Creating emulator instance - EmuInstance e; + NESInstance e; // Setting controller types e.setController1Type(controller1Type); e.setController2Type(controller2Type); // Loading ROM File - e.loadROMFile(romFilePath); + std::string romFileData; + if (loadStringFromFile(romFileData, romFilePath) == false) EXIT_WITH_ERROR("Could not rom file: %s\n", romFilePath.c_str()); + e.loadROM((uint8_t*)romFileData.data(), romFileData.size()); // If an initial state is provided, load it now - if (stateFilePath != "") e.loadStateFile(stateFilePath); + if (stateFilePath != "") + { + std::string stateFileData; + if (loadStringFromFile(stateFileData, stateFilePath) == false) EXIT_WITH_ERROR("Could not initial state file: %s\n", stateFilePath.c_str()); + e.deserializeState((uint8_t*)stateFileData.data()); + } // Creating playback instance auto p = PlaybackInstance(&e, sequence); diff --git a/source/quickNES/emuInstance.hpp b/source/quickNES/nesInstance.hpp similarity index 87% rename from source/quickNES/emuInstance.hpp rename to source/quickNES/nesInstance.hpp index 583fff1..d729dc5 100644 --- a/source/quickNES/emuInstance.hpp +++ b/source/quickNES/nesInstance.hpp @@ -1,8 +1,8 @@ #pragma once -#include -#include -#include +#include +#include +#include #define _DUMMY_SIZE 65536 @@ -12,10 +12,10 @@ extern void register_misc_mappers(); extern void register_extra_mappers(); extern void register_mapper_70(); -class EmuInstance : public EmuInstanceBase +class NESInstance : public NESInstanceBase { public: - EmuInstance() : EmuInstanceBase() + NESInstance() : NESInstanceBase() { // Creating new emulator _nes = new Nes_Emu; @@ -32,10 +32,10 @@ class EmuInstance : public EmuInstanceBase register_mapper_70(); } - virtual bool loadROMFileImpl(const std::string &romData) override + virtual bool loadROMImpl(const uint8_t* romData, const size_t romSize) override { // Loading rom data - Mem_File_Reader romReader(romData.data(), (int)romData.size()); + Mem_File_Reader romReader(romData, (int)romSize); Auto_File_Reader romFile(romReader); auto result = _nes->load_ines(romFile); return result == 0; diff --git a/source/quickerNES/emuInstance.hpp b/source/quickerNES/nesInstance.hpp similarity index 86% rename from source/quickerNES/emuInstance.hpp rename to source/quickerNES/nesInstance.hpp index 428301b..7da944a 100644 --- a/source/quickerNES/emuInstance.hpp +++ b/source/quickerNES/nesInstance.hpp @@ -1,14 +1,14 @@ #pragma once -#include -#include +#include +#include typedef quickerNES::Emu emulator_t; -class EmuInstance : public EmuInstanceBase +class NESInstance : public NESInstanceBase { public: - EmuInstance() : EmuInstanceBase() + NESInstance() : NESInstanceBase() { // Creating new emulator _nes = new emulator_t; @@ -20,12 +20,12 @@ class EmuInstance : public EmuInstanceBase _nes->set_pixels(video_buffer, image_width + 8); } - ~EmuInstance() = default; + ~NESInstance() = default; - virtual bool loadROMFileImpl(const std::string &romData) override + virtual bool loadROMImpl(const uint8_t* romData, const size_t romSize) override { // Loading rom data - _nes->load_ines((uint8_t *)romData.data()); + _nes->load_ines(romData); return true; } diff --git a/source/tester.cpp b/source/tester.cpp index 7ddb384..f24f971 100644 --- a/source/tester.cpp +++ b/source/tester.cpp @@ -2,7 +2,7 @@ #include "nlohmann/json.hpp" #include "sha1/sha1.hpp" #include "utils.hpp" -#include "emuInstance.hpp" +#include "nesInstance.hpp" #include #include #include @@ -94,18 +94,27 @@ int main(int argc, char *argv[]) std::string controller2Type = scriptJson["Controller 2 Type"].get(); // Creating emulator instance - EmuInstance e; + NESInstance e; // Setting controller types e.setController1Type(controller1Type); e.setController2Type(controller2Type); // Loading ROM File - e.loadROMFile(romFilePath); + std::string romFileData; + if (loadStringFromFile(romFileData, romFilePath) == false) EXIT_WITH_ERROR("Could not rom file: %s\n", romFilePath.c_str()); + e.loadROM((uint8_t*)romFileData.data(), romFileData.size()); + + // Calculating ROM SHA1 + auto romSHA1 = SHA1::GetHash((uint8_t *)romFileData.data(), romFileData.size()); // If an initial state is provided, load it now - if (initialStateFilePath != "") e.loadStateFile(initialStateFilePath); - + if (initialStateFilePath != "") + { + std::string stateFileData; + if (loadStringFromFile(stateFileData, initialStateFilePath) == false) EXIT_WITH_ERROR("Could not initial state file: %s\n", initialStateFilePath.c_str()); + e.deserializeState((uint8_t*)stateFileData.data()); + } // Disabling requested blocks from state serialization for (const auto& block : stateDisabledBlocks) e.disableStateBlock(block); @@ -115,9 +124,6 @@ int main(int argc, char *argv[]) // 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()); @@ -174,11 +180,11 @@ int main(int argc, char *argv[]) double elapsedTimeSeconds = (double)dt * 1.0e-9; // Calculating final state hash - const auto finalStateHash = e.getStateHash(); + auto result = calculateStateHash(&e); // Creating hash string char hashStringBuffer[256]; - sprintf(hashStringBuffer, "0x%lX%lX", finalStateHash.first, finalStateHash.second); + sprintf(hashStringBuffer, "0x%lX%lX", result.first, result.second); // Printing time information printf("[] Elapsed time: %3.3fs\n", (double)dt * 1.0e-9); diff --git a/source/utils.hpp b/source/utils.hpp index fea1793..64e3976 100644 --- a/source/utils.hpp +++ b/source/utils.hpp @@ -2,25 +2,20 @@ #include #include -#include #include #include #include #include #include #include - -// If we use NCurses, we need to use the appropriate printing function -#ifdef NCURSES - #include - #define LOG printw -#else - #define LOG printf -#endif +#include +#include "nesInstance.hpp" // If we use NCurses, define the following useful functions #ifdef NCURSES +#include + // Function to check for keypress taken from https://github.com/ajpaulson/learning-ncurses/blob/master/kbhit.c inline int kbhit() { @@ -84,16 +79,6 @@ inline 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(&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 @@ -126,26 +111,6 @@ inline std::string slurp(std::ifstream &in) return sstr.str(); } -#define EXIT_WITH_ERROR(...) exitWithError(__FILE__, __LINE__, __VA_ARGS__) -inline void exitWithError [[noreturn]] (const char *fileName, const int lineNumber, const char *format, ...) -{ - char *outstr = 0; - va_list ap; - va_start(ap, format); - int ret = vasprintf(&outstr, format, ap); - if (ret < 0) exit(-1); - - std::string outString = outstr; - free(outstr); - - char info[1024]; - - snprintf(info, sizeof(info) - 1, " + From %s:%d\n", fileName, lineNumber); - outString += info; - - throw std::runtime_error(outString.c_str()); -} - // Loads a string from a given file inline bool loadStringFromFile(std::string &dst, const std::string path) { @@ -191,70 +156,18 @@ inline std::vector splitVector(const T size, const T n) return subSizes; } -inline std::string simplifyMove(const std::string &move) -{ - std::string simpleMove; +typedef _uint128_t hash_t; - bool isEmptyMove = true; - for (size_t i = 0; i < move.size(); i++) - if (move[i] != '.' && move[i] != '|') - { - simpleMove += move[i]; - isEmptyMove = false; - } - if (isEmptyMove) return "."; - return simpleMove; +inline hash_t calculateMetroHash(uint8_t *data, size_t size) +{ + MetroHash128 hash; + hash.Update(data, size); + hash_t result; + hash.Finalize(reinterpret_cast(&result)); + return result; } -inline bool getBitFlag(const uint8_t value, const uint8_t idx) +inline hash_t calculateStateHash(const NESInstance* nes) { - if (((idx == 7) && (value & 0b10000000)) || - ((idx == 6) && (value & 0b01000000)) || - ((idx == 5) && (value & 0b00100000)) || - ((idx == 4) && (value & 0b00010000)) || - ((idx == 3) && (value & 0b00001000)) || - ((idx == 2) && (value & 0b00000100)) || - ((idx == 1) && (value & 0b00000010)) || - ((idx == 0) && (value & 0b00000001))) return true; - return false; -} - -inline size_t countButtonsPressedString(const std::string &input) -{ - size_t count = 0; - for (size_t i = 0; i < input.size(); i++) - if (input[i] != '.') count++; - return count; -}; - -template -inline uint16_t countButtonsPressedNumber(const T &input) -{ - uint16_t count = 0; - if (input & 0b0000000000000001) count++; - if (input & 0b0000000000000010) count++; - if (input & 0b0000000000000100) count++; - if (input & 0b0000000000001000) count++; - if (input & 0b0000000000010000) count++; - if (input & 0b0000000000100000) count++; - if (input & 0b0000000001000000) count++; - if (input & 0b0000000010000000) count++; - if (input & 0b0000000100000000) count++; - if (input & 0b0000001000000000) count++; - if (input & 0b0000010000000000) count++; - if (input & 0b0000100000000000) count++; - if (input & 0b0001000000000000) count++; - if (input & 0b0010000000000000) count++; - if (input & 0b0100000000000000) count++; - if (input & 0b1000000000000000) count++; - return count; -}; - -static auto moveCountComparerString = [](const std::string &a, const std::string &b) -{ - return countButtonsPressedString(a) < countButtonsPressedString(b); -}; -static auto moveCountComparerNumber = [](const uint8_t a, const uint8_t b) -{ - return countButtonsPressedNumber(a) < countButtonsPressedNumber(b); -}; + return calculateMetroHash(nes->getLowMem(), _LOW_MEM_SIZE); +} \ No newline at end of file