2024-02-08 18:19:39 +00:00
# include <cstdlib>
2024-03-06 15:21:04 +00:00
# include "argparse/argparse.hpp"
# include "jaffarCommon/serializers/contiguous.hpp"
# include "jaffarCommon/deserializers/contiguous.hpp"
# include "jaffarCommon/file.hpp"
# include "jaffarCommon/logger.hpp"
# include "jaffarCommon/string.hpp"
2024-01-28 15:02:58 +00:00
# include "nesInstance.hpp"
2024-01-10 19:11:49 +00:00
# include "playbackInstance.hpp"
2024-01-09 19:12:22 +00:00
2024-03-22 11:50:46 +00:00
SDL_Window * launchOutputWindow ( )
{
// Opening rendering window
SDL_SetMainReady ( ) ;
// We can only call SDL_InitSubSystem once
if ( ! SDL_WasInit ( SDL_INIT_VIDEO ) )
if ( SDL_InitSubSystem ( SDL_INIT_VIDEO ) ! = 0 ) JAFFAR_THROW_LOGIC ( " Failed to initialize video: %s " , SDL_GetError ( ) ) ;
auto window = SDL_CreateWindow ( " JaffarPlus " , SDL_WINDOWPOS_UNDEFINED , SDL_WINDOWPOS_UNDEFINED , 100 , 100 , SDL_WINDOW_RESIZABLE ) ;
if ( window = = nullptr ) JAFFAR_THROW_LOGIC ( " Coult not open SDL window " ) ;
return window ;
}
void closeOutputWindow ( SDL_Window * window ) { SDL_DestroyWindow ( window ) ; }
2024-01-09 19:12:22 +00:00
int main ( int argc , char * argv [ ] )
{
// Parsing command line arguments
argparse : : ArgumentParser program ( " player " , " 1.0 " ) ;
program . add_argument ( " romFile " )
2024-01-09 19:59:59 +00:00
. help ( " Path to the rom file to run. " )
2024-01-09 19:12:22 +00:00
. required ( ) ;
2024-01-09 19:59:59 +00:00
program . add_argument ( " sequenceFile " )
. help ( " Path to the input sequence file (.sol) to reproduce. " )
2024-01-09 19:12:22 +00:00
. required ( ) ;
program . add_argument ( " stateFile " )
2024-01-20 10:21:34 +00:00
. help ( " (Optional) Path to the initial state file to load. " )
. default_value ( std : : string ( " " ) ) ;
2024-01-09 19:12:22 +00:00
program . add_argument ( " --reproduce " )
. help ( " Plays the entire sequence without interruptions and exit at the end. " )
. default_value ( false )
. implicit_value ( true ) ;
program . add_argument ( " --disableRender " )
. help ( " Do not render game window. " )
. default_value ( false )
. implicit_value ( true ) ;
2024-01-21 14:34:15 +00:00
program . add_argument ( " --controller1 " )
. help ( " Specifies the controller 1 type. " )
. default_value ( std : : string ( " Joypad " ) ) ;
program . add_argument ( " --controller2 " )
. help ( " Specifies the controller 2 type. " )
. default_value ( std : : string ( " None " ) ) ;
2024-01-09 19:12:22 +00:00
// Try to parse arguments
2024-01-20 10:21:34 +00:00
try
{
program . parse_args ( argc , argv ) ;
}
catch ( const std : : runtime_error & err )
{
2024-03-06 15:21:04 +00:00
JAFFAR_THROW_LOGIC ( " %s \n %s " , err . what ( ) , program . help ( ) . str ( ) . c_str ( ) ) ;
2024-01-20 10:21:34 +00:00
}
2024-01-09 19:59:59 +00:00
// Getting ROM file path
std : : string romFilePath = program . get < std : : string > ( " romFile " ) ;
// Getting sequence file path
2024-01-10 19:11:49 +00:00
std : : string sequenceFilePath = program . get < std : : string > ( " sequenceFile " ) ;
2024-01-09 19:59:59 +00:00
// If initial state file is specified, load it
2024-01-10 19:11:49 +00:00
std : : string stateFilePath = program . get < std : : string > ( " stateFile " ) ;
2024-01-09 19:59:59 +00:00
// Getting reproduce flag
bool isReproduce = program . get < bool > ( " --reproduce " ) ;
// Getting reproduce flag
bool disableRender = program . get < bool > ( " --disableRender " ) ;
2024-01-10 19:11:49 +00:00
2024-01-21 14:34:15 +00:00
// Getting controller 1 Type
std : : string controller1Type = program . get < std : : string > ( " --controller1 " ) ;
// Getting controller 2 Type
std : : string controller2Type = program . get < std : : string > ( " --controller2 " ) ;
2024-01-11 18:44:54 +00:00
// Loading sequence file
std : : string inputSequence ;
2024-03-06 15:21:04 +00:00
auto status = jaffarCommon : : file : : loadStringFromFile ( inputSequence , sequenceFilePath . c_str ( ) ) ;
if ( status = = false ) JAFFAR_THROW_LOGIC ( " [ERROR] Could not find or read from sequence file: %s \n " , sequenceFilePath . c_str ( ) ) ;
2024-01-11 18:44:54 +00:00
2024-01-11 19:23:32 +00:00
// Building sequence information
2024-03-06 15:21:04 +00:00
const auto sequence = jaffarCommon : : string : : split ( inputSequence , ' ' ) ;
2024-01-11 19:23:32 +00:00
2024-01-11 18:44:54 +00:00
// Initializing terminal
2024-03-06 15:21:04 +00:00
jaffarCommon : : logger : : initializeTerminal ( ) ;
2024-01-11 18:44:54 +00:00
2024-01-10 19:11:49 +00:00
// Printing provided parameters
2024-03-22 11:50:46 +00:00
jaffarCommon : : logger : : log ( " [] Rom File Path: '%s' \n " , romFilePath . c_str ( ) ) ;
jaffarCommon : : logger : : log ( " [] Sequence File Path: '%s' \n " , sequenceFilePath . c_str ( ) ) ;
jaffarCommon : : logger : : log ( " [] Sequence Length: %lu \n " , sequence . size ( ) ) ;
jaffarCommon : : logger : : log ( " [] State File Path: '%s' \n " , stateFilePath . empty ( ) ? " <Boot Start> " : stateFilePath . c_str ( ) ) ;
jaffarCommon : : logger : : log ( " [] Generating Sequence... \n " ) ;
2024-01-11 18:44:54 +00:00
2024-03-06 15:21:04 +00:00
jaffarCommon : : logger : : refreshTerminal ( ) ;
2024-01-10 19:11:49 +00:00
2024-01-22 17:50:52 +00:00
// Creating emulator instance
2024-01-28 15:02:58 +00:00
NESInstance e ;
2024-01-10 19:11:49 +00:00
2024-01-21 14:34:15 +00:00
// Setting controller types
e . setController1Type ( controller1Type ) ;
e . setController2Type ( controller2Type ) ;
2024-01-15 21:19:29 +00:00
// Loading ROM File
2024-01-28 15:02:58 +00:00
std : : string romFileData ;
2024-03-06 15:21:04 +00:00
if ( jaffarCommon : : file : : loadStringFromFile ( romFileData , romFilePath ) = = false ) JAFFAR_THROW_LOGIC ( " Could not rom file: %s \n " , romFilePath . c_str ( ) ) ;
2024-01-28 15:02:58 +00:00
e . loadROM ( ( uint8_t * ) romFileData . data ( ) , romFileData . size ( ) ) ;
2024-01-15 21:19:29 +00:00
// If an initial state is provided, load it now
2024-01-28 15:02:58 +00:00
if ( stateFilePath ! = " " )
{
std : : string stateFileData ;
2024-03-06 15:21:04 +00:00
if ( jaffarCommon : : file : : loadStringFromFile ( stateFileData , stateFilePath ) = = false ) JAFFAR_THROW_LOGIC ( " Could not initial state file: %s \n " , stateFilePath . c_str ( ) ) ;
2024-02-09 19:43:42 +00:00
jaffarCommon : : deserializer : : Contiguous deserializer ( stateFileData . data ( ) ) ;
e . deserializeState ( deserializer ) ;
2024-01-28 15:02:58 +00:00
}
2024-01-15 21:19:29 +00:00
2024-01-10 19:11:49 +00:00
// Creating playback instance
2024-03-22 11:50:46 +00:00
auto p = PlaybackInstance ( & e ) ;
// If render is enabled then, create window now
SDL_Window * window = nullptr ;
if ( disableRender = = false )
{
window = launchOutputWindow ( ) ;
p . enableRendering ( window ) ;
}
// Initializing playback instance
p . initialize ( sequence ) ;
2024-01-11 18:44:54 +00:00
2024-01-12 20:13:51 +00:00
// Getting state size
2024-02-09 19:43:42 +00:00
auto stateSize = e . getFullStateSize ( ) ;
2024-01-12 20:13:51 +00:00
2024-01-11 18:44:54 +00:00
// Flag to continue running playback
bool continueRunning = true ;
// Variable for current step in view
2024-01-12 20:13:51 +00:00
ssize_t sequenceLength = p . getSequenceLength ( ) ;
2024-01-11 18:44:54 +00:00
ssize_t currentStep = 0 ;
// Flag to display frame information
bool showFrameInfo = true ;
// Interactive section
2024-01-20 10:21:34 +00:00
while ( continueRunning )
2024-01-11 18:44:54 +00:00
{
// Updating display
if ( disableRender = = false ) p . renderFrame ( currentStep ) ;
// Getting input
2024-01-20 10:21:34 +00:00
const auto & input = p . getStateInput ( currentStep ) ;
2024-01-12 20:13:51 +00:00
// Getting state hash
const auto hash = p . getStateHash ( currentStep ) ;
2024-01-11 18:44:54 +00:00
// Getting state data
2024-01-12 20:13:51 +00:00
const auto stateData = p . getStateData ( currentStep ) ;
2024-01-11 18:44:54 +00:00
// Printing data and commands
if ( showFrameInfo )
{
2024-03-06 15:21:04 +00:00
jaffarCommon : : logger : : clearTerminal ( ) ;
2024-01-11 18:44:54 +00:00
2024-03-22 11:50:46 +00:00
jaffarCommon : : logger : : log ( " [] ---------------------------------------------------------------- \n " ) ;
jaffarCommon : : logger : : log ( " [] Current Step #: %lu / %lu \n " , currentStep + 1 , sequenceLength ) ;
jaffarCommon : : logger : : log ( " [] Input: %s \n " , input . c_str ( ) ) ;
jaffarCommon : : logger : : log ( " [] State Hash: 0x%lX%lX \n " , hash . first , hash . second ) ;
2024-01-11 18:44:54 +00:00
// Only print commands if not in reproduce mode
2024-03-22 11:50:46 +00:00
if ( isReproduce = = false ) jaffarCommon : : logger : : log ( " [] Commands: n: -1 m: +1 | h: -10 | j: +10 | y: -100 | u: +100 | k: -1000 | i: +1000 | s: quicksave | p: play | q: quit \n " ) ;
2024-01-11 18:44:54 +00:00
2024-03-06 15:21:04 +00:00
jaffarCommon : : logger : : refreshTerminal ( ) ;
2024-01-11 18:44:54 +00:00
}
// Resetting show frame info flag
showFrameInfo = true ;
// Get command
2024-03-06 15:21:04 +00:00
auto command = jaffarCommon : : logger : : waitForKeyPress ( ) ;
2024-01-11 18:44:54 +00:00
// 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 ;
2024-01-20 10:21:34 +00:00
if ( currentStep > = sequenceLength ) currentStep = sequenceLength - 1 ;
2024-01-11 18:44:54 +00:00
// Quicksave creation command
if ( command = = ' s ' )
{
2024-01-20 10:21:34 +00:00
// Storing state file
2024-01-11 18:44:54 +00:00
std : : string saveFileName = " quicksave.state " ;
2024-01-12 20:13:51 +00:00
std : : string saveData ;
saveData . resize ( stateSize ) ;
memcpy ( saveData . data ( ) , stateData , stateSize ) ;
2024-03-06 15:21:04 +00:00
if ( jaffarCommon : : file : : saveStringToFile ( saveData , saveFileName . c_str ( ) ) = = false ) JAFFAR_THROW_LOGIC ( " [ERROR] Could not save state file: %s \n " , saveFileName . c_str ( ) ) ;
2024-03-22 11:50:46 +00:00
jaffarCommon : : logger : : log ( " [] Saved state to %s \n " , saveFileName . c_str ( ) ) ;
2024-01-11 18:44:54 +00:00
// 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 ;
}
2024-03-22 11:50:46 +00:00
// If render is enabled then, close window now
if ( disableRender = = false ) closeOutputWindow ( window ) ;
2024-01-11 18:44:54 +00:00
// Ending ncurses window
2024-03-06 15:21:04 +00:00
jaffarCommon : : logger : : finalizeTerminal ( ) ;
2024-01-09 19:12:22 +00:00
}