1448 lines
32 KiB
C
1448 lines
32 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#ifdef WIN32
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#include "types.h"
|
|
#include "endian.h"
|
|
#include "palette.h"
|
|
#include "input.h"
|
|
#include "fceu.h"
|
|
#include "driver.h"
|
|
#include "state.h"
|
|
#include "general.h"
|
|
#include "video.h"
|
|
#include "movie.h"
|
|
|
|
#define MOVIE_MAGIC 0x1a4d4346 // FCM\x1a
|
|
#define MOVIE_VERSION 2 // still at 2 since the format itself is still compatible - to detect which version the movie was made with, check the fceu version stored in the movie header (e.g against FCEU_VERSION_NUMERIC)
|
|
|
|
#define MOVIE_FLAG_NOSYNCHACK (1<<4) // set in newer version, used for old movie compatibility
|
|
|
|
// backwards compat
|
|
static void FCEUI_LoadMovie_v1(char *fname, int _read_only);
|
|
static int FCEUI_MovieGetInfo_v1(const char* fname, MOVIE_INFO* info);
|
|
|
|
extern char FileBase[];
|
|
|
|
/*
|
|
struct MovieHeader
|
|
{
|
|
uint32 magic; // +0
|
|
uint32 version=2; // +4
|
|
uint8 flags[4]; // +8
|
|
uint32 length_frames; // +12
|
|
uint32 rerecord_count; // +16
|
|
uint32 movie_data_size; // +20
|
|
uint32 offset_to_savestate; // +24, should be 4-byte-aligned
|
|
uint32 offset_to_movie_data; // +28, should be 4-byte-aligned
|
|
uint8 md5_of_rom_used[16]; // +32
|
|
uint32 version_of_emu_used // +48
|
|
char name_of_rom_used[] // +52, utf-8, null-terminated
|
|
char metadata[]; // utf-8, null-terminated
|
|
uint8 padding[];
|
|
uint8 savestate[]; // always present, even in a "from reset" recording
|
|
uint8 padding[]; // used for byte-alignment
|
|
uint8 movie_data[];
|
|
}
|
|
*/
|
|
|
|
static int current = 0; // > 0 for recording, < 0 for playback
|
|
static FILE *slots[10]={0};
|
|
static uint8 joop[4];
|
|
static uint32 framets = 0;
|
|
static uint32 frameptr = 0;
|
|
static uint8* moviedata = NULL;
|
|
static uint32 moviedatasize = 0;
|
|
static uint32 firstframeoffset = 0;
|
|
static uint32 savestate_offset = 0;
|
|
static uint32 framecount = 0;
|
|
static uint32 rerecord_count = 0;
|
|
/*static*/ int movie_readonly = 1;
|
|
static uint32 stopframe = 0;
|
|
int frame_display = 0;
|
|
static uint32 last_frame_display = ~0;
|
|
int input_display = 0;
|
|
uint32 cur_input_display = 0;
|
|
static uint32 last_input_display = ~0;
|
|
|
|
int resetDMCacc=0;
|
|
|
|
/* Cache variables used for playback. */
|
|
static uint32 nextts = 0;
|
|
static int32 nextd = 0;
|
|
|
|
SFORMAT FCEUMOV_STATEINFO[]={
|
|
{ joop, 4,"JOOP"},
|
|
{ &framets, 4|FCEUSTATE_RLSB, "FTS "},
|
|
{ &nextts, 4|FCEUSTATE_RLSB, "NXTS"},
|
|
{ &nextd, 4|FCEUSTATE_RLSB, "NXTD"},
|
|
{ &frameptr, 4|FCEUSTATE_RLSB, "FPTR"},
|
|
{ &framecount, 4|FCEUSTATE_RLSB, "FCNT"},
|
|
|
|
{ 0 }
|
|
};
|
|
|
|
static int CurrentMovie = 1;
|
|
static int MovieShow = 0;
|
|
|
|
static int MovieStatus[10];
|
|
|
|
static void DoEncode(int joy, int button, int);
|
|
|
|
int FCEUMOV_GetFrame(void)
|
|
{
|
|
return framecount;
|
|
}
|
|
|
|
int FCEUMOV_IsPlaying(void)
|
|
{
|
|
if(current < 0) return(1);
|
|
else return(0);
|
|
}
|
|
|
|
int FCEUMOV_IsRecording(void)
|
|
{
|
|
if(current > 0) return(1);
|
|
else return(0);
|
|
}
|
|
|
|
int FCEUMOV_ShouldPause(void)
|
|
{
|
|
if(stopframe && framecount == stopframe)
|
|
{
|
|
stopframe = 0; //only pause once!
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int suppressMovieStop=0;
|
|
int movieConvertOffset1=0, movieConvertOffset2=0,movieConvertOK=0,movieSyncHackOn=0;
|
|
|
|
static void StopPlayback(void)
|
|
{
|
|
if(suppressMovieStop)
|
|
return;
|
|
resetDMCacc=movieSyncHackOn=0;
|
|
fclose(slots[-1 - current]);
|
|
current=0;
|
|
FCEU_DispMessage("Movie playback stopped.");
|
|
}
|
|
|
|
void MovieFlushHeader(void)
|
|
{
|
|
if(current <= 0)
|
|
return;// only write header data if recording
|
|
|
|
FILE* fp = slots[current - 1];
|
|
if(fp == 0)
|
|
return;
|
|
|
|
unsigned long loc = ftell(fp);
|
|
fseek(fp, 4, SEEK_SET);
|
|
write32le(MOVIE_VERSION, fp);
|
|
fseek(fp, 12, SEEK_SET);
|
|
write32le(framecount, fp);
|
|
write32le(rerecord_count, fp);
|
|
write32le(frameptr, fp);
|
|
fseek(fp, 32, SEEK_SET);
|
|
fwrite(FCEUGameInfo->MD5, 1, 16, fp); // write ROM checksum
|
|
write32le(FCEU_VERSION_NUMERIC, fp); // write emu version used
|
|
|
|
// write ROM name used
|
|
fseek(fp, 52, SEEK_SET);
|
|
char str[512];
|
|
fgets(str,512,fp);
|
|
str[511]='\0';
|
|
int strdiff=strlen(FileBase)-strlen(str);
|
|
if(strdiff)
|
|
{
|
|
// resize the whole damn movie because the ROM name in the header is of variable length
|
|
int off=52;
|
|
fseek(fp, 52, SEEK_SET);
|
|
do { off++;
|
|
} while(fgetc(fp) && !feof(fp) && !ferror(fp));
|
|
|
|
if(feof(fp) || ferror(fp))
|
|
{
|
|
fseek(fp, loc, SEEK_SET);
|
|
return;
|
|
}
|
|
|
|
fseek(fp, 0, SEEK_END);
|
|
uint32 fsize=ftell(fp)-off;
|
|
char* ctemp=(char*)FCEU_malloc(fsize*sizeof(char)+4);
|
|
if(!ctemp)
|
|
{
|
|
fseek(fp, loc, SEEK_SET);
|
|
return;
|
|
}
|
|
fseek(fp, off, SEEK_SET);
|
|
fread(ctemp, 1,fsize, fp);
|
|
fseek(fp, 52+strlen(FileBase)+1, SEEK_SET);
|
|
int wrote = fwrite(ctemp, fsize,1, fp);
|
|
FCEU_free(ctemp);
|
|
if(!wrote)
|
|
{
|
|
fseek(fp, loc, SEEK_SET);
|
|
return;
|
|
}
|
|
|
|
if(loc >= firstframeoffset)
|
|
loc += strdiff;
|
|
savestate_offset += strdiff;
|
|
firstframeoffset += strdiff;
|
|
fseek(fp, 24, SEEK_SET);
|
|
write32le(savestate_offset, fp);
|
|
write32le(firstframeoffset, fp);
|
|
}
|
|
fseek(fp, 52, SEEK_SET);
|
|
fputs(FileBase, fp);
|
|
fputc('\0', fp);
|
|
|
|
fseek(fp, loc, SEEK_SET);
|
|
}
|
|
|
|
static void StopRecording(void)
|
|
{
|
|
if(suppressMovieStop)
|
|
return;
|
|
resetDMCacc=movieSyncHackOn=0;
|
|
DoEncode(0,0,1); /* Write a dummy timestamp value so that the movie will keep
|
|
"playing" after user input has stopped. */
|
|
// finish header
|
|
MovieFlushHeader();
|
|
|
|
// FIXME: truncate movie to length
|
|
// ftruncate();
|
|
fclose(slots[current - 1]);
|
|
MovieStatus[current - 1] = 1;
|
|
current=0;
|
|
FCEU_DispMessage("Movie recording stopped.");
|
|
}
|
|
|
|
void FCEUI_StopMovie(void)
|
|
{
|
|
if(current < 0) StopPlayback();
|
|
if(current > 0) StopRecording();
|
|
}
|
|
|
|
#ifdef WIN32
|
|
#include "process.h"
|
|
void executeCommand(const char* cmd)
|
|
{
|
|
if(!cmd || !*cmd)
|
|
return;
|
|
|
|
const char *argv[4];
|
|
argv[0] = getenv("COMSPEC");
|
|
argv[1] = "/c";
|
|
argv[2] = cmd;
|
|
argv[3] = NULL;
|
|
if(*argv && *(*argv))
|
|
_spawnve(_P_WAIT, argv[0], argv, NULL);
|
|
}
|
|
#endif
|
|
|
|
int justAutoConverted=0;
|
|
static const char* convertToFCM(const char *fname, char *buffer)
|
|
{
|
|
#ifdef WIN32
|
|
justAutoConverted=0;
|
|
|
|
// convert to fcm if not already
|
|
const char* dot = strrchr(fname, '.');
|
|
if(dot)
|
|
{
|
|
int fmv = !stricmp(dot, ".fmv");
|
|
int nmv = !stricmp(dot, ".nmv");
|
|
int vmv = !stricmp(dot, ".vmv");
|
|
if(fmv || nmv || vmv)
|
|
{
|
|
strcpy(buffer, fname);
|
|
buffer[dot-fname]='\0';
|
|
strcat(buffer,"-autoconverted.fcm");
|
|
|
|
int fceuver=0;
|
|
if(fmv)
|
|
fceuver=1;
|
|
else if(nmv)
|
|
fceuver=2;
|
|
else if(vmv)
|
|
fceuver=3;
|
|
|
|
extern char lastLoadedGameName [2048];
|
|
char cmd [1024], offset[64], romInfo[1024];
|
|
if(movieConvertOK)
|
|
sprintf(romInfo, "-smd5=\"%s\" -sromname=\"%s (MAYBE)\" -s", lastLoadedGameName, FileBase);
|
|
else
|
|
sprintf(romInfo, "-sromname=\"(unknown)\" -s");
|
|
if(movieConvertOffset2) sprintf(offset, "-o %d:%d", movieConvertOffset2,movieConvertOffset1);
|
|
else sprintf(offset, "-o %d", movieConvertOffset1);
|
|
sprintf(cmd, ".\\util\\nesmock\\nesmock.exe %s %s -spal=%c -sfceuver=%d \"%s\" \"%s\" ", offset, romInfo, FCEUI_GetCurrentVidSystem(0,0)?'1':'0', fceuver, fname, buffer);
|
|
// FCEU_PrintError(cmd);
|
|
executeCommand(cmd);
|
|
|
|
FILE* file = FCEUD_UTF8fopen(buffer,"rb");
|
|
if(file)
|
|
{
|
|
fseek(file, 12, SEEK_SET);
|
|
int frames=0;
|
|
read32le(&frames, file);
|
|
if(frames)
|
|
{
|
|
fname = buffer;
|
|
justAutoConverted=1;
|
|
}
|
|
else
|
|
{
|
|
static int errAlready=0;
|
|
if(!errAlready)
|
|
{
|
|
errAlready=1;
|
|
FCEU_PrintError("For some reason, nesmock was unable to create a valid FCM from the given file.\nThe command given was:\n%s\nPerhaps the file specified is not a movie file or contains no input data,\nor perhaps it is a movie file of a version unsupported by nesmock.\n\n(This error message will self-destruct until you restart FCEU.)", cmd);
|
|
}
|
|
}
|
|
fclose(file);
|
|
}
|
|
else
|
|
{
|
|
char str [512];
|
|
str[0] = '\0';
|
|
GetCurrentDirectory(512,str);
|
|
strcat(str, "\\util\\nesmock\\nesmock.exe");
|
|
file = FCEUD_UTF8fopen(str, "rb");
|
|
if(file)
|
|
{
|
|
static int errAlready=0;
|
|
if(!errAlready)
|
|
{
|
|
errAlready=1;
|
|
FCEU_PrintError("For some reason, nesmock was unable to convert the movie to FCM format.\nThe command given was:\n%s\n\n(This error message will self-destruct until you restart FCEU.)", cmd);
|
|
fclose(file);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
static int errAlready=0;
|
|
if(!errAlready)
|
|
{
|
|
errAlready=1;
|
|
FCEU_PrintError("Nesmock not found, so the movie could not be converted to FCM format.\nYou must place nesmock.exe at this location so FCEU can find it:\n%s\n\n(This error message will self-destruct until you restart FCEU.)", str);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
return fname;
|
|
}
|
|
|
|
static void ResetInputTypes()
|
|
{
|
|
#ifdef WIN32
|
|
extern int UsrInputType[3];
|
|
UsrInputType[0] = SI_GAMEPAD;
|
|
UsrInputType[1] = SI_GAMEPAD;
|
|
UsrInputType[2] = SIFC_NONE;
|
|
|
|
ParseGIInput(NULL/*FCEUGameInfo*/);
|
|
extern int cspec, gametype;
|
|
cspec=FCEUGameInfo->cspecial;
|
|
gametype=FCEUGameInfo->type;
|
|
|
|
InitOtherInput();
|
|
#endif
|
|
}
|
|
|
|
char curMovieFilename[512];
|
|
|
|
|
|
// PlayMovie / MoviePlay function
|
|
void FCEUI_LoadMovie(char *fname, int _read_only, int _stopframe)
|
|
{
|
|
char buffer [512];
|
|
fname = (char*)convertToFCM(fname,buffer);
|
|
|
|
FILE *fp;
|
|
char *fn = NULL;
|
|
|
|
FCEUI_StopMovie();
|
|
|
|
if(!fname)
|
|
fname = fn = FCEU_MakeFName(FCEUMKF_MOVIE,CurrentMovie,0);
|
|
|
|
char origname[512];
|
|
strcpy(origname,fname);
|
|
|
|
stopframe = _stopframe;
|
|
|
|
// check movie_readonly
|
|
movie_readonly = _read_only;
|
|
if(access(fname, W_OK))
|
|
movie_readonly = 2;
|
|
|
|
fp = FCEUD_UTF8fopen(fname, (movie_readonly>=2) ? "rb" : "r+b");
|
|
|
|
if(fn)
|
|
{
|
|
free(fn);
|
|
fname = NULL;
|
|
}
|
|
|
|
if(!fp) return;
|
|
|
|
// read header
|
|
|
|
uint32 magic;
|
|
uint32 version;
|
|
uint8 flags[4];
|
|
|
|
read32le(&magic, fp);
|
|
if(magic != MOVIE_MAGIC)
|
|
{
|
|
fclose(fp);
|
|
return;
|
|
}
|
|
//DEBUG_COMPARE_RAM(__LINE__);
|
|
|
|
read32le(&version, fp);
|
|
if(version == 1)
|
|
{
|
|
// attempt to load previous version's format
|
|
fclose(fp);
|
|
FCEUI_LoadMovie_v1(fname, _read_only);
|
|
return;
|
|
}
|
|
else if(version == MOVIE_VERSION)
|
|
{}
|
|
else
|
|
{
|
|
// unsupported version
|
|
fclose(fp);
|
|
return;
|
|
}
|
|
|
|
fread(flags, 1, 4, fp);
|
|
read32le(&framecount, fp);
|
|
read32le(&rerecord_count, fp);
|
|
read32le(&moviedatasize, fp);
|
|
read32le(&savestate_offset, fp);
|
|
read32le(&firstframeoffset, fp);
|
|
if(fseek(fp, savestate_offset, SEEK_SET))
|
|
{
|
|
fclose(fp);
|
|
return;
|
|
}
|
|
|
|
// FCEU_PrintError("flags[0] & MOVIE_FLAG_NOSYNCHACK=%d",flags[0] & MOVIE_FLAG_NOSYNCHACK);
|
|
if(flags[0] & MOVIE_FLAG_NOSYNCHACK)
|
|
movieSyncHackOn=0;
|
|
else
|
|
movieSyncHackOn=1;
|
|
|
|
if(flags[0] & MOVIE_FLAG_PAL)
|
|
{
|
|
FCEUI_SetVidSystem(1);
|
|
}
|
|
else
|
|
{
|
|
FCEUI_SetVidSystem(0);
|
|
}
|
|
|
|
|
|
// fully reload the game to reinitialize everything before playing any movie
|
|
// to try fixing nondeterministic playback of some games
|
|
{
|
|
extern char lastLoadedGameName [2048];
|
|
extern int disableBatteryLoading, suppressAddPowerCommand;
|
|
suppressAddPowerCommand=1;
|
|
suppressMovieStop=1;
|
|
{
|
|
FCEUGI * gi = FCEUI_LoadGame(lastLoadedGameName, 0);
|
|
if(!gi)
|
|
PowerNES();
|
|
}
|
|
suppressMovieStop=0;
|
|
suppressAddPowerCommand=0;
|
|
}
|
|
|
|
if(!FCEUSS_LoadFP(fp,1)) return;
|
|
if(flags[0] & MOVIE_FLAG_PAL)
|
|
{
|
|
FCEUI_SetVidSystem(1);
|
|
}
|
|
else
|
|
{
|
|
FCEUI_SetVidSystem(0);
|
|
}
|
|
ResetInputTypes();
|
|
|
|
fseek(fp, firstframeoffset, SEEK_SET);
|
|
moviedata = (uint8*)realloc(moviedata, moviedatasize);
|
|
fread(moviedata, 1, moviedatasize, fp);
|
|
|
|
framecount = 0; // movies start at frame 0!
|
|
frameptr = 0;
|
|
current = CurrentMovie;
|
|
slots[current] = fp;
|
|
|
|
memset(joop,0,sizeof(joop));
|
|
current = -1 - current;
|
|
framets=0;
|
|
nextts=0;
|
|
nextd = -1;
|
|
|
|
MovieStatus[CurrentMovie] = 1;
|
|
if(!fname)
|
|
FCEUI_SelectMovie(CurrentMovie,1); /* Quick hack to display status. */
|
|
else
|
|
FCEU_DispMessage("Movie playback started.");
|
|
|
|
strcpy(curMovieFilename, origname);
|
|
}
|
|
|
|
void FCEUI_SaveMovie(char *fname, uint8 flags, const char* metadata)
|
|
{
|
|
FILE *fp;
|
|
char *fn;
|
|
int poweron=0;
|
|
uint8 padding[4] = {0,0,0,0};
|
|
int n_padding;
|
|
|
|
FCEUI_StopMovie();
|
|
|
|
char origname[512];
|
|
if(fname)
|
|
{
|
|
fp = FCEUD_UTF8fopen(fname, "wb");
|
|
strcpy(origname,fname);
|
|
}
|
|
else
|
|
{
|
|
fp=FCEUD_UTF8fopen(fn=FCEU_MakeFName(FCEUMKF_MOVIE,CurrentMovie,0),"wb");
|
|
strcpy(origname,fn);
|
|
free(fn);
|
|
}
|
|
|
|
if(!fp) return;
|
|
|
|
// don't need the movieSyncHackOn sync hack for newly recorded movies
|
|
flags |= MOVIE_FLAG_NOSYNCHACK;
|
|
resetDMCacc=movieSyncHackOn=0;
|
|
|
|
// add PAL flag
|
|
if(FCEUI_GetCurrentVidSystem(0,0))
|
|
flags |= MOVIE_FLAG_PAL;
|
|
|
|
if(flags & MOVIE_FLAG_FROM_POWERON)
|
|
{
|
|
poweron=1;
|
|
flags &= ~MOVIE_FLAG_FROM_POWERON;
|
|
flags |= MOVIE_FLAG_FROM_RESET;
|
|
}
|
|
|
|
// write header
|
|
write32le(MOVIE_MAGIC, fp);
|
|
write32le(MOVIE_VERSION, fp);
|
|
fputc(flags, fp);
|
|
fputc(0, fp); // reserved
|
|
fputc(0, fp); // reserved
|
|
fputc(0, fp); // reserved
|
|
write32le(0, fp); // leave room for length frames
|
|
write32le(0, fp); // leave room for rerecord count
|
|
write32le(0, fp); // leave room for movie data size
|
|
write32le(0, fp); // leave room for savestate_offset
|
|
write32le(0, fp); // leave room for offset_to_controller_data
|
|
fwrite(FCEUGameInfo->MD5, 1, 16, fp); // write ROM checksum
|
|
write32le(FCEU_VERSION_NUMERIC, fp); // write emu version used
|
|
fputs(FileBase, fp); // write ROM name used
|
|
fputc(0, fp);
|
|
if(metadata)
|
|
{
|
|
if(strlen(metadata) < MOVIE_MAX_METADATA)
|
|
fputs(metadata, fp);
|
|
else
|
|
fwrite(metadata, 1, MOVIE_MAX_METADATA-1, fp);
|
|
}
|
|
fputc(0, fp);
|
|
|
|
// add padding
|
|
n_padding = (4 - (ftell(fp) & 0x3)) & 0x3;
|
|
fwrite(padding, 1, n_padding, fp);
|
|
|
|
if(flags & MOVIE_FLAG_FROM_RESET)
|
|
{
|
|
if(poweron)
|
|
{
|
|
// make a for-movie-recording power-on clear the game's save data, too
|
|
// (note: FCEU makes a save state immediately after this and that loads that on movie playback)
|
|
extern char lastLoadedGameName [2048];
|
|
extern int disableBatteryLoading, suppressAddPowerCommand;
|
|
suppressAddPowerCommand=1;
|
|
disableBatteryLoading=1;
|
|
suppressMovieStop=1;
|
|
{
|
|
// NOTE: this will NOT write an FCEUNPCMD_POWER into the movie file
|
|
FCEUGI * gi = FCEUI_LoadGame(lastLoadedGameName, 0);
|
|
if(!gi)
|
|
PowerNES(); // and neither will this, if it can even happen
|
|
}
|
|
suppressMovieStop=0;
|
|
disableBatteryLoading=0;
|
|
suppressAddPowerCommand=0;
|
|
}
|
|
}
|
|
|
|
savestate_offset = ftell(fp);
|
|
FCEUSS_SaveFP(fp);
|
|
fseek(fp, 0, SEEK_END);
|
|
|
|
ResetInputTypes();
|
|
|
|
// add padding
|
|
n_padding = (4 - (ftell(fp) & 0x3)) & 0x3;
|
|
fwrite(padding, 1, n_padding, fp);
|
|
|
|
firstframeoffset = ftell(fp);
|
|
|
|
// finish header
|
|
fseek(fp, 24, SEEK_SET); // offset_to_savestate offset
|
|
write32le(savestate_offset, fp);
|
|
write32le(firstframeoffset, fp);
|
|
|
|
fseek(fp, firstframeoffset, SEEK_SET);
|
|
|
|
// set recording flag
|
|
current=CurrentMovie;
|
|
|
|
movie_readonly = 0;
|
|
frameptr = 0;
|
|
framecount = 0;
|
|
rerecord_count = 0;
|
|
slots[current] = fp;
|
|
memset(joop,0,sizeof(joop));
|
|
current++;
|
|
framets=0;
|
|
nextd = -1;
|
|
|
|
// trigger a reset
|
|
if(flags & MOVIE_FLAG_FROM_RESET)
|
|
{
|
|
if(poweron)
|
|
{
|
|
PowerNES(); // NOTE: this will write an FCEUNPCMD_POWER into the movie file
|
|
}
|
|
else
|
|
ResetNES(); // NOTE: this will write an FCEUNPCMD_RESET into the movie file
|
|
}
|
|
if(!fname)
|
|
FCEUI_SelectMovie(CurrentMovie,1); /* Quick hack to display status. */
|
|
else
|
|
FCEU_DispMessage("Movie recording started.");
|
|
|
|
strcpy(curMovieFilename, origname);
|
|
}
|
|
|
|
static void movie_writechar(int c)
|
|
{
|
|
if(frameptr == moviedatasize)
|
|
{
|
|
moviedatasize += 4096;
|
|
moviedata = (uint8*)realloc(moviedata, moviedatasize);
|
|
}
|
|
moviedata[frameptr++] = (uint8)(c & 0xff);
|
|
fputc(c, slots[current - 1]);
|
|
}
|
|
|
|
static int movie_readchar()
|
|
{
|
|
if(frameptr >= moviedatasize)
|
|
{
|
|
return -1;
|
|
}
|
|
return (int)(moviedata[frameptr++]);
|
|
}
|
|
|
|
static void DoEncode(int joy, int button, int dummy)
|
|
{
|
|
uint8 d;
|
|
|
|
d = 0;
|
|
|
|
if(framets >= 65536)
|
|
d = 3 << 5;
|
|
else if(framets >= 256)
|
|
d = 2 << 5;
|
|
else if(framets > 0)
|
|
d = 1 << 5;
|
|
|
|
if(dummy) d|=0x80;
|
|
|
|
d |= joy << 3;
|
|
d |= button;
|
|
|
|
movie_writechar(d);
|
|
//printf("Wr: %02x, %d\n",d,slots[current-1]);
|
|
while(framets)
|
|
{
|
|
movie_writechar(framets & 0xff);
|
|
//printf("Wrts: %02x\n",framets & 0xff);
|
|
framets >>= 8;
|
|
}
|
|
}
|
|
|
|
// TODO: make this function legible! (what are all these magic numbers and weirdly named variables and crazy unexplained loops?)
|
|
void FCEUMOV_AddJoy(uint8 *js, int SkipFlush)
|
|
{
|
|
int x,y;
|
|
|
|
// if(!current) return; // Not playback nor recording.
|
|
|
|
if(current < 0) // Playback
|
|
{
|
|
while(nextts == framets || nextd == -1)
|
|
{
|
|
int tmp,ti;
|
|
uint8 d;
|
|
|
|
if(nextd != -1)
|
|
{
|
|
if(nextd&0x80)
|
|
{
|
|
//puts("Egads");
|
|
FCEU_DoSimpleCommand(nextd&0x1F);
|
|
}
|
|
else
|
|
joop[(nextd >> 3)&0x3] ^= 1 << (nextd&0x7);
|
|
}
|
|
|
|
|
|
tmp = movie_readchar();
|
|
d = tmp;
|
|
|
|
if(tmp < 0)
|
|
{
|
|
StopPlayback();
|
|
memcpy(&cur_input_display,js,4);
|
|
return;
|
|
}
|
|
|
|
nextts = 0;
|
|
tmp >>= 5;
|
|
tmp &= 0x3;
|
|
ti=0;
|
|
|
|
int tmpfix = tmp;
|
|
while(tmp--) { nextts |= movie_readchar() << (ti * 8); ti++; }
|
|
|
|
// This fixes a bug in movies recorded before version 0.98.11
|
|
// It's probably not necessary, but it may keep away CRAZY PEOPLE who recorded
|
|
// movies on <= 0.98.10 and don't work on playback.
|
|
if(tmpfix == 1 && !nextts)
|
|
{nextts |= movie_readchar()<<8; }
|
|
else if(tmpfix == 2 && !nextts) {nextts |= movie_readchar()<<16; }
|
|
|
|
if(nextd != -1)
|
|
framets = 0;
|
|
nextd = d;
|
|
}
|
|
|
|
memcpy(js,joop,4);
|
|
}
|
|
else if(current > 0) // Recording
|
|
{
|
|
// flush header info every 300 frames in case of crash
|
|
//Don't if in bot mode--we don't need extra overhead
|
|
if(!SkipFlush)
|
|
{
|
|
static int fcounter=0;
|
|
fcounter++;
|
|
if(!(fcounter%300))
|
|
MovieFlushHeader();
|
|
}
|
|
|
|
for(x=0;x<4;x++)
|
|
{
|
|
if(js[x] != joop[x])
|
|
{
|
|
for(y=0;y<8;y++)
|
|
if((js[x] ^ joop[x]) & (1 << y))
|
|
DoEncode(x, y, 0);
|
|
joop[x] = js[x];
|
|
}
|
|
else if(framets == ((1<<24)-1)) DoEncode(0,0,1); // Overflow will happen, so do dummy update.
|
|
}
|
|
}
|
|
|
|
if(current)
|
|
{
|
|
framets++;
|
|
framecount++;
|
|
}
|
|
|
|
memcpy(&cur_input_display,js,4);
|
|
|
|
//Stop the movie at a specified frame
|
|
if(current < 0 && FCEUMOV_ShouldPause() && FCEUI_EmulationPaused()==0)
|
|
{
|
|
FCEUI_ToggleEmulationPause();
|
|
}
|
|
|
|
}
|
|
|
|
void FCEUMOV_AddCommand(int cmd)
|
|
{
|
|
if(current <= 0) return; // Return if not recording a movie
|
|
//printf("%d\n",cmd);
|
|
DoEncode((cmd>>3)&0x3,cmd&0x7,1);
|
|
}
|
|
|
|
void FCEUMOV_CheckMovies(void)
|
|
{
|
|
FILE *st=NULL;
|
|
char *fn;
|
|
int ssel;
|
|
|
|
for(ssel=0;ssel<10;ssel++)
|
|
{
|
|
st=FCEUD_UTF8fopen(fn=FCEU_MakeFName(FCEUMKF_MOVIE,ssel,0),"rb");
|
|
free(fn);
|
|
if(st)
|
|
{
|
|
MovieStatus[ssel]=1;
|
|
fclose(st);
|
|
}
|
|
else
|
|
MovieStatus[ssel]=0;
|
|
}
|
|
|
|
}
|
|
|
|
void FCEUI_SelectMovieNext(int n)
|
|
{
|
|
if(n>0)
|
|
CurrentMovie=(CurrentMovie+1)%10;
|
|
else
|
|
CurrentMovie=(CurrentMovie+9)%10;
|
|
FCEUI_SelectMovie(CurrentMovie, 1);
|
|
}
|
|
|
|
|
|
int FCEUI_SelectMovie(int w, int show)
|
|
{
|
|
int oldslot=CurrentMovie;
|
|
if(w == -1) { MovieShow = 0; return; }
|
|
FCEUI_SelectState(-1,0);
|
|
|
|
CurrentMovie=w;
|
|
MovieShow=180;
|
|
|
|
if(show)
|
|
{
|
|
MovieShow=180;
|
|
if(current > 0)
|
|
FCEU_DispMessage("-recording movie %d-",current-1);
|
|
else if (current < 0)
|
|
FCEU_DispMessage("-playing movie %d-",-1 - current);
|
|
else
|
|
FCEU_DispMessage("-select movie-");
|
|
}
|
|
}
|
|
|
|
int movcounter=0;
|
|
|
|
void FCEU_DrawMovies(uint8 *XBuf)
|
|
{
|
|
int frameDisplayOn = current != 0 && frame_display;
|
|
extern int howlong;
|
|
#if WIN32
|
|
extern int32 fps_scale;
|
|
#else
|
|
int32 fps_scale=256;
|
|
#endif
|
|
int howl=(180-(FCEUI_EmulationPaused()?(60):(20*fps_scale/256)));
|
|
if(howl>176) howl=180;
|
|
if(howl<1) howl=1;
|
|
if((howlong<howl || movcounter)
|
|
&& (frameDisplayOn &&
|
|
(!movcounter || last_frame_display!=framecount)))
|
|
//|| input_display && (!movcounter || last_input_display!=cur_input_display)))
|
|
{
|
|
//Old input display code
|
|
/*
|
|
char inputstr [32];
|
|
if(input_display)
|
|
{
|
|
uint32 c = cur_input_display;
|
|
sprintf(inputstr, "%c%c%c%c%c%c%c%c %c%c%c%c%c%c%c%c",
|
|
(c&0x40)?'<':' ', (c&0x10)?'^':' ', (c&0x80)?'>':' ', (c&0x20)?'v':' ',
|
|
(c&0x01)?'A':' ', (c&0x02)?'B':' ', (c&0x08)?'S':' ', (c&0x04)?'s':' ',
|
|
(c&0x4000)?'<':' ', (c&0x1000)?'^':' ', (c&0x8000)?'>':' ', (c&0x2000)?'v':' ',
|
|
(c&0x0100)?'A':' ', (c&0x0200)?'B':' ', (c&0x0800)?'S':' ', (c&0x0400)?'s':' ');
|
|
if(!(c&0xff00))
|
|
inputstr[8] = '\0';
|
|
}
|
|
if(frameDisplayOn && !input_display)
|
|
FCEU_DispMessage("%s frame %u",current >= 0?"Recording":"Playing",framecount);
|
|
else if(input_display && !frameDisplayOn)
|
|
FCEU_DispMessage("Input: %s",inputstr);
|
|
else //if(input_display && frame_display)
|
|
FCEU_DispMessage("%s %u %s",current >= 0?"Recording":"Playing",framecount,inputstr);
|
|
*/
|
|
if(frameDisplayOn)
|
|
{
|
|
FCEU_DispMessage("%s frame %u",current >= 0?"Recording":"Playing",framecount);
|
|
}
|
|
last_frame_display = framecount;
|
|
last_input_display = cur_input_display;
|
|
movcounter=180-1;
|
|
return;
|
|
}
|
|
|
|
if(movcounter) movcounter--;
|
|
|
|
if(!MovieShow) return;
|
|
|
|
FCEU_DrawNumberRow(XBuf,MovieStatus, CurrentMovie);
|
|
MovieShow--;
|
|
}
|
|
|
|
int FCEUMOV_WriteState(FILE* st)
|
|
{
|
|
uint32 to_write = 0;
|
|
if(current < 0)
|
|
to_write = moviedatasize;
|
|
else if(current > 0)
|
|
to_write = frameptr;
|
|
|
|
if(!st)
|
|
return to_write;
|
|
|
|
if(to_write)
|
|
fwrite(moviedata, 1, to_write, st);
|
|
return to_write;
|
|
}
|
|
|
|
static int load_successful;
|
|
|
|
int FCEUMOV_ReadState(FILE* st, uint32 size)
|
|
{
|
|
// if this savestate was made while replaying,
|
|
// we need to "undo" nextd and nextts
|
|
if(nextd != -1)
|
|
{
|
|
int d = 1;
|
|
if(nextts > 65536)
|
|
d = 4;
|
|
else if(nextts > 256)
|
|
d = 3;
|
|
else if(nextts > 0)
|
|
d = 2;
|
|
frameptr -= d;
|
|
nextd = -1;
|
|
}
|
|
|
|
// if(current > 0 || (!movie_readonly && current < 0)) /* Recording or Playback (!read-only) */
|
|
if(current!=0 && !movie_readonly)
|
|
{
|
|
// copy movie data from savestate
|
|
moviedata = (uint8*)realloc(moviedata, size);
|
|
moviedatasize = size;
|
|
if(size && fread(moviedata, 1, size, st)<size)
|
|
return 0;
|
|
if(current < 0) // switch to recording
|
|
current = -current;
|
|
fseek(slots[current - 1], firstframeoffset, SEEK_SET);
|
|
fwrite(moviedata, 1, frameptr, slots[current - 1]);
|
|
if(!FCEU_BotMode())
|
|
{
|
|
rerecord_count++;
|
|
}
|
|
}
|
|
// else if(current < 0) /* Playback (read-only) */
|
|
else if(current!=0 && movie_readonly)
|
|
{
|
|
if(current > 0) // switch to playback
|
|
current = -current;
|
|
// allow frameptr to be updated but keep movie data
|
|
fseek(st, size, SEEK_CUR);
|
|
// prevent seeking beyond the end of the movie
|
|
if(frameptr > moviedatasize)
|
|
frameptr = moviedatasize;
|
|
}
|
|
else /* Neither recording or replaying */
|
|
{
|
|
// skip movie data
|
|
fseek(st, size, SEEK_CUR);
|
|
}
|
|
|
|
load_successful=1;
|
|
return 1;
|
|
}
|
|
|
|
void FCEUMOV_PreLoad(void)
|
|
{
|
|
load_successful=0;
|
|
}
|
|
|
|
int FCEUMOV_PostLoad(void)
|
|
{
|
|
if(!FCEUI_IsMovieActive())
|
|
return 1;
|
|
else
|
|
return load_successful;
|
|
}
|
|
|
|
char* FCEUI_MovieGetCurrentName(int addSlotNumber)
|
|
{
|
|
return FCEU_MakeFName(FCEUMKF_MOVIE,(addSlotNumber ? CurrentMovie : -1),0);
|
|
}
|
|
|
|
int FCEUI_IsMovieActive(void)
|
|
{
|
|
return current;
|
|
}
|
|
|
|
void FCEUI_MovieToggleFrameDisplay(void)
|
|
{
|
|
frame_display=!frame_display;
|
|
if(!(current != 0 && frame_display))// && !input_display)
|
|
FCEU_ResetMessages();
|
|
else
|
|
{
|
|
last_frame_display = ~framecount;
|
|
last_input_display = ~cur_input_display;
|
|
}
|
|
}
|
|
|
|
void FCEUI_ToggleInputDisplay(void)
|
|
{
|
|
switch(input_display)
|
|
{
|
|
case 0:
|
|
input_display = 1;
|
|
break;
|
|
case 1:
|
|
input_display = 2;
|
|
break;
|
|
case 2:
|
|
input_display = 4;
|
|
break;
|
|
default:
|
|
input_display = 0;
|
|
break;
|
|
}
|
|
|
|
//if(!input_display && !(current != 0 && frame_display))
|
|
//FCEU_ResetMessages();
|
|
//else
|
|
if(input_display)
|
|
{
|
|
last_frame_display = ~framecount;
|
|
last_input_display = ~cur_input_display;
|
|
}
|
|
}
|
|
|
|
void FCEUI_MovieToggleReadOnly(void)
|
|
{
|
|
if(movie_readonly < 2)
|
|
{
|
|
movie_readonly = !movie_readonly;
|
|
if(movie_readonly)
|
|
FCEU_DispMessage("Movie is now Read-Only.");
|
|
else
|
|
FCEU_DispMessage("Movie is now Read+Write.");
|
|
}
|
|
else
|
|
{
|
|
FCEU_DispMessage("Movie file is Read-Only.");
|
|
}
|
|
}
|
|
|
|
char lastMovieInfoFilename [512] = {'\0',};
|
|
int FCEUI_MovieGetInfo(const char* fname, MOVIE_INFO* info)
|
|
{
|
|
MovieFlushHeader();
|
|
|
|
char buffer [512];
|
|
fname = (const char*)convertToFCM(fname,buffer);
|
|
strncpy(lastMovieInfoFilename, fname, 512);
|
|
|
|
// main get info part of function
|
|
{
|
|
uint32 magic;
|
|
uint32 version;
|
|
uint8 _flags[4];
|
|
|
|
FILE* fp = FCEUD_UTF8fopen(fname, "rb");
|
|
if(!fp)
|
|
return 0;
|
|
|
|
read32le(&magic, fp);
|
|
if(magic != MOVIE_MAGIC)
|
|
{
|
|
fclose(fp);
|
|
return 0;
|
|
}
|
|
|
|
read32le(&version, fp);
|
|
if(version != MOVIE_VERSION)
|
|
{
|
|
fclose(fp);
|
|
if(version == 1)
|
|
return FCEUI_MovieGetInfo_v1(fname, info);
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
info->movie_version = MOVIE_VERSION;
|
|
|
|
fread(_flags, 1, 4, fp);
|
|
|
|
info->flags = _flags[0];
|
|
read32le(&info->num_frames, fp);
|
|
read32le(&info->rerecord_count, fp);
|
|
|
|
if(access(fname, W_OK))
|
|
info->read_only = 1;
|
|
else
|
|
info->read_only = 0;
|
|
|
|
fseek(fp, 12, SEEK_CUR); // skip movie_data_size, offset_to_savestate, and offset_to_movie_data
|
|
|
|
fread(info->md5_of_rom_used, 1, 16, fp);
|
|
info->md5_of_rom_used_present = 1;
|
|
|
|
read32le(&info->emu_version_used, fp);
|
|
|
|
// I probably could have planned this better...
|
|
{
|
|
char str[256];
|
|
size_t r;
|
|
int p;
|
|
int p2=0;
|
|
char last_c=32;
|
|
|
|
if(info->name_of_rom_used && info->name_of_rom_used_size)
|
|
info->name_of_rom_used[0]='\0';
|
|
|
|
r=fread(str, 1, 256, fp);
|
|
while(r > 0)
|
|
{
|
|
for(p=0; p<r && last_c != '\0'; ++p)
|
|
{
|
|
if(info->name_of_rom_used && info->name_of_rom_used_size && (p2 < info->name_of_rom_used_size-1))
|
|
{
|
|
info->name_of_rom_used[p2]=str[p];
|
|
p2++;
|
|
last_c=str[p];
|
|
}
|
|
}
|
|
if(p<r)
|
|
{
|
|
memmove(str, str+p, r-p);
|
|
r -= p;
|
|
break;
|
|
}
|
|
r=fread(str, 1, 256, fp);
|
|
}
|
|
|
|
p2=0;
|
|
last_c=32;
|
|
|
|
if(info->metadata && info->metadata_size)
|
|
info->metadata[0]='\0';
|
|
|
|
while(r > 0)
|
|
{
|
|
for(p=0; p<r && last_c != '\0'; ++p)
|
|
{
|
|
if(info->metadata && info->metadata_size && (p2 < info->metadata_size-1))
|
|
{
|
|
info->metadata[p2]=str[p];
|
|
p2++;
|
|
last_c=str[p];
|
|
}
|
|
}
|
|
if(p != r)
|
|
break;
|
|
r=fread(str, 1, 256, fp);
|
|
}
|
|
|
|
if(r<=0)
|
|
{
|
|
// somehow failed to read romname and metadata
|
|
fclose(fp);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// check what hacks are necessary
|
|
fseek(fp, 24, SEEK_SET); // offset_to_savestate offset
|
|
uint32 temp_savestate_offset;
|
|
read32le(&temp_savestate_offset, fp);
|
|
if(fseek(fp, temp_savestate_offset, SEEK_SET))
|
|
{
|
|
fclose(fp);
|
|
return 0;
|
|
}
|
|
if(!FCEUSS_LoadFP(fp,2)) return 0; // 2 -> don't really load, just load to find what's there then load backup
|
|
|
|
fclose(fp);
|
|
return 1;
|
|
}
|
|
}
|
|
/*
|
|
Backwards compat
|
|
*/
|
|
|
|
|
|
/*
|
|
struct MovieHeader_v1
|
|
{
|
|
uint32 magic;
|
|
uint32 version=1;
|
|
uint8 flags[4];
|
|
uint32 length_frames;
|
|
uint32 rerecord_count;
|
|
uint32 movie_data_size;
|
|
uint32 offset_to_savestate;
|
|
uint32 offset_to_movie_data;
|
|
uint16 metadata_ucs2[]; // ucs-2, ick! sizeof(metadata) = offset_to_savestate - MOVIE_HEADER_SIZE
|
|
}
|
|
*/
|
|
|
|
#define MOVIE_V1_HEADER_SIZE 32
|
|
|
|
static void FCEUI_LoadMovie_v1(char *fname, int _read_only)
|
|
{
|
|
FILE *fp;
|
|
char *fn = NULL;
|
|
|
|
FCEUI_StopMovie();
|
|
|
|
if(!fname)
|
|
fname = fn = FCEU_MakeFName(FCEUMKF_MOVIE,CurrentMovie,0);
|
|
|
|
// check movie_readonly
|
|
movie_readonly = _read_only;
|
|
if(access(fname, W_OK))
|
|
movie_readonly = 2;
|
|
|
|
fp = FCEUD_UTF8fopen(fname, (movie_readonly>=2) ? "rb" : "r+b");
|
|
|
|
if(fn)
|
|
{
|
|
free(fn);
|
|
fname = NULL;
|
|
}
|
|
|
|
if(!fp) return;
|
|
|
|
// read header
|
|
{
|
|
uint32 magic;
|
|
uint32 version;
|
|
uint8 flags[4];
|
|
uint32 fc;
|
|
|
|
read32le(&magic, fp);
|
|
if(magic != MOVIE_MAGIC)
|
|
{
|
|
fclose(fp);
|
|
return;
|
|
}
|
|
|
|
read32le(&version, fp);
|
|
if(version != 1)
|
|
{
|
|
fclose(fp);
|
|
return;
|
|
}
|
|
|
|
fread(flags, 1, 4, fp);
|
|
read32le(&fc, fp);
|
|
read32le(&rerecord_count, fp);
|
|
read32le(&moviedatasize, fp);
|
|
read32le(&savestate_offset, fp);
|
|
read32le(&firstframeoffset, fp);
|
|
if(fseek(fp, savestate_offset, SEEK_SET))
|
|
{
|
|
fclose(fp);
|
|
return;
|
|
}
|
|
|
|
if(flags[0] & MOVIE_FLAG_NOSYNCHACK)
|
|
movieSyncHackOn=0;
|
|
else
|
|
movieSyncHackOn=1;
|
|
}
|
|
|
|
// fully reload the game to reinitialize everything before playing any movie
|
|
// to try fixing nondeterministic playback of some games
|
|
{
|
|
extern char lastLoadedGameName [2048];
|
|
extern int disableBatteryLoading, suppressAddPowerCommand;
|
|
suppressAddPowerCommand=1;
|
|
suppressMovieStop=1;
|
|
{
|
|
FCEUGI * gi = FCEUI_LoadGame(lastLoadedGameName, 0);
|
|
if(!gi)
|
|
PowerNES();
|
|
}
|
|
suppressMovieStop=0;
|
|
suppressAddPowerCommand=0;
|
|
}
|
|
|
|
if(!FCEUSS_LoadFP(fp,1)) return;
|
|
|
|
ResetInputTypes();
|
|
|
|
fseek(fp, firstframeoffset, SEEK_SET);
|
|
moviedata = (uint8*)realloc(moviedata, moviedatasize);
|
|
fread(moviedata, 1, moviedatasize, fp);
|
|
|
|
framecount = 0; // movies start at frame 0!
|
|
frameptr = 0;
|
|
current = CurrentMovie;
|
|
slots[current] = fp;
|
|
|
|
memset(joop,0,sizeof(joop));
|
|
current = -1 - current;
|
|
framets=0;
|
|
nextts=0;
|
|
nextd = -1;
|
|
MovieStatus[CurrentMovie] = 1;
|
|
if(!fname)
|
|
FCEUI_SelectMovie(CurrentMovie,1); /* Quick hack to display status. */
|
|
else
|
|
FCEU_DispMessage("Movie playback started.");
|
|
}
|
|
|
|
static int FCEUI_MovieGetInfo_v1(const char* fname, MOVIE_INFO* info)
|
|
{
|
|
uint32 magic;
|
|
uint32 version;
|
|
uint8 _flags[4];
|
|
uint32 savestateoffset;
|
|
uint8 tmp[MOVIE_MAX_METADATA<<1];
|
|
int metadata_length;
|
|
|
|
FILE* fp = FCEUD_UTF8fopen(fname, "rb");
|
|
if(!fp)
|
|
return 0;
|
|
|
|
read32le(&magic, fp);
|
|
if(magic != MOVIE_MAGIC)
|
|
{
|
|
fclose(fp);
|
|
return 0;
|
|
}
|
|
|
|
read32le(&version, fp);
|
|
if(version != 1)
|
|
{
|
|
fclose(fp);
|
|
return 0;
|
|
}
|
|
|
|
info->movie_version = 1;
|
|
info->emu_version_used = 0; // unknown
|
|
|
|
fread(_flags, 1, 4, fp);
|
|
|
|
info->flags = _flags[0];
|
|
read32le(&info->num_frames, fp);
|
|
read32le(&info->rerecord_count, fp);
|
|
|
|
if(access(fname, W_OK))
|
|
info->read_only = 1;
|
|
else
|
|
info->read_only = 0;
|
|
|
|
fseek(fp, 4, SEEK_CUR);
|
|
read32le(&savestateoffset, fp);
|
|
|
|
metadata_length = (int)savestateoffset - MOVIE_V1_HEADER_SIZE;
|
|
if(metadata_length > 0)
|
|
{
|
|
int i;
|
|
|
|
metadata_length >>= 1;
|
|
if(metadata_length >= MOVIE_MAX_METADATA)
|
|
metadata_length = MOVIE_MAX_METADATA-1;
|
|
|
|
fseek(fp, MOVIE_V1_HEADER_SIZE, SEEK_SET);
|
|
fread(tmp, 1, metadata_length<<1, fp);
|
|
}
|
|
|
|
// turn old ucs2 metadata into utf8
|
|
if(info->metadata && info->metadata_size)
|
|
{
|
|
char* ptr=info->metadata;
|
|
char* ptr_end=info->metadata+info->metadata_size-1;
|
|
int c_ptr=0;
|
|
while(ptr<ptr_end && c_ptr<metadata_length)
|
|
{
|
|
uint16 c=(tmp[c_ptr<<1] | (tmp[(c_ptr<<1)+1] << 8));
|
|
switch(c)
|
|
{
|
|
case 0 ... 0x7f:
|
|
*ptr++ = (char)(c&0x7f);
|
|
break;
|
|
case 0x80 ... 0x7ff:
|
|
if(ptr+1>=ptr_end)
|
|
ptr_end=ptr;
|
|
else
|
|
{
|
|
*ptr++=(0xc0 | (c>>6));
|
|
*ptr++=(0x80 | (c & 0x3f));
|
|
}
|
|
break;
|
|
case 0x800 ... 0xffff:
|
|
if(ptr+2>=ptr_end)
|
|
ptr_end=ptr;
|
|
else
|
|
{
|
|
*ptr++=(0xe0 | (c>>12));
|
|
*ptr++=(0x80 | ((c>>6) & 0x3f));
|
|
*ptr++=(0x80 | (c & 0x3f));
|
|
}
|
|
break;
|
|
}
|
|
c_ptr++;
|
|
}
|
|
*ptr='\0';
|
|
}
|
|
|
|
// md5 info not available from v1
|
|
info->md5_of_rom_used_present = 0;
|
|
// rom name used for the movie not available from v1
|
|
if(info->name_of_rom_used && info->name_of_rom_used_size)
|
|
info->name_of_rom_used[0] = '\0';
|
|
|
|
// check what hacks are necessary
|
|
fseek(fp, 24, SEEK_SET); // offset_to_savestate offset
|
|
uint32 temp_savestate_offset;
|
|
read32le(&temp_savestate_offset, fp);
|
|
if(fseek(fp, temp_savestate_offset, SEEK_SET))
|
|
{
|
|
fclose(fp);
|
|
return 0;
|
|
}
|
|
if(!FCEUSS_LoadFP(fp,2)) return 0; // 2 -> don't really load, just load to find what's there then load backup
|
|
|
|
|
|
fclose(fp);
|
|
return 1;
|
|
}
|
|
|