From 6ca0537d45e0d1fbcbe02d6d0ecdb8329efbc7e1 Mon Sep 17 00:00:00 2001 From: SergioMartin86 Date: Sun, 28 Jul 2024 16:43:09 +0200 Subject: [PATCH] Adapting for pre-decoded inputs --- extern/jaffarCommon | 2 +- source/controller.hpp | 207 --------------------------- source/inputParser.hpp | 224 ++++++++++++++++++++++++++++++ source/nesInstanceBase.hpp | 80 ++--------- source/player.cpp | 7 +- source/quickNES/nesInstance.hpp | 14 +- source/quickerNES/nesInstance.hpp | 14 +- source/tester.cpp | 18 +-- 8 files changed, 267 insertions(+), 299 deletions(-) delete mode 100644 source/controller.hpp create mode 100644 source/inputParser.hpp diff --git a/extern/jaffarCommon b/extern/jaffarCommon index 6db55ef..e7fd15b 160000 --- a/extern/jaffarCommon +++ b/extern/jaffarCommon @@ -1 +1 @@ -Subproject commit 6db55ef68011a6777f980f878a54501f42b6c894 +Subproject commit e7fd15b6e3ffed9bd718c0bfc0b0a6247e5dfe76 diff --git a/source/controller.hpp b/source/controller.hpp deleted file mode 100644 index 887a800..0000000 --- a/source/controller.hpp +++ /dev/null @@ -1,207 +0,0 @@ -#pragma once - -// Base controller class -// by eien86 - -#include -#include -#include - -namespace quickNES -{ - -class Controller -{ -public: - - enum controller_t { none, joypad, fourscore1, fourscore2 }; - - typedef uint32_t port_t; - - struct input_t - { - bool power = false; - bool reset = false; - port_t port1 = 0; - port_t port2 = 0; - }; - - inline bool parseInputString(const std::string& input, input_t* decoded) const - { - // Parse valid flag - bool isValid = true; - - // Converting input into a stream for parsing - std::istringstream ss(input); - - // Start separator - if (ss.get() != '|') isValid = false; - - // Parsing console inputs - isValid &= parseConsoleInputs(decoded->reset, decoded->power, ss); - - // Parsing controller 1 inputs - isValid &= parseControllerInputs(_controller1Type, decoded->port1, ss); - - // Parsing controller 1 inputs - isValid &= parseControllerInputs(_controller2Type, decoded->port2, ss); - - // End separator - if (ss.get() != '|') isValid = false; - - // If its not the end of the stream, then extra values remain and its invalid - ss.get(); - if (ss.eof() == false) isValid = false; - - // Returning valid flag - return isValid; - }; - - inline void setController1Type(const controller_t type) { _controller1Type = type; } - inline void setController2Type(const controller_t type) { _controller2Type = type; } - - private: - - static bool parseJoyPadInput(uint8_t& code, std::istringstream& ss) - { - // Currently read character - char c; - - // Cleaning code - code = 0; - - // Up - c = ss.get(); - if (c != '.' && c != 'U') return false; - if (c == 'U') code |= 0b00010000; - - // Down - c = ss.get(); - if (c != '.' && c != 'D') return false; - if (c == 'D') code |= 0b00100000; - - // Left - c = ss.get(); - if (c != '.' && c != 'L') return false; - if (c == 'L') code |= 0b01000000; - - // Right - c = ss.get(); - if (c != '.' && c != 'R') return false; - if (c == 'R') code |= 0b10000000; - - // Start - c = ss.get(); - if (c != '.' && c != 'S') return false; - if (c == 'S') code |= 0b00001000; - - // Select - c = ss.get(); - if (c != '.' && c != 's') return false; - if (c == 's') code |= 0b00000100; - - // B - c = ss.get(); - if (c != '.' && c != 'B') return false; - if (c == 'B') code |= 0b00000010; - - // A - c = ss.get(); - if (c != '.' && c != 'A') return false; - if (c == 'A') code |= 0b00000001; - - return true; - } - - static bool parseControllerInputs(const controller_t type, port_t& port, std::istringstream& ss) - { - // Parse valid flag - bool isValid = true; - - // If no controller assigned then, its port is all zeroes. - if (type == controller_t::none) { port = 0; return true; } - - // Controller separator - if (ss.get() != '|') isValid = false; - - // If normal joypad, parse its code now - if (type == controller_t::joypad) - { - // Storage for joypad's code - uint8_t code = 0; - - // Parsing joypad code - isValid &= parseJoyPadInput(code, ss); - - // Pushing input code into the port - port = code; - - // Adding joypad signature - // Per https://www.nesdev.org/wiki/Standard_controller, the joypad reports 1s after the first 8 bits - port |= ~0xFF; - } - - // If its fourscore, its like two joypads separated by a | - if (type == controller_t::fourscore1 || type == controller_t::fourscore2) - { - // Storage for joypad's code - uint8_t code1 = 0; - uint8_t code2 = 0; - - // Parsing joypad code1 - isValid &= parseJoyPadInput(code1, ss); - - // Separator - if (ss.get() != '|') return false; - - // Parsing joypad code1 - isValid &= parseJoyPadInput(code2, ss); - - // Creating code - port = code1; - port |= (uint32_t)0 | code2 << 8; - if (type == controller_t::fourscore1) port |= (uint32_t)0 | 1 << 19; - if (type == controller_t::fourscore2) port |= (uint32_t)0 | 1 << 18; - port |= (uint32_t)0 | 1 << 24; - port |= (uint32_t)0 | 1 << 25; - port |= (uint32_t)0 | 1 << 26; - port |= (uint32_t)0 | 1 << 27; - port |= (uint32_t)0 | 1 << 28; - port |= (uint32_t)0 | 1 << 29; - port |= (uint32_t)0 | 1 << 30; - port |= (uint32_t)0 | 1 << 31; - } - // Return valid flag - return isValid; - } - - static bool parseConsoleInputs(bool& reset, bool& power, std::istringstream& ss) - { - // Parse valid flag - bool isValid = true; - - // Currently read character - char c; - - // Power trigger - c = ss.get(); - if (c != '.' && c != 'P') isValid = false; - if (c == 'P') power = true; - if (c == '.') power = false; - - // Reset trigger - c = ss.get(); - if (c != '.' && c != 'r') isValid = false; - if (c == 'r') reset = true; - if (c == '.') reset = false; - - // Return valid flag - return isValid; - } - - controller_t _controller1Type; - controller_t _controller2Type; - -}; // class Controller - -} // namespace quickNES \ No newline at end of file diff --git a/source/inputParser.hpp b/source/inputParser.hpp new file mode 100644 index 0000000..4f90543 --- /dev/null +++ b/source/inputParser.hpp @@ -0,0 +1,224 @@ +#pragma once + +// Base controller class +// by eien86 + +#include +#include +#include +#include +#include + +namespace jaffar +{ + +typedef uint32_t port_t; + +struct input_t +{ + bool power = false; + bool reset = false; + port_t port1 = 0; + port_t port2 = 0; +}; + + +class InputParser +{ +public: + + enum controller_t { none, joypad, fourscore1, fourscore2 }; + + InputParser(const nlohmann::json &config) + { + // Parsing controller 1 type + { + bool isTypeRecognized = false; + const auto controller1Type = jaffarCommon::json::getString(config, "Controller 1 Type"); + if (controller1Type == "None") { _controller1Type = controller_t::none; isTypeRecognized = true; } + if (controller1Type == "Joypad") { _controller1Type = controller_t::joypad; isTypeRecognized = true; } + if (controller1Type == "FourScore1") { _controller1Type = controller_t::fourscore1; isTypeRecognized = true; } + if (controller1Type == "FourScore2") { _controller1Type = controller_t::fourscore2; isTypeRecognized = true; } + if (isTypeRecognized == false) JAFFAR_THROW_LOGIC("Controller 1 type not recognized: '%s'\n", controller1Type.c_str()); + } + + // Parsing controller 2 type + { + bool isTypeRecognized = false; + const auto controller2Type = jaffarCommon::json::getString(config, "Controller 2 Type"); + if (controller2Type == "None") { _controller2Type = controller_t::none; isTypeRecognized = true; } + if (controller2Type == "Joypad") { _controller2Type = controller_t::joypad; isTypeRecognized = true; } + if (controller2Type == "FourScore1") { _controller2Type = controller_t::fourscore1; isTypeRecognized = true; } + if (controller2Type == "FourScore2") { _controller2Type = controller_t::fourscore2; isTypeRecognized = true; } + if (isTypeRecognized == false) JAFFAR_THROW_LOGIC("Controller 2 type not recognized: '%s'\n", controller2Type.c_str()); + } + + } + + inline input_t parseInputString(const std::string& inputString) const + { + // Storage for the input + input_t input; + + // Converting input into a stream for parsing + std::istringstream ss(inputString); + + // Start separator + if (ss.get() != '|') reportBadInputString(inputString); + + // Parsing console inputs + parseConsoleInputs(input.reset, input.power, ss, inputString); + + // Parsing controller 1 inputs + parseControllerInputs(_controller1Type, input.port1, ss, inputString); + + // Parsing controller 1 inputs + parseControllerInputs(_controller2Type, input.port2, ss, inputString); + + // End separator + if (ss.get() != '|') reportBadInputString(inputString); + + // If its not the end of the stream, then extra values remain and its invalid + ss.get(); + if (ss.eof() == false) reportBadInputString(inputString); + + return input; + } + + private: + + static inline void reportBadInputString(const std::string& inputString) + { + JAFFAR_THROW_LOGIC("Could not decode input string: '%s'\n", inputString.c_str()); + } + + static void parseJoyPadInput(uint8_t& code, std::istringstream& ss, const std::string& inputString) + { + // Currently read character + char c; + + // Cleaning code + code = 0; + + // Up + c = ss.get(); + if (c != '.' && c != 'U') reportBadInputString(inputString); + if (c == 'U') code |= 0b00010000; + + // Down + c = ss.get(); + if (c != '.' && c != 'D') reportBadInputString(inputString); + if (c == 'D') code |= 0b00100000; + + // Left + c = ss.get(); + if (c != '.' && c != 'L') reportBadInputString(inputString); + if (c == 'L') code |= 0b01000000; + + // Right + c = ss.get(); + if (c != '.' && c != 'R') reportBadInputString(inputString); + if (c == 'R') code |= 0b10000000; + + // Start + c = ss.get(); + if (c != '.' && c != 'S') reportBadInputString(inputString); + if (c == 'S') code |= 0b00001000; + + // Select + c = ss.get(); + if (c != '.' && c != 's') reportBadInputString(inputString); + if (c == 's') code |= 0b00000100; + + // B + c = ss.get(); + if (c != '.' && c != 'B') reportBadInputString(inputString); + if (c == 'B') code |= 0b00000010; + + // A + c = ss.get(); + if (c != '.' && c != 'A') reportBadInputString(inputString); + if (c == 'A') code |= 0b00000001; + } + + static void parseControllerInputs(const controller_t type, port_t& port, std::istringstream& ss, const std::string& inputString) + { + // If no controller assigned then, its port is all zeroes. + if (type == controller_t::none) { port = 0; return; } + + // Controller separator + if (ss.get() != '|') reportBadInputString(inputString); + + // If normal joypad, parse its code now + if (type == controller_t::joypad) + { + // Storage for joypad's code + uint8_t code = 0; + + // Parsing joypad code + parseJoyPadInput(code, ss, inputString); + + // Pushing input code into the port + port = code; + + // Adding joypad signature + // Per https://www.nesdev.org/wiki/Standard_controller, the joypad reports 1s after the first 8 bits + port |= ~0xFF; + } + + // If its fourscore, its like two joypads separated by a | + if (type == controller_t::fourscore1 || type == controller_t::fourscore2) + { + // Storage for joypad's code + uint8_t code1 = 0; + uint8_t code2 = 0; + + // Parsing joypad code1 + parseJoyPadInput(code1, ss, inputString); + + // Separator + if (ss.get() != '|') reportBadInputString(inputString); + + // Parsing joypad code1 + parseJoyPadInput(code2, ss, inputString); + + // Creating code + port = code1; + port |= (uint32_t)0 | code2 << 8; + if (type == controller_t::fourscore1) port |= (uint32_t)0 | 1 << 19; + if (type == controller_t::fourscore2) port |= (uint32_t)0 | 1 << 18; + port |= (uint32_t)0 | 1 << 24; + port |= (uint32_t)0 | 1 << 25; + port |= (uint32_t)0 | 1 << 26; + port |= (uint32_t)0 | 1 << 27; + port |= (uint32_t)0 | 1 << 28; + port |= (uint32_t)0 | 1 << 29; + port |= (uint32_t)0 | 1 << 30; + port |= (uint32_t)0 | 1 << 31; + } + } + + static void parseConsoleInputs(bool& reset, bool& power, std::istringstream& ss, const std::string& inputString) + { + // Currently read character + char c; + + // Power trigger + c = ss.get(); + if (c != '.' && c != 'P') reportBadInputString(inputString); + if (c == 'P') power = true; + if (c == '.') power = false; + + // Reset trigger + c = ss.get(); + if (c != '.' && c != 'r') reportBadInputString(inputString); + if (c == 'r') reset = true; + if (c == '.') reset = false; + } + + controller_t _controller1Type; + controller_t _controller2Type; + +}; // class InputParser + +} // namespace jaffar \ No newline at end of file diff --git a/source/nesInstanceBase.hpp b/source/nesInstanceBase.hpp index 3351242..d4e49d9 100644 --- a/source/nesInstanceBase.hpp +++ b/source/nesInstanceBase.hpp @@ -3,7 +3,7 @@ #include "jaffarCommon/serializers/contiguous.hpp" #include "jaffarCommon/serializers/differential.hpp" #include "jaffarCommon/logger.hpp" -#include "controller.hpp" +#include "inputParser.hpp" // Size of image generated in graphics buffer static const uint16_t image_width = 256; @@ -13,71 +13,14 @@ class NESInstanceBase { public: - NESInstanceBase() = default; + NESInstanceBase(const nlohmann::json& config) + { + _inputParser = std::make_unique(config); + } + virtual ~NESInstanceBase() = default; - inline void advanceState(const std::string &input) - { - // Storage for the decoded input - quickNES::Controller::input_t decodedInput; - - // Getting decoded input from the input string - decodeInput(input, &decodedInput); - - // Calling advance state with the decoded input - advanceState(&decodedInput); - } - - inline void advanceState(const void* decodedInputBuffer) - { - // Casting decoded input to the right type - const auto decodedInput = (quickNES::Controller::input_t*) decodedInputBuffer; - - // Parsing power - if (decodedInput->power == true) JAFFAR_THROW_LOGIC("Power button pressed, but not supported."); - - // Parsing reset - if (decodedInput->reset == true) doSoftReset(); - - // Running specified inputs - advanceStateImpl(decodedInput->port1, decodedInput->port2); - } - - inline size_t getDecodedInputSize() const - { - return sizeof(quickNES::Controller::input_t); - } - - inline void decodeInput(const std::string &input, void* decodedInputBuffer) const - { - const auto decodedInput = (quickNES::Controller::input_t*) decodedInputBuffer; - bool isInputValid = _controller.parseInputString(input, decodedInput); - if (isInputValid == false) JAFFAR_THROW_LOGIC("Move provided cannot be parsed: '%s'\n", input.c_str()); - } - - inline void setController1Type(const std::string& type) - { - bool isTypeRecognized = false; - - if (type == "None") { _controller.setController1Type(quickNES::Controller::controller_t::none); isTypeRecognized = true; } - if (type == "Joypad") { _controller.setController1Type(quickNES::Controller::controller_t::joypad); isTypeRecognized = true; } - if (type == "FourScore1") { _controller.setController1Type(quickNES::Controller::controller_t::fourscore1); isTypeRecognized = true; } - if (type == "FourScore2") { _controller.setController1Type(quickNES::Controller::controller_t::fourscore2); isTypeRecognized = true; } - - if (isTypeRecognized == false) JAFFAR_THROW_LOGIC("Input type not recognized: '%s'\n", type.c_str()); - } - - inline void setController2Type(const std::string& type) - { - bool isTypeRecognized = false; - - if (type == "None") { _controller.setController2Type(quickNES::Controller::controller_t::none); isTypeRecognized = true; } - if (type == "Joypad") { _controller.setController2Type(quickNES::Controller::controller_t::joypad); isTypeRecognized = true; } - if (type == "FourScore1") { _controller.setController2Type(quickNES::Controller::controller_t::fourscore1); isTypeRecognized = true; } - if (type == "FourScore2") { _controller.setController2Type(quickNES::Controller::controller_t::fourscore2); isTypeRecognized = true; } - - if (isTypeRecognized == false) JAFFAR_THROW_LOGIC("Input type not recognized: '%s'\n", type.c_str()); - } + virtual void advanceState(const jaffar::input_t &input) = 0; inline void enableRendering() { _doRendering = true; }; inline void disableRendering() { _doRendering = false; }; @@ -114,7 +57,8 @@ class NESInstanceBase virtual size_t getFullStateSize() const = 0; virtual size_t getDifferentialStateSize() const = 0; - + inline jaffar::InputParser* getInputParser() const { return _inputParser.get(); } + // Virtual functions virtual uint8_t *getLowMem() const = 0; @@ -134,7 +78,6 @@ class NESInstanceBase virtual void enableStateBlockImpl(const std::string& block) = 0; virtual void disableStateBlockImpl(const std::string& block) = 0; virtual bool loadROMImpl(const uint8_t* romData, const size_t romSize) = 0; - virtual void advanceStateImpl(const quickNES::Controller::port_t controller1, const quickNES::Controller::port_t controller2) = 0; // Storage for the light state size size_t _stateSize; @@ -144,6 +87,7 @@ class NESInstanceBase private: - // Controller class for input parsing - quickNES::Controller _controller; + // Input parser instance + std::unique_ptr _inputParser; + }; diff --git a/source/player.cpp b/source/player.cpp index 65d8a3d..bea6436 100644 --- a/source/player.cpp +++ b/source/player.cpp @@ -112,12 +112,11 @@ int main(int argc, char *argv[]) jaffarCommon::logger::refreshTerminal(); // Creating emulator instance + nlohmann::json emulatorConfig; + emulatorConfig["Controller 1 Type"] = controller1Type; + emulatorConfig["Controller 2 Type"] = controller2Type; NESInstance e; - // Setting controller types - e.setController1Type(controller1Type); - e.setController2Type(controller2Type); - // Loading ROM File std::string romFileData; if (jaffarCommon::file::loadStringFromFile(romFileData, romFilePath) == false) JAFFAR_THROW_LOGIC("Could not rom file: %s\n", romFilePath.c_str()); diff --git a/source/quickNES/nesInstance.hpp b/source/quickNES/nesInstance.hpp index e2a1f4d..6db7464 100644 --- a/source/quickNES/nesInstance.hpp +++ b/source/quickNES/nesInstance.hpp @@ -17,7 +17,8 @@ extern void register_mapper_70(); class NESInstance final : public NESInstanceBase { public: - NESInstance() : NESInstanceBase() + + NESInstance(const nlohmann::json& config) : NESInstanceBase(config) { // If running the original QuickNES, register extra mappers now register_misc_mappers(); @@ -60,6 +61,12 @@ class NESInstance final : public NESInstanceBase void *getInternalEmulatorPointer() override { return &_nes; } + void advanceState(const jaffar::input_t &input) override + { + if (_doRendering == true) _nes.emulate_frame(input.port1, input.port2); + if (_doRendering == false) _nes.emulate_skip_frame(input.port1, input.port2); + } + protected: bool loadROMImpl(const uint8_t* romData, const size_t romSize) override @@ -74,11 +81,6 @@ class NESInstance final : public NESInstanceBase void enableStateBlockImpl(const std::string& block) override {}; void disableStateBlockImpl(const std::string& block) override {}; - void advanceStateImpl(const quickNES::Controller::port_t controller1, const quickNES::Controller::port_t controller2) override - { - if (_doRendering == true) _nes.emulate_frame(controller1, controller2); - if (_doRendering == false) _nes.emulate_skip_frame(controller1, controller2); - } private: diff --git a/source/quickerNES/nesInstance.hpp b/source/quickerNES/nesInstance.hpp index 32270a5..32721b8 100644 --- a/source/quickerNES/nesInstance.hpp +++ b/source/quickerNES/nesInstance.hpp @@ -9,6 +9,8 @@ class NESInstance final : public NESInstanceBase { public: + NESInstance(const nlohmann::json& config) : NESInstanceBase(config) {} + uint8_t *getLowMem() const override { return _nes.get_low_mem(); }; size_t getLowMemSize() const override { return _nes.get_low_mem_size(); }; @@ -50,6 +52,12 @@ class NESInstance final : public NESInstanceBase void setNTABBlockSize(const size_t size) override { _nes.setNTABBlockSize(size); } + void advanceState(const jaffar::input_t &input) override + { + if (_doRendering == true) _nes.emulate_frame(input.port1, input.port2); + if (_doRendering == false) _nes.emulate_skip_frame(input.port1, input.port2); + } + protected: bool loadROMImpl(const uint8_t* romData, const size_t romSize) override @@ -61,11 +69,7 @@ class NESInstance final : public NESInstanceBase void enableStateBlockImpl(const std::string& block) override { _nes.enableStateBlock(block); }; void disableStateBlockImpl(const std::string& block) override { _nes.disableStateBlock(block); }; - void advanceStateImpl(const quickNES::Controller::port_t controller1, const quickNES::Controller::port_t controller2) override - { - if (_doRendering == true) _nes.emulate_frame(controller1, controller2); - if (_doRendering == false) _nes.emulate_skip_frame(controller1, controller2); - } + private: diff --git a/source/tester.cpp b/source/tester.cpp index f674ce2..91d63b0 100644 --- a/source/tester.cpp +++ b/source/tester.cpp @@ -87,7 +87,7 @@ int main(int argc, char *argv[]) stateDisabledBlocks.push_back(entry.get()); stateDisabledBlocksOutput += entry.get() + std::string(" "); } - + // Getting Controller 1 type if (scriptJson.contains("Controller 1 Type") == false) JAFFAR_THROW_LOGIC("Script file missing 'Controller 1 Type' entry\n"); if (scriptJson["Controller 1 Type"].is_string() == false) JAFFAR_THROW_LOGIC("Script file 'Controller 1 Type' entry is not a string\n"); @@ -116,11 +116,7 @@ int main(int argc, char *argv[]) const auto differentialCompressionUseZlib = differentialCompressionJs["Use Zlib"].get(); // Creating emulator instance - NESInstance e; - - // Setting controller types - e.setController1Type(controller1Type); - e.setController2Type(controller2Type); + NESInstance e(scriptJson); // Loading ROM File std::string romFileData; @@ -165,6 +161,13 @@ int main(int argc, char *argv[]) // Getting sequence lenght const auto sequenceLength = sequence.size(); + // Getting input parser from the emulator + const auto inputParser = e.getInputParser(); + + // Getting decoded emulator input for each entry in the sequence + std::vector decodedSequence; + for (const auto& inputString : sequence) decodedSequence.push_back(inputParser->parseInputString(inputString)); + // Getting emulation core name std::string emulationCoreName = e.getCoreName(); @@ -174,7 +177,6 @@ int main(int argc, char *argv[]) printf("[] Cycle Type: '%s'\n", cycleType.c_str()); printf("[] Emulation Core: '%s'\n", emulationCoreName.c_str()); printf("[] ROM File: '%s'\n", romFilePath.c_str()); - printf("[] Controller Types: '%s' / '%s'\n", controller1Type.c_str(), controller2Type.c_str()); printf("[] ROM Hash: 'SHA1: %s'\n", romSHA1.c_str()); printf("[] Sequence File: '%s'\n", sequenceFilePath.c_str()); printf("[] Sequence Length: %lu\n", sequenceLength); @@ -218,7 +220,7 @@ int main(int argc, char *argv[]) // Actually running the sequence auto t0 = std::chrono::high_resolution_clock::now(); - for (const std::string &input : sequence) + for (const auto &input : decodedSequence) { if (doPreAdvance == true) e.advanceState(input);