Progress with player
This commit is contained in:
parent
d310134441
commit
16143afe0d
|
@ -1,3 +1,3 @@
|
||||||
[submodule "tests/roms/nes-test-roms"]
|
[submodule "tests/roms/nes-test-roms"]
|
||||||
path = tests/nes-test-roms
|
path = tests/roms/test-roms
|
||||||
url = git@github.com:christopherpow/nes-test-roms.git
|
url = git@github.com:christopherpow/nes-test-roms.git
|
||||||
|
|
|
@ -183,45 +183,5 @@ double HQNState::getFPS() const
|
||||||
return fps;
|
return fps;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copied from bizinterface.cpp in BizHawk/quicknes
|
|
||||||
void HQNState::blit(int32_t *dest, const int32_t *colors, int cropleft, int croptop, int cropright, int cropbottom) const
|
|
||||||
{
|
|
||||||
// what is the point of the 256 color bitmap and the dynamic color allocation to it?
|
|
||||||
// why not just render directly to a 512 color bitmap with static palette positions?
|
|
||||||
// Nes_Emu *e = m_emu; // e was a parameter but since this is now part of a class, it's just in here
|
|
||||||
// const int srcpitch = e->frame().pitch;
|
|
||||||
// const unsigned char *src = e->frame().pixels;
|
|
||||||
// const unsigned char *const srcend = src + (e->image_height - cropbottom) * srcpitch;
|
|
||||||
//
|
|
||||||
// const short *lut = e->frame().palette;
|
|
||||||
//
|
|
||||||
// const int rowlen = 256 - cropleft - cropright;
|
|
||||||
//
|
|
||||||
// src += cropleft;
|
|
||||||
// src += croptop * srcpitch;
|
|
||||||
//
|
|
||||||
// for (; src < srcend; src += srcpitch)
|
|
||||||
// {
|
|
||||||
// for (int i = 0; i < rowlen; i++)
|
|
||||||
// {
|
|
||||||
// *dest++ = colors[lut[src[i]]];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
Nes_Emu *e = m_emu; // e was a parameter but since this is now part of a class, it's just in here
|
|
||||||
const unsigned char *in_pixels = e->frame().pixels;
|
|
||||||
int32_t *out_pixels = dest;
|
|
||||||
|
|
||||||
for (unsigned h = 0; h < Nes_Emu::image_height; h++, in_pixels += e->frame().pitch, out_pixels += Nes_Emu::image_width)
|
|
||||||
for (unsigned w = 0; w < Nes_Emu::image_width; w++)
|
|
||||||
{
|
|
||||||
unsigned col = e->frame().palette[in_pixels[w]];
|
|
||||||
const Nes_Emu::rgb_t& rgb = m_emu->nes_colors[col];
|
|
||||||
unsigned r = rgb.red;
|
|
||||||
unsigned g = rgb.green;
|
|
||||||
unsigned b = rgb.blue;
|
|
||||||
out_pixels[w] = (r << 16) | (g << 8) | (b << 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // end namespace hqn
|
} // end namespace hqn
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include <core/Nes_Emu.h>
|
#include <core/Nes_Emu.h>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
#define BLIT_SIZE 65536
|
#define BLIT_SIZE 65536
|
||||||
|
|
||||||
|
@ -104,12 +105,6 @@ public:
|
||||||
*/
|
*/
|
||||||
double getFPS() const;
|
double getFPS() const;
|
||||||
|
|
||||||
/**
|
|
||||||
* Blit the contents of the NES screen to dest. Dest should be able to hold 256*240 ints.
|
|
||||||
* Unless you want to change the color palette you should use NES_VIDEO_PALETTE for colors.
|
|
||||||
*/
|
|
||||||
void blit(int32_t *dest, const int32_t *colors, int cropleft, int croptop, int cropright, int cropbottom) const;
|
|
||||||
|
|
||||||
inline HQNListener *getListener() const
|
inline HQNListener *getListener() const
|
||||||
{ return m_listener; }
|
{ return m_listener; }
|
||||||
|
|
||||||
|
@ -152,5 +147,45 @@ void printUsage(const char *filename);
|
||||||
|
|
||||||
} // end namespace hqn
|
} // end namespace hqn
|
||||||
|
|
||||||
|
// Copied from bizinterface.cpp in BizHawk/quicknes
|
||||||
|
inline void saveBlit(const Nes_Emu *e, int32_t *dest, const int32_t *colors, int cropleft, int croptop, int cropright, int cropbottom)
|
||||||
|
{
|
||||||
|
// what is the point of the 256 color bitmap and the dynamic color allocation to it?
|
||||||
|
// why not just render directly to a 512 color bitmap with static palette positions?
|
||||||
|
// Nes_Emu *e = m_emu; // e was a parameter but since this is now part of a class, it's just in here
|
||||||
|
// const int srcpitch = e->frame().pitch;
|
||||||
|
// const unsigned char *src = e->frame().pixels;
|
||||||
|
// const unsigned char *const srcend = src + (e->image_height - cropbottom) * srcpitch;
|
||||||
|
//
|
||||||
|
// const short *lut = e->frame().palette;
|
||||||
|
//
|
||||||
|
// const int rowlen = 256 - cropleft - cropright;
|
||||||
|
//
|
||||||
|
// src += cropleft;
|
||||||
|
// src += croptop * srcpitch;
|
||||||
|
//
|
||||||
|
// for (; src < srcend; src += srcpitch)
|
||||||
|
// {
|
||||||
|
// for (int i = 0; i < rowlen; i++)
|
||||||
|
// {
|
||||||
|
// *dest++ = colors[lut[src[i]]];
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
const unsigned char *in_pixels = e->frame().pixels;
|
||||||
|
if (in_pixels == NULL) return;
|
||||||
|
int32_t *out_pixels = dest;
|
||||||
|
|
||||||
|
for (unsigned h = 0; h < Nes_Emu::image_height; h++, in_pixels += e->frame().pitch, out_pixels += Nes_Emu::image_width)
|
||||||
|
for (unsigned w = 0; w < Nes_Emu::image_width; w++)
|
||||||
|
{
|
||||||
|
unsigned col = e->frame().palette[in_pixels[w]];
|
||||||
|
const Nes_Emu::rgb_t& rgb = e->nes_colors[col];
|
||||||
|
unsigned r = rgb.red;
|
||||||
|
unsigned g = rgb.green;
|
||||||
|
unsigned b = rgb.blue;
|
||||||
|
out_pixels[w] = (r << 16) | (g << 8) | (b << 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* __HQN_H__ */
|
#endif /* __HQN_H__ */
|
||||||
|
|
|
@ -190,7 +190,6 @@ void GUIController::update(bool readNES)
|
||||||
|
|
||||||
if (SDL_LockTexture(m_tex, nullptr, &nesPixels, &pitch) < 0) return;
|
if (SDL_LockTexture(m_tex, nullptr, &nesPixels, &pitch) < 0) return;
|
||||||
|
|
||||||
m_state.blit((int32_t*)nesPixels, HQNState::NES_VIDEO_PALETTE, 0, 0, 0, 0);
|
|
||||||
SDL_UnlockTexture(m_tex);
|
SDL_UnlockTexture(m_tex);
|
||||||
|
|
||||||
// render to screen
|
// render to screen
|
||||||
|
@ -201,7 +200,7 @@ void GUIController::update(bool readNES)
|
||||||
processEvents();
|
processEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GUIController::update_blit(int32_t* blit, SDL_Surface* base, SDL_Surface* button_a, SDL_Surface* button_b, SDL_Surface* button_select, SDL_Surface* button_start, SDL_Surface* button_up, SDL_Surface* button_down, SDL_Surface* button_left, SDL_Surface* button_right)
|
void GUIController::update_blit(const int32_t* blit, SDL_Surface* base, SDL_Surface* button_a, SDL_Surface* button_b, SDL_Surface* button_select, SDL_Surface* button_start, SDL_Surface* button_up, SDL_Surface* button_down, SDL_Surface* button_left, SDL_Surface* button_right)
|
||||||
{
|
{
|
||||||
void *nesPixels = nullptr;
|
void *nesPixels = nullptr;
|
||||||
int pitch = 0;
|
int pitch = 0;
|
||||||
|
|
|
@ -99,7 +99,7 @@ public:
|
||||||
*/
|
*/
|
||||||
void setCloseOperation(CloseOperation ops);
|
void setCloseOperation(CloseOperation ops);
|
||||||
CloseOperation getCloseOperation() const;
|
CloseOperation getCloseOperation() const;
|
||||||
void update_blit(int32_t* blit, SDL_Surface* base, SDL_Surface* button_a, SDL_Surface* button_b, SDL_Surface* button_select, SDL_Surface* button_start, SDL_Surface* button_up, SDL_Surface* button_down, SDL_Surface* button_left, SDL_Surface* button_right);
|
void update_blit(const int32_t* blit, SDL_Surface* base, SDL_Surface* button_a, SDL_Surface* button_b, SDL_Surface* button_select, SDL_Surface* button_start, SDL_Surface* button_up, SDL_Surface* button_down, SDL_Surface* button_left, SDL_Surface* button_right);
|
||||||
|
|
||||||
// Methods overriden from superclass.
|
// Methods overriden from superclass.
|
||||||
virtual void onLoadROM(HQNState *state, const char *filename);
|
virtual void onLoadROM(HQNState *state, const char *filename);
|
||||||
|
|
|
@ -59,7 +59,7 @@ quickerNESPlayerSrc = [
|
||||||
|
|
||||||
quickerNESPlayerIncludeDirs = include_directories([ 'source'])
|
quickerNESPlayerIncludeDirs = include_directories([ 'source'])
|
||||||
quickerNESPlayerDependencies = [ dependency('sdl2'), dependency('SDL2_image') ]
|
quickerNESPlayerDependencies = [ dependency('sdl2'), dependency('SDL2_image') ]
|
||||||
quickerNESPlayerCPPFlags = [ ]
|
quickerNESPlayerCPPFlags = [ '-DNCURSES' ]
|
||||||
quickerNESPlayerCFlags = [ ]
|
quickerNESPlayerCFlags = [ ]
|
||||||
quickerNESPlayerLinkArgs = [ '-lncurses' ]
|
quickerNESPlayerLinkArgs = [ '-lncurses' ]
|
||||||
|
|
||||||
|
|
|
@ -177,7 +177,10 @@ const char * Nes_Emu::emulate_frame( int joypad1, int joypad2 )
|
||||||
f->joypad_read_count = emu.joypad_read_count;
|
f->joypad_read_count = emu.joypad_read_count;
|
||||||
f->burst_phase = emu.ppu.burst_phase;
|
f->burst_phase = emu.ppu.burst_phase;
|
||||||
f->pitch = emu.ppu.host_row_bytes;
|
f->pitch = emu.ppu.host_row_bytes;
|
||||||
f->pixels = emu.ppu.host_pixels + f->left;
|
if (emu.ppu.host_pixels != NULL)
|
||||||
|
f->pixels = emu.ppu.host_pixels + f->left;
|
||||||
|
else
|
||||||
|
f->pixels = NULL;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -46,6 +46,8 @@ public:
|
||||||
enum { image_width = 256 };
|
enum { image_width = 256 };
|
||||||
enum { image_height = 240 };
|
enum { image_height = 240 };
|
||||||
|
|
||||||
|
const uint8_t* getHostPixels () const { return emu.ppu.host_pixels; }
|
||||||
|
|
||||||
// Basic emulation
|
// Basic emulation
|
||||||
|
|
||||||
// Emulate one video frame using joypad1 and joypad2 as input. Afterwards, image
|
// Emulate one video frame using joypad1 and joypad2 as input. Afterwards, image
|
||||||
|
@ -265,6 +267,7 @@ inline void Nes_Emu::set_pixels( void* p, long n )
|
||||||
emu.ppu.host_row_bytes = n;
|
emu.ppu.host_row_bytes = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
inline uint8_t const* Nes_Emu::chr_mem()
|
inline uint8_t const* Nes_Emu::chr_mem()
|
||||||
{
|
{
|
||||||
return cart()->chr_size() ? (uint8_t*) cart()->chr() : emu.ppu.impl->chr_ram;
|
return cart()->chr_size() ? (uint8_t*) cart()->chr() : emu.ppu.impl->chr_ram;
|
||||||
|
|
|
@ -32,11 +32,6 @@ class EmuInstance
|
||||||
auto result = _nes->load_ines(romFile);
|
auto result = _nes->load_ines(romFile);
|
||||||
if (result != 0) EXIT_WITH_ERROR("Could not initialize emulator with rom file: %s\n", romFilePath.c_str());
|
if (result != 0) EXIT_WITH_ERROR("Could not initialize emulator with rom file: %s\n", romFilePath.c_str());
|
||||||
|
|
||||||
// Setting base memory pointers
|
|
||||||
_baseMem = _nes->low_mem();
|
|
||||||
_ppuNameTableMem = _nes->nametable_mem();
|
|
||||||
_highMem = _nes->high_mem();
|
|
||||||
|
|
||||||
// Getting state size to use
|
// Getting state size to use
|
||||||
_stateSize = getStateSizeImpl();
|
_stateSize = getStateSizeImpl();
|
||||||
|
|
||||||
|
@ -44,6 +39,10 @@ class EmuInstance
|
||||||
if (stateFilePath != "") loadStateFile(stateFilePath);
|
if (stateFilePath != "") loadStateFile(stateFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t* getLowMem() { return _nes->low_mem(); };
|
||||||
|
uint8_t* getNametableMem() { return _nes->nametable_mem(); };
|
||||||
|
uint8_t* getHighMem() { return _nes->high_mem();};
|
||||||
|
|
||||||
void loadStateFile(const std::string& stateFilePath)
|
void loadStateFile(const std::string& stateFilePath)
|
||||||
{
|
{
|
||||||
// Loading state data
|
// Loading state data
|
||||||
|
@ -167,11 +166,6 @@ class EmuInstance
|
||||||
// Emulator instance
|
// Emulator instance
|
||||||
Nes_Emu* _nes;
|
Nes_Emu* _nes;
|
||||||
|
|
||||||
// Base low-memory pointer
|
|
||||||
uint8_t* _baseMem;
|
|
||||||
uint8_t* _ppuNameTableMem;
|
|
||||||
uint8_t* _highMem;
|
|
||||||
|
|
||||||
// State size for the given rom
|
// State size for the given rom
|
||||||
size_t _stateSize;
|
size_t _stateSize;
|
||||||
|
|
||||||
|
|
|
@ -11,39 +11,54 @@
|
||||||
|
|
||||||
#define _INVERSE_FRAME_RATE 16667
|
#define _INVERSE_FRAME_RATE 16667
|
||||||
|
|
||||||
|
struct stepData_t
|
||||||
|
{
|
||||||
|
std::string input;
|
||||||
|
uint8_t* stateData;
|
||||||
|
int32_t curBlit[BLIT_SIZE];
|
||||||
|
};
|
||||||
|
|
||||||
class PlaybackInstance
|
class PlaybackInstance
|
||||||
{
|
{
|
||||||
private:
|
|
||||||
|
|
||||||
// Storage for the HQN state
|
|
||||||
hqn::HQNState _hqnState;
|
|
||||||
|
|
||||||
// Storage for the HQN GUI controller
|
|
||||||
hqn::GUIController* _hqnGUI;
|
|
||||||
|
|
||||||
// Pointer to the contained emulator instance
|
|
||||||
EmuInstance* const _emu;
|
|
||||||
|
|
||||||
// Flag to store whether to use the button overlay
|
|
||||||
bool _useOverlay = false;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
void addStep(const std::string& input)
|
||||||
|
{
|
||||||
|
stepData_t step;
|
||||||
|
step.input = input;
|
||||||
|
step.stateData = (uint8_t*) calloc(_emu->getStateSize(), 1);
|
||||||
|
_emu->serializeState(step.stateData);
|
||||||
|
saveBlit(_emu->getInternalEmulator(), step.curBlit, hqn::HQNState::NES_VIDEO_PALETTE, 0, 0, 0, 0);
|
||||||
|
|
||||||
|
// Adding the step into the sequence
|
||||||
|
_stepSequence.push_back(step);
|
||||||
|
}
|
||||||
|
|
||||||
// Initializes the playback module instance
|
// Initializes the playback module instance
|
||||||
PlaybackInstance(EmuInstance* emu, const std::string& overlayPath = "") : _emu(emu)
|
PlaybackInstance(EmuInstance* emu, const std::string sequenceString, const std::string& overlayPath = "") : _emu(emu)
|
||||||
{
|
{
|
||||||
|
// Loading Emulator instance HQN
|
||||||
|
_hqnState.m_emu = _emu->getInternalEmulator();
|
||||||
|
static uint8_t video_buffer[Nes_Emu::image_width * Nes_Emu::image_height];
|
||||||
|
_hqnState.m_emu->set_pixels(video_buffer, Nes_Emu::image_width+8);
|
||||||
|
|
||||||
|
// Building sequence information
|
||||||
|
const auto inputSequence = split(sequenceString, ' ');
|
||||||
|
|
||||||
|
// Building sequence information
|
||||||
|
for (const auto& input : inputSequence)
|
||||||
|
{
|
||||||
|
// Adding new step
|
||||||
|
addStep(input);
|
||||||
|
|
||||||
|
// Advance state based on the input received
|
||||||
|
_emu->advanceState(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding last step with no input
|
||||||
|
addStep("<End Of Sequence>");
|
||||||
|
|
||||||
|
// Loading overlay, if provided
|
||||||
if (overlayPath != "")
|
if (overlayPath != "")
|
||||||
{
|
{
|
||||||
// Using overlay
|
// Using overlay
|
||||||
|
@ -89,11 +104,6 @@ class PlaybackInstance
|
||||||
if (_overlayButtonDownSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
|
if (_overlayButtonDownSurface == NULL) EXIT_WITH_ERROR("[Error] Could not load image: %s, Reason: %s\n", imagePath.c_str(), SDL_GetError());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loading Emulator instance HQN
|
|
||||||
_hqnState.m_emu = _emu->getInternalEmulator();
|
|
||||||
static uint8_t video_buffer[Nes_Emu::image_width * Nes_Emu::image_height];
|
|
||||||
_hqnState.m_emu->set_pixels(video_buffer, Nes_Emu::image_width+8);
|
|
||||||
|
|
||||||
// Opening rendering window
|
// Opening rendering window
|
||||||
SDL_SetMainReady();
|
SDL_SetMainReady();
|
||||||
|
|
||||||
|
@ -108,14 +118,13 @@ class PlaybackInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to render frame
|
// Function to render frame
|
||||||
void renderFrame(const uint16_t currentStep, const std::string& move)
|
void renderFrame(const size_t stepId)
|
||||||
{
|
{
|
||||||
// Sleeping for the inverse frame rate
|
// Checking the required step id does not exceed contents of the sequence
|
||||||
usleep(_INVERSE_FRAME_RATE);
|
if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence");
|
||||||
|
|
||||||
// Storing current game state
|
// Getting step information
|
||||||
uint8_t emuState[_emu->getStateSize()];
|
const auto& step = _stepSequence[stepId];
|
||||||
_emu->serializeState(emuState);
|
|
||||||
|
|
||||||
// Pointer to overlay images (NULL if unused)
|
// Pointer to overlay images (NULL if unused)
|
||||||
SDL_Surface* overlayButtonASurface = NULL;
|
SDL_Surface* overlayButtonASurface = NULL;
|
||||||
|
@ -130,24 +139,76 @@ class PlaybackInstance
|
||||||
// Load correct overlay images, if using overlay
|
// Load correct overlay images, if using overlay
|
||||||
if (_useOverlay == true)
|
if (_useOverlay == true)
|
||||||
{
|
{
|
||||||
if (move.find("A") != std::string::npos) overlayButtonASurface = _overlayButtonASurface;
|
if (step.input.find("A") != std::string::npos) overlayButtonASurface = _overlayButtonASurface;
|
||||||
if (move.find("B") != std::string::npos) overlayButtonBSurface = _overlayButtonBSurface;
|
if (step.input.find("B") != std::string::npos) overlayButtonBSurface = _overlayButtonBSurface;
|
||||||
if (move.find("S") != std::string::npos) overlayButtonSelectSurface = _overlayButtonSelectSurface;
|
if (step.input.find("S") != std::string::npos) overlayButtonSelectSurface = _overlayButtonSelectSurface;
|
||||||
if (move.find("T") != std::string::npos) overlayButtonStartSurface = _overlayButtonStartSurface;
|
if (step.input.find("T") != std::string::npos) overlayButtonStartSurface = _overlayButtonStartSurface;
|
||||||
if (move.find("L") != std::string::npos) overlayButtonLeftSurface = _overlayButtonLeftSurface;
|
if (step.input.find("L") != std::string::npos) overlayButtonLeftSurface = _overlayButtonLeftSurface;
|
||||||
if (move.find("R") != std::string::npos) overlayButtonRightSurface = _overlayButtonRightSurface;
|
if (step.input.find("R") != std::string::npos) overlayButtonRightSurface = _overlayButtonRightSurface;
|
||||||
if (move.find("U") != std::string::npos) overlayButtonUpSurface = _overlayButtonUpSurface;
|
if (step.input.find("U") != std::string::npos) overlayButtonUpSurface = _overlayButtonUpSurface;
|
||||||
if (move.find("D") != std::string::npos) overlayButtonDownSurface = _overlayButtonDownSurface;
|
if (step.input.find("D") != std::string::npos) overlayButtonDownSurface = _overlayButtonDownSurface;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since renderer is off by one frame, we need to emulate an additional frame
|
// Updating image
|
||||||
_emu->advanceState(0,0);
|
_hqnGUI->update_blit(step.curBlit, _overlayBaseSurface, overlayButtonASurface, overlayButtonBSurface, overlayButtonSelectSurface, overlayButtonStartSurface, overlayButtonLeftSurface, overlayButtonRightSurface, overlayButtonUpSurface, overlayButtonDownSurface);
|
||||||
int32_t curImage[BLIT_SIZE];
|
|
||||||
_hqnState.blit(curImage, hqn::HQNState::NES_VIDEO_PALETTE, 0, 0, 0, 0);
|
|
||||||
_hqnGUI->update_blit(curImage, _overlayBaseSurface, overlayButtonASurface, overlayButtonBSurface, overlayButtonSelectSurface, overlayButtonStartSurface, overlayButtonLeftSurface, overlayButtonRightSurface, overlayButtonUpSurface, overlayButtonDownSurface);
|
|
||||||
|
|
||||||
// Reload game state
|
|
||||||
_emu->deserializeState(emuState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t getSequenceLength() const
|
||||||
|
{
|
||||||
|
return _stepSequence.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string getInput(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* getStateData(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.stateData;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
EmuInstance* const _emu;
|
||||||
|
|
||||||
|
// Flag to store whether to use the button overlay
|
||||||
|
bool _useOverlay = false;
|
||||||
|
|
||||||
|
// 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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -50,17 +50,105 @@ int main(int argc, char *argv[])
|
||||||
// Getting reproduce flag
|
// Getting reproduce flag
|
||||||
bool disableRender = program.get<bool>("--disableRender");
|
bool disableRender = program.get<bool>("--disableRender");
|
||||||
|
|
||||||
|
// Loading sequence file
|
||||||
|
std::string inputSequence;
|
||||||
|
auto status = loadStringFromFile(inputSequence, sequenceFilePath.c_str());
|
||||||
|
if (status == false) EXIT_WITH_ERROR("[ERROR] Could not find or read from sequence file: %s\n", sequenceFilePath.c_str());
|
||||||
|
|
||||||
|
// Initializing terminal
|
||||||
|
initializeTerminal();
|
||||||
|
|
||||||
// Printing provided parameters
|
// Printing provided parameters
|
||||||
printf("Rom File Path: %s\n", romFilePath.c_str());
|
printw("[] Rom File Path: '%s'\n", romFilePath.c_str());
|
||||||
printf("Sequence File Path: %s\n", sequenceFilePath.c_str());
|
printw("[] Sequence File Path: '%s'\n", sequenceFilePath.c_str());
|
||||||
printf("State File Path: %s\n", stateFilePath.c_str());
|
printw("[] State File Path: '%s'\n", stateFilePath.empty() ? "<Boot Start>" : stateFilePath.c_str());
|
||||||
|
printw("[] Generating Sequence...\n");
|
||||||
|
|
||||||
|
refreshTerminal();
|
||||||
|
|
||||||
// Creating emulator instance
|
// Creating emulator instance
|
||||||
auto e = EmuInstance(romFilePath, stateFilePath);
|
auto e = EmuInstance(romFilePath, stateFilePath);
|
||||||
|
|
||||||
// Creating playback instance
|
// Creating playback instance
|
||||||
auto p = PlaybackInstance(&e);
|
auto p = PlaybackInstance(&e, inputSequence);
|
||||||
|
|
||||||
while(true) p.renderFrame(0, "");
|
// Flag to continue running playback
|
||||||
|
bool continueRunning = true;
|
||||||
|
|
||||||
|
// Variable for current step in view
|
||||||
|
ssize_t sequenceLength = p.getSequenceLength();
|
||||||
|
ssize_t currentStep = 0;
|
||||||
|
|
||||||
|
// Flag to display frame information
|
||||||
|
bool showFrameInfo = true;
|
||||||
|
|
||||||
|
// Interactive section
|
||||||
|
while(continueRunning)
|
||||||
|
{
|
||||||
|
// Updating display
|
||||||
|
if (disableRender == false) p.renderFrame(currentStep);
|
||||||
|
|
||||||
|
// Getting input
|
||||||
|
const auto& input = p.getInput(currentStep);
|
||||||
|
|
||||||
|
// Getting state data
|
||||||
|
//const auto stateData = p.getStateData(currentStep);
|
||||||
|
|
||||||
|
// Printing data and commands
|
||||||
|
if (showFrameInfo)
|
||||||
|
{
|
||||||
|
clearTerminal();
|
||||||
|
|
||||||
|
printw("[] ----------------------------------------------------------------\n");
|
||||||
|
printw("[] Current Step #: %lu / %lu\n", currentStep + 1, sequenceLength);
|
||||||
|
printw("[] Input: %s\n", input.c_str());
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
|
||||||
|
refreshTerminal();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resetting show frame info flag
|
||||||
|
showFrameInfo = true;
|
||||||
|
|
||||||
|
// Get command
|
||||||
|
auto command = getKeyPress();
|
||||||
|
|
||||||
|
// Advance/Rewind commands
|
||||||
|
if (command == 'n') currentStep = currentStep - 1;
|
||||||
|
if (command == 'm') currentStep = currentStep + 1;
|
||||||
|
if (command == 'h') currentStep = currentStep - 10;
|
||||||
|
if (command == 'j') currentStep = currentStep + 10;
|
||||||
|
if (command == 'y') currentStep = currentStep - 100;
|
||||||
|
if (command == 'u') currentStep = currentStep + 100;
|
||||||
|
if (command == 'k') currentStep = currentStep - 1000;
|
||||||
|
if (command == 'i') currentStep = currentStep + 1000;
|
||||||
|
|
||||||
|
// Correct current step if requested more than possible
|
||||||
|
if (currentStep < 0) currentStep = 0;
|
||||||
|
if (currentStep >= sequenceLength) currentStep = sequenceLength-1;
|
||||||
|
|
||||||
|
// Quicksave creation command
|
||||||
|
if (command == 's')
|
||||||
|
{
|
||||||
|
// Storing state file
|
||||||
|
std::string saveFileName = "quicksave.state";
|
||||||
|
e.saveStateFile(saveFileName);
|
||||||
|
printw("[] Saved state to %s\n", saveFileName.c_str());
|
||||||
|
|
||||||
|
// Do no show frame info again after this action
|
||||||
|
showFrameInfo = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start playback from current point
|
||||||
|
if (command == 'p') isReproduce = true;
|
||||||
|
|
||||||
|
// Start playback from current point
|
||||||
|
if (command == 'q') continueRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ending ncurses window
|
||||||
|
finalizeTerminal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
// If we use NCurses, we need to use the appropriate printing function
|
// If we use NCurses, we need to use the appropriate printing function
|
||||||
|
@ -56,7 +57,33 @@ inline int getKeyPress()
|
||||||
return getch();
|
return getch();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
void initializeTerminal()
|
||||||
|
{
|
||||||
|
// Initializing ncurses screen
|
||||||
|
initscr();
|
||||||
|
cbreak();
|
||||||
|
noecho();
|
||||||
|
nodelay(stdscr, TRUE);
|
||||||
|
scrollok(stdscr, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearTerminal()
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void finalizeTerminal()
|
||||||
|
{
|
||||||
|
endwin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void refreshTerminal()
|
||||||
|
{
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif // NCURSES
|
||||||
|
|
||||||
// Function to split a string into a sub-strings delimited by a character
|
// 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
|
// Taken from stack overflow answer to https://stackoverflow.com/questions/236129/how-do-i-iterate-over-the-words-of-a-string
|
||||||
|
|
Loading…
Reference in New Issue