fceux/src/oldmovie.cpp

677 lines
15 KiB
C++

#include "types.h"
#include "fceu.h"
#include "driver.h"
#include "netplay.h"
#include "oldmovie.h"
#include "movie.h"
#include "utils/xstring.h"
#include <fstream>
using namespace std;
// FCM\x1a
#define MOVIE_MAGIC 0x1a4d4346
// 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_VERSION 2
//-------
//this is just supposed to be a comment describing the disk format
//---
//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 uint8 joop[4];
static uint8 joopcmd;
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;
//Cache variables used for playback.
static uint32 nextts = 0;
static int32 nextd = 0;
// turn old ucs2 metadata into utf8
void convert_metadata(char* metadata, int metadata_size, uint8* tmp, int metadata_length)
{
char* ptr=metadata;
char* ptr_end=metadata+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));
//mbg merge 7/17/06 changed to if..elseif
if(c<=0x7f)
*ptr++ = (char)(c&0x7f);
else if(c<=0x7FF)
if(ptr+1>=ptr_end)
ptr_end=ptr;
else
{
*ptr++=(0xc0 | (c>>6));
*ptr++=(0x80 | (c & 0x3f));
}
else
if(ptr+2>=ptr_end)
ptr_end=ptr;
else
{
*ptr++=(0xe0 | (c>>12));
*ptr++=(0x80 | ((c>>6) & 0x3f));
*ptr++=(0x80 | (c & 0x3f));
}
c_ptr++;
}
*ptr='\0';
}
//backwards compat
static void FCEUI_LoadMovie_v1(char *fname, int _read_only);
//static int FCEUI_MovieGetInfo_v1(const char* fname, MOVIE_INFO* info);
//int _old_FCEUI_MovieGetInfo(const char* fname, MOVIE_INFO* info)
//{
// //mbg: wtf?
// //MovieFlushHeader();
//
// // 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;
// uint32 p; //mbg merge 7/17/06 change to uint32
// 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(temp_savestate_offset != 0xFFFFFFFF)
// {
// if(fseek(fp, temp_savestate_offset, SEEK_SET))
// {
// fclose(fp);
// return 0;
// }
//
// //don't really load, just load to find what's there then load backup
// if(!FCEUSS_LoadFP(fp,SSLOADPARAM_DUMMY)) return 0;
// }
//
// 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,0,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=true;
// {
// FCEUGI * gi = FCEUI_LoadGame(lastLoadedGameName, 0);
// if(!gi)
// PowerNES();
// }
// suppressMovieStop=false;
// suppressAddPowerCommand=0;
// }
//
// if(!FCEUSS_LoadFP(fp,SSLOADPARAM_BACKUP)) 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 = 0;
// slots = fp;
//
// memset(joop,0,sizeof(joop));
// current = -1 - current;
// framets=0;
// nextts=0;
// nextd = -1;
// 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; //mbg merge 7/17/06 removed
//
// 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));
// //mbg merge 7/17/06 changed to if..elseif
// if(c<=0x7f)
// *ptr++ = (char)(c&0x7f);
// else if(c<=0x7FF)
// if(ptr+1>=ptr_end)
// ptr_end=ptr;
// else
// {
// *ptr++=(0xc0 | (c>>6));
// *ptr++=(0x80 | (c & 0x3f));
// }
// else
// if(ptr+2>=ptr_end)
// ptr_end=ptr;
// else
// {
// *ptr++=(0xe0 | (c>>12));
// *ptr++=(0x80 | ((c>>6) & 0x3f));
// *ptr++=(0x80 | (c & 0x3f));
// }
//
// 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,SSLOADPARAM_DUMMY)) return 0; // 2 -> don't really load, just load to find what's there then load backup
//
//
// fclose(fp);
// return 1;
//}
static int movie_readchar()
{
if(frameptr >= moviedatasize)
{
return -1;
}
return (int)(moviedata[frameptr++]);
}
static void _addjoy()
{
while(nextts == framets || nextd == -1)
{
int tmp,ti;
uint8 d;
if(nextd != -1)
{
if(nextd&0x80)
{
//FCEU_DoSimpleCommand(nextd&0x1F);
int command = nextd&0x1F;
if(command == FCEUNPCMD_RESET)
joopcmd = MOVIECMD_RESET;
if(command == FCEUNPCMD_POWER)
joopcmd = MOVIECMD_POWER;
}
else
joop[(nextd >> 3)&0x3] ^= 1 << (nextd&0x7);
}
tmp = movie_readchar();
d = tmp;
if(tmp < 0)
{
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;
}
framets++;
}
EFCM_CONVERTRESULT convert_fcm(MovieData& md, std::string fname)
{
//convert EVEN OLDER movies to fcm
//fname = (char*)convertToFCM(fname,buffer);
uint32 framecount;
uint32 rerecord_count;
int movieConvertOffset1=0, movieConvertOffset2=0,movieSyncHackOn=0;
ifstream* fp = (ifstream*)FCEUD_UTF8_fstream(fname, "rb");
if(!fp) false;
// read header
uint32 magic;
uint32 version;
uint8 flags[4];
read32le(&magic, fp);
if(magic != MOVIE_MAGIC)
{
delete fp;
return FCM_CONVERTRESULT_FAILOPEN;
}
read32le(&version, fp);
if(version == 1)
{
// attempt to load previous version's format
//TODO
delete fp;
//FCEUI_LoadMovie_v1(fname, _read_only);
return FCM_CONVERTRESULT_OLDVERSION;
}
else if(version == MOVIE_VERSION)
{}
else
{
// unsupported version
delete fp;
return FCM_CONVERTRESULT_UNSUPPORTEDVERSION;
}
fp->read((char*)&flags,4);
read32le(&framecount, fp);
read32le(&rerecord_count, fp);
read32le(&moviedatasize, fp);
read32le(&savestate_offset, fp);
read32le(&firstframeoffset, fp);
//read header values
fp->read((char*)&md.romChecksum,16);
read32le((uint32*)&md.emuVersion,fp);
md.romFilename = readNullTerminatedAscii(fp);
md.comments.push_back(L"author " + mbstowcs(readNullTerminatedAscii(fp)));
//int metadata_length = savestate_offset - MOVIE_V1_HEADER_SIZE;
//uint8* metadata = new uint8[metadata_length];
//char* wcmetadata = new char[metadata_length*4]; //seems to me like we support the worst case
//fp->read((char*)metadata,metadata_length);
//convert_metadata(wcmetadata,metadata_length*4,metadata,metadata_length);
//md.comments.push_back(L"author " + (std::wstring)(wchar_t*)wcmetadata);
// 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)
md.palFlag = true;
bool initreset = false;
if(flags[0] & MOVIE_FLAG_FROM_POWERON)
{
//don't need to load a savestate
}
else if(flags[0] & MOVIE_FLAG_FROM_RESET)
{
initreset = true;
}
else
{
delete fp;
return FCM_CONVERTRESULT_STARTFROMSAVESTATENOTSUPPORTED;
}
//analyze input types?
//ResetInputTypes();
fp->seekg(firstframeoffset,ios::beg);
moviedata = (uint8*)realloc(moviedata, moviedatasize);
fp->read((char*)moviedata,moviedatasize);
frameptr = 0;
memset(joop,0,sizeof(joop));
framets=0;
nextts=0;
nextd = -1;
//prepare output structure
md.rerecordCount = rerecord_count;
md.records.resize(framecount);
md.guid.newGuid();
//begin decoding.
//joymask is used to look for any joystick that has input.
//if joysticks 3 or 4 have input then we will enable fourscore
uint8 joymask[4] = {0,0,0,0};
for(uint32 i=0;i<framecount;i++)
{
joopcmd = 0;
if(i==0 && initreset)
joopcmd = MOVIECMD_RESET;
_addjoy();
md.records[i].commands = joopcmd;
for(int j=0;j<4;j++) {
joymask[j] |= joop[j];
md.records[i].joysticks[j] = joop[j];
}
}
md.ports[2] = SIS_NONE;
if(joymask[2] || joymask[3])
{
md.fourscore = true;
md.ports[0] = md.ports[1] = SI_NONE;
}
else
{
md.fourscore = false;
md.ports[0] = md.ports[1] = SI_GAMEPAD;
}
free(moviedata);
moviedata = 0;
delete fp;
return FCM_CONVERTRESULT_SUCCESS;
}