Adapting for pre-decoded inputs

This commit is contained in:
SergioMartin86 2024-07-28 16:43:09 +02:00
parent 67c93e664e
commit 6ca0537d45
8 changed files with 267 additions and 299 deletions

2
extern/jaffarCommon vendored

@ -1 +1 @@
Subproject commit 6db55ef68011a6777f980f878a54501f42b6c894
Subproject commit e7fd15b6e3ffed9bd718c0bfc0b0a6247e5dfe76

View File

@ -1,207 +0,0 @@
#pragma once
// Base controller class
// by eien86
#include <cstdint>
#include <string>
#include <sstream>
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

224
source/inputParser.hpp Normal file
View File

@ -0,0 +1,224 @@
#pragma once
// Base controller class
// by eien86
#include <cstdint>
#include <string>
#include <sstream>
#include <jaffarCommon/exceptions.hpp>
#include <jaffarCommon/json.hpp>
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

View File

@ -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<jaffar::InputParser>(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,6 +57,7 @@ class NESInstanceBase
virtual size_t getFullStateSize() const = 0;
virtual size_t getDifferentialStateSize() const = 0;
inline jaffar::InputParser* getInputParser() const { return _inputParser.get(); }
// Virtual functions
@ -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<jaffar::InputParser> _inputParser;
};

View File

@ -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());

View File

@ -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:

View File

@ -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:

View File

@ -116,11 +116,7 @@ int main(int argc, char *argv[])
const auto differentialCompressionUseZlib = differentialCompressionJs["Use Zlib"].get<bool>();
// 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<jaffar::input_t> 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);