2024-01-10 18:48:57 +00:00
|
|
|
#pragma once
|
|
|
|
|
2024-07-28 14:52:05 +00:00
|
|
|
#include "nesInstance.hpp"
|
2024-01-10 18:48:57 +00:00
|
|
|
#include <SDL.h>
|
|
|
|
#include <SDL_image.h>
|
2024-03-06 15:21:04 +00:00
|
|
|
#include <extern/hqn/hqn.h>
|
|
|
|
#include <extern/hqn/hqn_gui_controller.h>
|
|
|
|
#include <jaffarCommon/deserializers/contiguous.hpp>
|
|
|
|
#include <jaffarCommon/hash.hpp>
|
2024-07-28 14:52:05 +00:00
|
|
|
#include <jaffarCommon/serializers/contiguous.hpp>
|
|
|
|
#include <string>
|
|
|
|
#include <unistd.h>
|
2024-01-10 18:48:57 +00:00
|
|
|
|
|
|
|
#define _INVERSE_FRAME_RATE 16667
|
|
|
|
|
2024-01-11 18:44:54 +00:00
|
|
|
struct stepData_t
|
|
|
|
{
|
2024-07-28 15:07:38 +00:00
|
|
|
std::string inputString;
|
|
|
|
jaffar::input_t decodedInput;
|
2024-01-20 10:21:34 +00:00
|
|
|
uint8_t *stateData;
|
2024-03-06 15:21:04 +00:00
|
|
|
jaffarCommon::hash::hash_t hash;
|
2024-01-11 18:44:54 +00:00
|
|
|
};
|
|
|
|
|
2024-01-10 18:48:57 +00:00
|
|
|
class PlaybackInstance
|
|
|
|
{
|
2024-01-20 14:26:14 +00:00
|
|
|
static const uint16_t image_width = 256;
|
|
|
|
static const uint16_t image_height = 240;
|
|
|
|
|
2024-01-20 10:21:34 +00:00
|
|
|
public:
|
2024-07-28 15:07:38 +00:00
|
|
|
void addStep(const std::string &inputString, const jaffar::input_t decodedInput)
|
2024-01-11 18:44:54 +00:00
|
|
|
{
|
|
|
|
stepData_t step;
|
2024-07-28 15:07:38 +00:00
|
|
|
step.inputString = inputString;
|
|
|
|
step.decodedInput = decodedInput;
|
2024-02-09 19:43:42 +00:00
|
|
|
step.stateData = (uint8_t *)malloc(_emu->getFullStateSize());
|
|
|
|
|
|
|
|
jaffarCommon::serializer::Contiguous serializer(step.stateData);
|
|
|
|
_emu->serializeState(serializer);
|
2024-03-06 15:21:04 +00:00
|
|
|
step.hash = jaffarCommon::hash::calculateMetroHash(_emu->getLowMem(), _emu->getLowMemSize());
|
2024-01-11 18:44:54 +00:00
|
|
|
|
|
|
|
// Adding the step into the sequence
|
|
|
|
_stepSequence.push_back(step);
|
|
|
|
}
|
2024-01-10 18:48:57 +00:00
|
|
|
|
2024-01-11 18:44:54 +00:00
|
|
|
// Initializes the playback module instance
|
2024-03-22 11:50:46 +00:00
|
|
|
PlaybackInstance(NESInstance *emu, const std::string &overlayPath = "") : _emu(emu)
|
2024-01-11 18:44:54 +00:00
|
|
|
{
|
2024-01-20 10:21:34 +00:00
|
|
|
// Loading overlay, if provided
|
|
|
|
if (overlayPath != "")
|
|
|
|
{
|
|
|
|
// Using overlay
|
|
|
|
_useOverlay = true;
|
|
|
|
|
|
|
|
// Loading overlay images
|
|
|
|
std::string imagePath;
|
|
|
|
|
|
|
|
imagePath = _overlayPath + std::string("/base.png");
|
|
|
|
_overlayBaseSurface = IMG_Load(imagePath.c_str());
|
2024-03-06 15:21:04 +00:00
|
|
|
if (_overlayBaseSurface == NULL) JAFFAR_THROW_LOGIC("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
|
2024-01-20 10:21:34 +00:00
|
|
|
|
|
|
|
imagePath = _overlayPath + std::string("/button_a.png");
|
|
|
|
_overlayButtonASurface = IMG_Load(imagePath.c_str());
|
2024-03-06 15:21:04 +00:00
|
|
|
if (_overlayButtonASurface == NULL) JAFFAR_THROW_LOGIC("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
|
2024-01-20 10:21:34 +00:00
|
|
|
|
|
|
|
imagePath = _overlayPath + std::string("/button_b.png");
|
|
|
|
_overlayButtonBSurface = IMG_Load(imagePath.c_str());
|
2024-03-06 15:21:04 +00:00
|
|
|
if (_overlayButtonBSurface == NULL) JAFFAR_THROW_LOGIC("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
|
2024-01-20 10:21:34 +00:00
|
|
|
|
|
|
|
imagePath = _overlayPath + std::string("/button_select.png");
|
|
|
|
_overlayButtonSelectSurface = IMG_Load(imagePath.c_str());
|
2024-03-06 15:21:04 +00:00
|
|
|
if (_overlayButtonSelectSurface == NULL) JAFFAR_THROW_LOGIC("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
|
2024-01-20 10:21:34 +00:00
|
|
|
|
|
|
|
imagePath = _overlayPath + std::string("/button_start.png");
|
|
|
|
_overlayButtonStartSurface = IMG_Load(imagePath.c_str());
|
2024-03-06 15:21:04 +00:00
|
|
|
if (_overlayButtonStartSurface == NULL) JAFFAR_THROW_LOGIC("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
|
2024-01-20 10:21:34 +00:00
|
|
|
|
|
|
|
imagePath = _overlayPath + std::string("/button_left.png");
|
|
|
|
_overlayButtonLeftSurface = IMG_Load(imagePath.c_str());
|
2024-03-06 15:21:04 +00:00
|
|
|
if (_overlayButtonLeftSurface == NULL) JAFFAR_THROW_LOGIC("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
|
2024-01-20 10:21:34 +00:00
|
|
|
|
|
|
|
imagePath = _overlayPath + std::string("/button_right.png");
|
|
|
|
_overlayButtonRightSurface = IMG_Load(imagePath.c_str());
|
2024-03-06 15:21:04 +00:00
|
|
|
if (_overlayButtonRightSurface == NULL) JAFFAR_THROW_LOGIC("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
|
2024-01-20 10:21:34 +00:00
|
|
|
|
|
|
|
imagePath = _overlayPath + std::string("/button_up.png");
|
|
|
|
_overlayButtonUpSurface = IMG_Load(imagePath.c_str());
|
2024-03-06 15:21:04 +00:00
|
|
|
if (_overlayButtonUpSurface == NULL) JAFFAR_THROW_LOGIC("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
|
2024-01-20 10:21:34 +00:00
|
|
|
|
|
|
|
imagePath = _overlayPath + std::string("/button_down.png");
|
|
|
|
_overlayButtonDownSurface = IMG_Load(imagePath.c_str());
|
2024-03-06 15:21:04 +00:00
|
|
|
if (_overlayButtonDownSurface == NULL) JAFFAR_THROW_LOGIC("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
|
2024-01-20 10:21:34 +00:00
|
|
|
}
|
2024-03-22 11:50:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void initialize(const std::vector<std::string> &sequence)
|
|
|
|
{
|
2024-07-28 15:07:38 +00:00
|
|
|
// Getting input decoder
|
|
|
|
auto inputParser = _emu->getInputParser();
|
|
|
|
|
2024-03-22 11:50:46 +00:00
|
|
|
// Building sequence information
|
|
|
|
for (const auto &input : sequence)
|
|
|
|
{
|
2024-07-28 15:07:38 +00:00
|
|
|
// Getting decoded input
|
|
|
|
const auto decodedInput = inputParser->parseInputString(input);
|
|
|
|
|
2024-03-22 11:50:46 +00:00
|
|
|
// Adding new step
|
2024-07-28 15:07:38 +00:00
|
|
|
addStep(input, decodedInput);
|
2024-03-22 11:50:46 +00:00
|
|
|
|
|
|
|
// Advance state based on the input received
|
2024-07-28 15:07:38 +00:00
|
|
|
_emu->advanceState(decodedInput);
|
2024-03-22 11:50:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Adding last step with no input
|
2024-07-28 15:07:38 +00:00
|
|
|
addStep("<End Of Sequence>", jaffar::input_t());
|
2024-03-22 11:50:46 +00:00
|
|
|
}
|
|
|
|
|
2024-07-28 14:52:05 +00:00
|
|
|
void enableRendering(SDL_Window *window)
|
2024-03-22 11:50:46 +00:00
|
|
|
{
|
|
|
|
// Allocating video buffer
|
|
|
|
_video_buffer = (uint8_t *)malloc(image_width * image_height);
|
|
|
|
|
|
|
|
// Setting video buffer
|
2024-07-28 14:52:05 +00:00
|
|
|
((emulator_t *)_emu->getInternalEmulatorPointer())->set_pixels(_video_buffer, image_width + 8);
|
2024-01-20 10:21:34 +00:00
|
|
|
|
2024-03-22 11:50:46 +00:00
|
|
|
// Loading Emulator instance HQN
|
|
|
|
_hqnState.setEmulatorPointer(_emu->getInternalEmulatorPointer());
|
|
|
|
static uint8_t video_buffer[image_width * image_height];
|
|
|
|
_hqnState.m_emu->set_pixels(video_buffer, image_width + 8);
|
2024-01-20 10:21:34 +00:00
|
|
|
|
2024-03-22 11:50:46 +00:00
|
|
|
// Enabling emulation rendering
|
|
|
|
_emu->enableRendering();
|
2024-01-20 10:21:34 +00:00
|
|
|
|
|
|
|
// Creating HQN GUI
|
2024-03-22 11:50:46 +00:00
|
|
|
_hqnGUI = hqn::GUIController::create(_hqnState, window);
|
2024-01-20 10:21:34 +00:00
|
|
|
_hqnGUI->setScale(1);
|
2024-01-11 18:44:54 +00:00
|
|
|
}
|
2024-01-10 18:48:57 +00:00
|
|
|
|
2024-01-20 10:21:34 +00:00
|
|
|
// Function to render frame
|
|
|
|
void renderFrame(const size_t stepId)
|
2024-01-10 18:48:57 +00:00
|
|
|
{
|
2024-01-20 10:21:34 +00:00
|
|
|
// Checking the required step id does not exceed contents of the sequence
|
2024-03-06 15:21:04 +00:00
|
|
|
if (stepId > _stepSequence.size()) JAFFAR_THROW_LOGIC("[Error] Attempting to render a step larger than the step sequence");
|
2024-01-20 10:21:34 +00:00
|
|
|
|
|
|
|
// Getting step information
|
|
|
|
const auto &step = _stepSequence[stepId];
|
|
|
|
|
|
|
|
// Pointer to overlay images (NULL if unused)
|
|
|
|
SDL_Surface *overlayButtonASurface = NULL;
|
|
|
|
SDL_Surface *overlayButtonBSurface = NULL;
|
|
|
|
SDL_Surface *overlayButtonSelectSurface = NULL;
|
|
|
|
SDL_Surface *overlayButtonStartSurface = NULL;
|
|
|
|
SDL_Surface *overlayButtonLeftSurface = NULL;
|
|
|
|
SDL_Surface *overlayButtonRightSurface = NULL;
|
|
|
|
SDL_Surface *overlayButtonUpSurface = NULL;
|
|
|
|
SDL_Surface *overlayButtonDownSurface = NULL;
|
|
|
|
|
|
|
|
// Load correct overlay images, if using overlay
|
|
|
|
if (_useOverlay == true)
|
|
|
|
{
|
2024-07-28 15:07:38 +00:00
|
|
|
if (step.inputString.find("A") != std::string::npos) overlayButtonASurface = _overlayButtonASurface;
|
|
|
|
if (step.inputString.find("B") != std::string::npos) overlayButtonBSurface = _overlayButtonBSurface;
|
|
|
|
if (step.inputString.find("S") != std::string::npos) overlayButtonSelectSurface = _overlayButtonSelectSurface;
|
|
|
|
if (step.inputString.find("T") != std::string::npos) overlayButtonStartSurface = _overlayButtonStartSurface;
|
|
|
|
if (step.inputString.find("L") != std::string::npos) overlayButtonLeftSurface = _overlayButtonLeftSurface;
|
|
|
|
if (step.inputString.find("R") != std::string::npos) overlayButtonRightSurface = _overlayButtonRightSurface;
|
|
|
|
if (step.inputString.find("U") != std::string::npos) overlayButtonUpSurface = _overlayButtonUpSurface;
|
|
|
|
if (step.inputString.find("D") != std::string::npos) overlayButtonDownSurface = _overlayButtonDownSurface;
|
2024-01-20 10:21:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Since we do not store the blit information (too much memory), we need to load the previous frame and re-run the input
|
|
|
|
|
|
|
|
// If its the first step, then simply reset
|
|
|
|
if (stepId == 0) _emu->doHardReset();
|
|
|
|
|
|
|
|
// Else we load the previous frame
|
|
|
|
if (stepId > 0)
|
|
|
|
{
|
|
|
|
const auto stateData = getStateData(stepId - 1);
|
2024-02-09 19:43:42 +00:00
|
|
|
jaffarCommon::deserializer::Contiguous deserializer(stateData);
|
|
|
|
_emu->deserializeState(deserializer);
|
2024-07-28 15:07:38 +00:00
|
|
|
_emu->advanceState(getDecodedInput(stepId - 1));
|
2024-01-20 10:21:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Updating image
|
|
|
|
int32_t curBlit[BLIT_SIZE];
|
|
|
|
saveBlit(_emu->getInternalEmulatorPointer(), curBlit, hqn::HQNState::NES_VIDEO_PALETTE, 0, 0, 0, 0);
|
|
|
|
_hqnGUI->update_blit(curBlit, _overlayBaseSurface, overlayButtonASurface, overlayButtonBSurface, overlayButtonSelectSurface, overlayButtonStartSurface, overlayButtonLeftSurface, overlayButtonRightSurface, overlayButtonUpSurface, overlayButtonDownSurface);
|
|
|
|
}
|
2024-01-10 18:48:57 +00:00
|
|
|
|
2024-01-20 10:21:34 +00:00
|
|
|
size_t getSequenceLength() const
|
|
|
|
{
|
|
|
|
return _stepSequence.size();
|
|
|
|
}
|
2024-01-10 18:48:57 +00:00
|
|
|
|
2024-07-28 15:07:38 +00:00
|
|
|
const std::string getInputString(const size_t stepId) const
|
2024-01-20 10:21:34 +00:00
|
|
|
{
|
|
|
|
// Checking the required step id does not exceed contents of the sequence
|
2024-03-06 15:21:04 +00:00
|
|
|
if (stepId > _stepSequence.size()) JAFFAR_THROW_LOGIC("[Error] Attempting to render a step larger than the step sequence");
|
2024-01-10 18:48:57 +00:00
|
|
|
|
2024-01-20 10:21:34 +00:00
|
|
|
// Getting step information
|
|
|
|
const auto &step = _stepSequence[stepId];
|
2024-01-10 18:48:57 +00:00
|
|
|
|
2024-01-20 10:21:34 +00:00
|
|
|
// Returning step input
|
2024-07-28 15:07:38 +00:00
|
|
|
return step.inputString;
|
2024-01-20 10:21:34 +00:00
|
|
|
}
|
2024-01-10 18:48:57 +00:00
|
|
|
|
2024-07-28 15:07:38 +00:00
|
|
|
const jaffar::input_t getDecodedInput(const size_t stepId) const
|
2024-01-20 10:21:34 +00:00
|
|
|
{
|
|
|
|
// Checking the required step id does not exceed contents of the sequence
|
2024-03-06 15:21:04 +00:00
|
|
|
if (stepId > _stepSequence.size()) JAFFAR_THROW_LOGIC("[Error] Attempting to render a step larger than the step sequence");
|
2024-01-10 18:48:57 +00:00
|
|
|
|
2024-01-20 10:21:34 +00:00
|
|
|
// Getting step information
|
|
|
|
const auto &step = _stepSequence[stepId];
|
2024-01-10 18:48:57 +00:00
|
|
|
|
2024-01-20 10:21:34 +00:00
|
|
|
// Returning step input
|
2024-07-28 15:07:38 +00:00
|
|
|
return step.decodedInput;
|
2024-01-20 10:21:34 +00:00
|
|
|
}
|
2024-01-10 18:48:57 +00:00
|
|
|
|
2024-07-28 15:07:38 +00:00
|
|
|
const uint8_t *getStateData(const size_t stepId) const
|
2024-01-20 10:21:34 +00:00
|
|
|
{
|
|
|
|
// Checking the required step id does not exceed contents of the sequence
|
2024-03-06 15:21:04 +00:00
|
|
|
if (stepId > _stepSequence.size()) JAFFAR_THROW_LOGIC("[Error] Attempting to render a step larger than the step sequence");
|
2024-01-10 18:48:57 +00:00
|
|
|
|
2024-01-20 10:21:34 +00:00
|
|
|
// Getting step information
|
|
|
|
const auto &step = _stepSequence[stepId];
|
2024-01-10 18:48:57 +00:00
|
|
|
|
2024-01-20 10:21:34 +00:00
|
|
|
// Returning step input
|
2024-07-28 15:07:38 +00:00
|
|
|
return step.stateData;
|
2024-01-10 18:48:57 +00:00
|
|
|
}
|
|
|
|
|
2024-07-28 15:07:38 +00:00
|
|
|
const jaffarCommon::hash::hash_t getStateHash(const size_t stepId) const
|
2024-01-10 18:48:57 +00:00
|
|
|
{
|
2024-01-20 10:21:34 +00:00
|
|
|
// Checking the required step id does not exceed contents of the sequence
|
2024-03-06 15:21:04 +00:00
|
|
|
if (stepId > _stepSequence.size()) JAFFAR_THROW_LOGIC("[Error] Attempting to render a step larger than the step sequence");
|
2024-01-13 06:04:22 +00:00
|
|
|
|
2024-01-20 10:21:34 +00:00
|
|
|
// Getting step information
|
|
|
|
const auto &step = _stepSequence[stepId];
|
2024-01-13 06:04:22 +00:00
|
|
|
|
2024-01-20 10:21:34 +00:00
|
|
|
// Returning step input
|
2024-07-28 15:07:38 +00:00
|
|
|
return step.hash;
|
2024-01-13 06:04:22 +00:00
|
|
|
}
|
2024-01-20 10:21:34 +00:00
|
|
|
|
2024-07-28 15:07:38 +00:00
|
|
|
|
2024-01-20 10:21:34 +00:00
|
|
|
private:
|
|
|
|
// Internal sequence information
|
|
|
|
std::vector<stepData_t> _stepSequence;
|
|
|
|
|
|
|
|
// Storage for the HQN state
|
|
|
|
hqn::HQNState _hqnState;
|
|
|
|
|
|
|
|
// Storage for the HQN GUI controller
|
|
|
|
hqn::GUIController *_hqnGUI;
|
|
|
|
|
|
|
|
// Pointer to the contained emulator instance
|
2024-01-28 15:02:58 +00:00
|
|
|
NESInstance *const _emu;
|
2024-01-20 10:21:34 +00:00
|
|
|
|
|
|
|
// Flag to store whether to use the button overlay
|
|
|
|
bool _useOverlay = false;
|
|
|
|
|
2024-01-28 18:57:24 +00:00
|
|
|
// Video buffer
|
|
|
|
uint8_t *_video_buffer;
|
|
|
|
|
2024-01-20 10:21:34 +00:00
|
|
|
// Overlay info
|
|
|
|
std::string _overlayPath;
|
|
|
|
SDL_Surface *_overlayBaseSurface = NULL;
|
|
|
|
SDL_Surface *_overlayButtonASurface;
|
|
|
|
SDL_Surface *_overlayButtonBSurface;
|
|
|
|
SDL_Surface *_overlayButtonSelectSurface;
|
|
|
|
SDL_Surface *_overlayButtonStartSurface;
|
|
|
|
SDL_Surface *_overlayButtonLeftSurface;
|
|
|
|
SDL_Surface *_overlayButtonRightSurface;
|
|
|
|
SDL_Surface *_overlayButtonUpSurface;
|
|
|
|
SDL_Surface *_overlayButtonDownSurface;
|
2024-01-10 18:48:57 +00:00
|
|
|
};
|