2024-02-08 18:19:39 +00:00
# include <jaffarCommon/extern/argparse/argparse.hpp>
# include <jaffarCommon/include/json.hpp>
2024-02-09 19:43:42 +00:00
# include <jaffarCommon/include/serializers/contiguous.hpp>
# include <jaffarCommon/include/serializers/differential.hpp>
# include <jaffarCommon/include/deserializers/contiguous.hpp>
# include <jaffarCommon/include/deserializers/differential.hpp>
2024-02-08 18:19:39 +00:00
# include <jaffarCommon/include/hash.hpp>
# include <jaffarCommon/include/string.hpp>
# include <jaffarCommon/include/file.hpp>
2024-01-28 15:02:58 +00:00
# include "nesInstance.hpp"
2024-01-20 10:21:34 +00:00
# include <chrono>
# include <sstream>
2024-01-20 15:48:38 +00:00
# include <vector>
# include <string>
2024-01-15 19:56:58 +00:00
2024-01-12 18:14:57 +00:00
int main ( int argc , char * argv [ ] )
{
2024-01-20 10:21:34 +00:00
// Parsing command line arguments
2024-01-16 21:22:36 +00:00
argparse : : ArgumentParser program ( " tester " , " 1.0 " ) ;
2024-01-12 18:14:57 +00:00
program . add_argument ( " scriptFile " )
. help ( " Path to the test script file to run. " )
. required ( ) ;
2024-01-19 16:03:47 +00:00
program . add_argument ( " --cycleType " )
. help ( " Specifies the emulation actions to be performed per each input. Possible values: 'Simple': performs only advance state, 'Rerecord': performs load/advance/save, and 'Full': performs load/advance/save/advance. " )
. default_value ( std : : string ( " Simple " ) ) ;
2024-01-16 21:22:36 +00:00
program . add_argument ( " --hashOutputFile " )
. help ( " Path to write the hash output to. " )
. default_value ( std : : string ( " " ) ) ;
2024-01-12 18:14:57 +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 )
{
EXIT_WITH_ERROR ( " %s \n %s " , err . what ( ) , program . help ( ) . str ( ) . c_str ( ) ) ;
}
2024-01-12 18:14:57 +00:00
// Getting test script file path
std : : string scriptFilePath = program . get < std : : string > ( " scriptFile " ) ;
2024-01-16 21:22:36 +00:00
// Getting path where to save the hash output (if any)
std : : string hashOutputFile = program . get < std : : string > ( " --hashOutputFile " ) ;
// Getting reproduce flag
2024-01-19 16:03:47 +00:00
std : : string cycleType = program . get < std : : string > ( " --cycleType " ) ;
2024-01-16 21:22:36 +00:00
2024-01-12 18:14:57 +00:00
// Loading script file
std : : string scriptJsonRaw ;
2024-02-08 18:19:39 +00:00
if ( jaffarCommon : : loadStringFromFile ( scriptJsonRaw , scriptFilePath ) = = false ) EXIT_WITH_ERROR ( " Could not find/read script file: %s \n " , scriptFilePath . c_str ( ) ) ;
2024-01-12 18:14:57 +00:00
2024-01-20 10:21:34 +00:00
// Parsing script
2024-01-12 18:14:57 +00:00
const auto scriptJson = nlohmann : : json : : parse ( scriptJsonRaw ) ;
// Getting rom file path
if ( scriptJson . contains ( " Rom File " ) = = false ) EXIT_WITH_ERROR ( " Script file missing 'Rom File' entry \n " ) ;
if ( scriptJson [ " Rom File " ] . is_string ( ) = = false ) EXIT_WITH_ERROR ( " Script file 'Rom File' entry is not a string \n " ) ;
std : : string romFilePath = scriptJson [ " Rom File " ] . get < std : : string > ( ) ;
// Getting initial state file path
if ( scriptJson . contains ( " Initial State File " ) = = false ) EXIT_WITH_ERROR ( " Script file missing 'Initial State File' entry \n " ) ;
if ( scriptJson [ " Initial State File " ] . is_string ( ) = = false ) EXIT_WITH_ERROR ( " Script file 'Initial State File' entry is not a string \n " ) ;
std : : string initialStateFilePath = scriptJson [ " Initial State File " ] . get < std : : string > ( ) ;
// Getting sequence file path
if ( scriptJson . contains ( " Sequence File " ) = = false ) EXIT_WITH_ERROR ( " Script file missing 'Sequence File' entry \n " ) ;
if ( scriptJson [ " Sequence File " ] . is_string ( ) = = false ) EXIT_WITH_ERROR ( " Script file 'Sequence File' entry is not a string \n " ) ;
std : : string sequenceFilePath = scriptJson [ " Sequence File " ] . get < std : : string > ( ) ;
// Getting expected ROM SHA1 hash
if ( scriptJson . contains ( " Expected ROM SHA1 " ) = = false ) EXIT_WITH_ERROR ( " Script file missing 'Expected ROM SHA1' entry \n " ) ;
if ( scriptJson [ " Expected ROM SHA1 " ] . is_string ( ) = = false ) EXIT_WITH_ERROR ( " Script file 'Expected ROM SHA1' entry is not a string \n " ) ;
std : : string expectedROMSHA1 = scriptJson [ " Expected ROM SHA1 " ] . get < std : : string > ( ) ;
2024-01-20 15:48:38 +00:00
// Parsing disabled blocks in lite state serialization
2024-01-20 17:30:24 +00:00
std : : vector < std : : string > stateDisabledBlocks ;
std : : string stateDisabledBlocksOutput ;
if ( scriptJson . contains ( " Disable State Blocks " ) = = false ) EXIT_WITH_ERROR ( " Script file missing 'Disable State Blocks' entry \n " ) ;
if ( scriptJson [ " Disable State Blocks " ] . is_array ( ) = = false ) EXIT_WITH_ERROR ( " Script file 'Disable State Blocks' is not an array \n " ) ;
for ( const auto & entry : scriptJson [ " Disable State Blocks " ] )
2024-01-20 15:48:38 +00:00
{
2024-01-20 17:30:24 +00:00
if ( entry . is_string ( ) = = false ) EXIT_WITH_ERROR ( " Script file 'Disable State Blocks' entry is not a string \n " ) ;
stateDisabledBlocks . push_back ( entry . get < std : : string > ( ) ) ;
stateDisabledBlocksOutput + = entry . get < std : : string > ( ) + std : : string ( " " ) ;
2024-01-20 15:48:38 +00:00
}
2024-01-21 14:34:15 +00:00
// Getting Controller 1 type
if ( scriptJson . contains ( " Controller 1 Type " ) = = false ) EXIT_WITH_ERROR ( " Script file missing 'Controller 1 Type' entry \n " ) ;
if ( scriptJson [ " Controller 1 Type " ] . is_string ( ) = = false ) EXIT_WITH_ERROR ( " Script file 'Controller 1 Type' entry is not a string \n " ) ;
std : : string controller1Type = scriptJson [ " Controller 1 Type " ] . get < std : : string > ( ) ;
// Getting Controller 2 type
if ( scriptJson . contains ( " Controller 2 Type " ) = = false ) EXIT_WITH_ERROR ( " Script file missing 'Controller 2 Type' entry \n " ) ;
if ( scriptJson [ " Controller 2 Type " ] . is_string ( ) = = false ) EXIT_WITH_ERROR ( " Script file 'Controller 2 Type' entry is not a string \n " ) ;
std : : string controller2Type = scriptJson [ " Controller 2 Type " ] . get < std : : string > ( ) ;
2024-02-06 16:54:39 +00:00
// Getting differential compression configuration
if ( scriptJson . contains ( " Differential Compression " ) = = false ) EXIT_WITH_ERROR ( " Script file missing 'Differential Compression' entry \n " ) ;
if ( scriptJson [ " Differential Compression " ] . is_object ( ) = = false ) EXIT_WITH_ERROR ( " Script file 'Differential Compression' entry is not a key/value object \n " ) ;
const auto & differentialCompressionJs = scriptJson [ " Differential Compression " ] ;
if ( differentialCompressionJs . contains ( " Enabled " ) = = false ) EXIT_WITH_ERROR ( " Script file missing 'Differential Compression / Enabled' entry \n " ) ;
if ( differentialCompressionJs [ " Enabled " ] . is_boolean ( ) = = false ) EXIT_WITH_ERROR ( " Script file 'Differential Compression / Enabled' entry is not a boolean \n " ) ;
const auto differentialCompressionEnabled = differentialCompressionJs [ " Enabled " ] . get < bool > ( ) ;
if ( differentialCompressionJs . contains ( " Max Differences " ) = = false ) EXIT_WITH_ERROR ( " Script file missing 'Differential Compression / Max Differences' entry \n " ) ;
if ( differentialCompressionJs [ " Max Differences " ] . is_number ( ) = = false ) EXIT_WITH_ERROR ( " Script file 'Differential Compression / Max Differences' entry is not a number \n " ) ;
const auto differentialCompressionMaxDifferences = differentialCompressionJs [ " Max Differences " ] . get < size_t > ( ) ;
if ( differentialCompressionJs . contains ( " Use Zlib " ) = = false ) EXIT_WITH_ERROR ( " Script file missing 'Differential Compression / Use Zlib' entry \n " ) ;
if ( differentialCompressionJs [ " Use Zlib " ] . is_boolean ( ) = = false ) EXIT_WITH_ERROR ( " Script file 'Differential Compression / Use Zlib' entry is not a boolean \n " ) ;
const auto differentialCompressionUseZlib = differentialCompressionJs [ " Use Zlib " ] . get < bool > ( ) ;
2024-01-20 14:26:14 +00:00
// Creating emulator instance
2024-01-28 15:02:58 +00:00
NESInstance e ;
2024-01-15 19:56:58 +00:00
2024-01-21 14:34:15 +00:00
// Setting controller types
e . setController1Type ( controller1Type ) ;
e . setController2Type ( controller2Type ) ;
2024-01-15 19:56:58 +00:00
// Loading ROM File
2024-01-28 15:02:58 +00:00
std : : string romFileData ;
2024-02-08 18:19:39 +00:00
if ( jaffarCommon : : loadStringFromFile ( romFileData , romFilePath ) = = false ) EXIT_WITH_ERROR ( " 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 19:56:58 +00:00
2024-01-28 15:02:58 +00:00
// Calculating ROM SHA1
auto romSHA1 = SHA1 : : GetHash ( ( uint8_t * ) romFileData . data ( ) , romFileData . size ( ) ) ;
2024-01-20 10:21:34 +00:00
2024-01-28 15:02:58 +00:00
// If an initial state is provided, load it now
if ( initialStateFilePath ! = " " )
{
std : : string stateFileData ;
2024-02-08 18:19:39 +00:00
if ( jaffarCommon : : loadStringFromFile ( stateFileData , initialStateFilePath ) = = false ) EXIT_WITH_ERROR ( " Could not initial state file: %s \n " , initialStateFilePath . c_str ( ) ) ;
2024-02-09 19:43:42 +00:00
jaffarCommon : : deserializer : : Contiguous d ( stateFileData . data ( ) ) ;
e . deserializeState ( d ) ;
2024-01-28 15:02:58 +00:00
}
2024-01-28 17:19:23 +00:00
2024-01-27 19:02:05 +00:00
// Disabling requested blocks from state serialization
for ( const auto & block : stateDisabledBlocks ) e . disableStateBlock ( block ) ;
2024-01-13 10:10:42 +00:00
// Disable rendering
2024-01-14 05:12:32 +00:00
e . disableRendering ( ) ;
2024-01-13 10:10:42 +00:00
2024-02-09 19:43:42 +00:00
// Getting full state size
const auto stateSize = e . getFullStateSize ( ) ;
2024-01-18 18:56:30 +00:00
2024-02-06 16:54:39 +00:00
// Getting differential state size
const auto fixedDiferentialStateSize = e . getDifferentialStateSize ( ) ;
const auto fullDifferentialStateSize = fixedDiferentialStateSize + differentialCompressionMaxDifferences ;
2024-01-12 18:14:57 +00:00
// Checking with the expected SHA1 hash
if ( romSHA1 ! = expectedROMSHA1 ) EXIT_WITH_ERROR ( " Wrong ROM SHA1. Found: '%s', Expected: '%s' \n " , romSHA1 . c_str ( ) , expectedROMSHA1 . c_str ( ) ) ;
// Loading sequence file
std : : string sequenceRaw ;
2024-02-08 18:19:39 +00:00
if ( jaffarCommon : : loadStringFromFile ( sequenceRaw , sequenceFilePath ) = = false ) EXIT_WITH_ERROR ( " [ERROR] Could not find or read from input sequence file: %s \n " , sequenceFilePath . c_str ( ) ) ;
2024-01-12 18:14:57 +00:00
// Building sequence information
2024-02-08 18:19:39 +00:00
const auto sequence = jaffarCommon : : split ( sequenceRaw , ' ' ) ;
2024-01-12 18:14:57 +00:00
// Getting sequence lenght
const auto sequenceLength = sequence . size ( ) ;
2024-01-15 19:56:58 +00:00
// Getting emulation core name
std : : string emulationCoreName = e . getCoreName ( ) ;
2024-01-14 10:48:26 +00:00
2024-01-12 18:14:57 +00:00
// Printing test information
2024-01-13 12:35:54 +00:00
printf ( " [] ----------------------------------------- \n " ) ;
2024-02-06 16:54:39 +00:00
printf ( " [] Running Script: '%s' \n " , scriptFilePath . c_str ( ) ) ;
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 ) ;
2024-02-08 18:19:39 +00:00
printf ( " [] State Size: %lu bytes - Disabled Blocks: [ %s ] \n " , stateSize , stateDisabledBlocksOutput . c_str ( ) ) ;
2024-02-06 16:54:39 +00:00
printf ( " [] Use Differential Compression: %s \n " , differentialCompressionEnabled ? " true " : " false " ) ;
if ( differentialCompressionEnabled = = true )
{
printf ( " [] + Max Differences: %lu \n " , differentialCompressionMaxDifferences ) ;
printf ( " [] + Use Zlib: %s \n " , differentialCompressionUseZlib ? " true " : " false " ) ;
printf ( " [] + Fixed Diff State Size: %lu \n " , fixedDiferentialStateSize ) ;
printf ( " [] + Full Diff State Size: %lu \n " , fullDifferentialStateSize ) ;
}
2024-01-13 12:35:54 +00:00
printf ( " [] ********** Running Test ********** \n " ) ;
2024-01-20 10:21:34 +00:00
2024-01-12 18:14:57 +00:00
fflush ( stdout ) ;
2024-01-20 10:21:34 +00:00
2024-01-16 21:22:36 +00:00
// Serializing initial state
2024-02-09 19:43:42 +00:00
auto currentState = ( uint8_t * ) malloc ( stateSize ) ;
{
jaffarCommon : : serializer : : Contiguous cs ( currentState ) ;
e . serializeState ( cs ) ;
}
2024-01-16 21:22:36 +00:00
2024-02-06 16:54:39 +00:00
// Serializing differential state data (in case it's used)
uint8_t * differentialStateData = nullptr ;
size_t differentialStateMaxSizeDetected = 0 ;
2024-02-09 19:43:42 +00:00
// Allocating memory for differential data and performing the first serialization
2024-02-06 16:54:39 +00:00
if ( differentialCompressionEnabled = = true )
{
differentialStateData = ( uint8_t * ) malloc ( fullDifferentialStateSize ) ;
2024-02-09 19:43:42 +00:00
auto s = jaffarCommon : : serializer : : Differential ( differentialStateData , fullDifferentialStateSize , currentState , stateSize , differentialCompressionUseZlib ) ;
e . serializeState ( s ) ;
differentialStateMaxSizeDetected = s . getOutputSize ( ) ;
2024-02-06 16:54:39 +00:00
}
2024-01-19 16:03:47 +00:00
// Check whether to perform each action
bool doPreAdvance = cycleType = = " Full " ;
bool doDeserialize = cycleType = = " Rerecord " | | cycleType = = " Full " ;
bool doSerialize = cycleType = = " Rerecord " | | cycleType = = " Full " ;
2024-01-12 18:14:57 +00:00
// Actually running the sequence
auto t0 = std : : chrono : : high_resolution_clock : : now ( ) ;
2024-01-20 10:21:34 +00:00
for ( const std : : string & input : sequence )
2024-01-16 21:22:36 +00:00
{
2024-01-19 16:03:47 +00:00
if ( doPreAdvance = = true ) e . advanceState ( input ) ;
2024-02-06 16:54:39 +00:00
if ( doDeserialize = = true )
{
2024-02-08 06:44:08 +00:00
if ( differentialCompressionEnabled = = true )
{
2024-02-09 19:43:42 +00:00
jaffarCommon : : deserializer : : Differential d ( differentialStateData , fullDifferentialStateSize , currentState , stateSize , differentialCompressionUseZlib ) ;
e . deserializeState ( d ) ;
2024-02-08 06:44:08 +00:00
}
2024-02-09 19:43:42 +00:00
if ( differentialCompressionEnabled = = false )
{
jaffarCommon : : deserializer : : Contiguous d ( currentState , stateSize ) ;
e . deserializeState ( d ) ;
}
2024-02-06 16:54:39 +00:00
}
2024-01-16 21:22:36 +00:00
e . advanceState ( input ) ;
2024-02-06 16:54:39 +00:00
if ( doSerialize = = true )
{
2024-02-08 06:44:08 +00:00
if ( differentialCompressionEnabled = = true )
{
2024-02-09 19:43:42 +00:00
auto s = jaffarCommon : : serializer : : Differential ( differentialStateData , fullDifferentialStateSize , currentState , stateSize , differentialCompressionUseZlib ) ;
e . serializeState ( s ) ;
differentialStateMaxSizeDetected = std : : max ( differentialStateMaxSizeDetected , s . getOutputSize ( ) ) ;
2024-02-08 06:44:08 +00:00
}
2024-02-09 19:43:42 +00:00
if ( differentialCompressionEnabled = = false )
{
auto s = jaffarCommon : : serializer : : Contiguous ( currentState , stateSize ) ;
e . serializeState ( s ) ;
}
2024-02-06 16:54:39 +00:00
}
2024-01-20 10:21:34 +00:00
}
2024-01-12 18:14:57 +00:00
auto tf = std : : chrono : : high_resolution_clock : : now ( ) ;
// Calculating running time
auto dt = std : : chrono : : duration_cast < std : : chrono : : nanoseconds > ( tf - t0 ) . count ( ) ;
double elapsedTimeSeconds = ( double ) dt * 1.0e-9 ;
2024-01-12 20:13:51 +00:00
// Calculating final state hash
2024-02-08 18:19:39 +00:00
auto result = jaffarCommon : : calculateMetroHash ( e . getLowMem ( ) , e . getLowMemSize ( ) ) ;
2024-01-12 20:13:51 +00:00
2024-01-16 21:22:36 +00:00
// Creating hash string
char hashStringBuffer [ 256 ] ;
2024-01-28 15:02:58 +00:00
sprintf ( hashStringBuffer , " 0x%lX%lX " , result . first , result . second ) ;
2024-01-12 20:13:51 +00:00
// Printing time information
2024-02-06 16:54:39 +00:00
printf ( " [] Elapsed time: %3.3fs \n " , ( double ) dt * 1.0e-9 ) ;
printf ( " [] Performance: %.3f inputs / s \n " , ( double ) sequenceLength / elapsedTimeSeconds ) ;
printf ( " [] Final State Hash: %s \n " , hashStringBuffer ) ;
if ( differentialCompressionEnabled = = true )
{
printf ( " [] Differential State Max Size Detected: %lu \n " , differentialStateMaxSizeDetected ) ;
}
2024-01-16 21:22:36 +00:00
// If saving hash, do it now
2024-02-08 18:19:39 +00:00
if ( hashOutputFile ! = " " ) jaffarCommon : : saveStringToFile ( std : : string ( hashStringBuffer ) , hashOutputFile . c_str ( ) ) ;
2024-01-12 20:13:51 +00:00
2024-01-16 21:22:36 +00:00
// If reached this point, everything ran ok
return 0 ;
2024-01-12 18:14:57 +00:00
}