oops forgot a couple of files

This commit is contained in:
zeromus 2008-11-10 22:37:21 +00:00
parent 024462c221
commit 6194523549
1 changed files with 674 additions and 0 deletions

674
desmume/src/movie.cpp Normal file
View File

@ -0,0 +1,674 @@
#include <assert.h>
#include <fstream>
#include "utils/guid.h"
#include "utils/xstring.h"
#include "movie.h"
#include "NDSSystem.h"
using namespace std;
#define FCEU_PrintError printlog
#define MOVIE_VERSION 1
//----movie engine main state
EMOVIEMODE movieMode = MOVIEMODE_INACTIVE;
//this should not be set unless we are in MOVIEMODE_RECORD!
fstream* osRecordingMovie = 0;
int currFrameCounter;
uint32 cur_input_display = 0;
int pauseframe = -1;
bool movie_readonly = true;
char curMovieFilename[512] = {0};
MovieData currMovieData;
int currRerecordCount;
//--------------
void MovieData::clearRecordRange(int start, int len)
{
for(int i=0;i<len;i++)
records[i+start].clear();
}
void MovieData::insertEmpty(int at, int frames)
{
if(at == -1)
{
int currcount = records.size();
records.resize(records.size()+frames);
clearRecordRange(currcount,frames);
}
else
{
records.insert(records.begin()+at,frames,MovieRecord());
clearRecordRange(at,frames);
}
}
void MovieRecord::clear()
{
pad = 0;
commands = 0;
touch.padding = 0;
}
const char MovieRecord::mnemonics[13] = {'R','L','D','U','T','S','B','A','Y','X','W','E','G'};
void MovieRecord::dumpPad(std::ostream* os, u16 pad)
{
//these are mnemonics for each joystick bit.
//since we usually use the regular joypad, these will be more helpful.
//but any character other than ' ' or '.' should count as a set bit
//maybe other input types will need to be encoded another way..
for(int bit=0;bit<13;bit++)
{
int bitmask = (1<<(12-bit));
char mnemonic = mnemonics[bit];
//if the bit is set write the mnemonic
if(pad & bitmask)
os->put(mnemonic);
else //otherwise write an unset bit
os->put('.');
}
}
void MovieRecord::parsePad(std::istream* is, u16& pad)
{
char buf[13];
is->read(buf,13);
pad = 0;
for(int i=0;i<13;i++)
{
pad <<= 1;
pad |= ((buf[i]=='.'||buf[i]==' ')?0:1);
}
}
void MovieRecord::parse(MovieData* md, std::istream* is)
{
//by the time we get in here, the initial pipe has already been extracted
//extract the commands
commands = u32DecFromIstream(is);
is->get(); //eat the pipe
parsePad(is, pad);
touch.x = u32DecFromIstream(is);
touch.y = u32DecFromIstream(is);
touch.touch = u32DecFromIstream(is);
is->get(); //eat the pipe
//should be left at a newline
}
void MovieRecord::dump(MovieData* md, std::ostream* os, int index)
{
//dump the misc commands
//*os << '|' << setw(1) << (int)commands;
os->put('|');
putdec<uint8,1,true>(os,commands);
os->put('|');
dumpPad(os, pad);
putdec<u8,3,true>(os,touch.x); os->put(' ');
putdec<u8,3,true>(os,touch.y); os->put(' ');
putdec<u8,1,true>(os,touch.touch);
os->put('|');
//each frame is on a new line
os->put('\n');
}
MovieData::MovieData()
: version(MOVIE_VERSION)
, emuVersion(DESMUME_VERSION_NUMERIC)
, binaryFlag(false)
, rerecordCount(1)
//, greenZoneCount(0)
{
memset(&romChecksum,0,sizeof(MD5DATA));
}
void MovieData::truncateAt(int frame)
{
records.resize(frame);
}
void MovieData::installValue(std::string& key, std::string& val)
{
//todo - use another config system, or drive this from a little data structure. because this is gross
if(key == "version")
installInt(val,version);
else if(key == "emuVersion")
installInt(val,emuVersion);
else if(key == "rerecordCount")
installInt(val,rerecordCount);
else if(key == "romFilename")
romFilename = val;
else if(key == "romChecksum")
StringToBytes(val,&romChecksum,MD5DATA::size);
else if(key == "guid")
guid = Desmume_Guid::fromString(val);
else if(key == "comment")
comments.push_back(mbstowcs(val));
else if(key == "savestate")
{
int len = Base64StringToBytesLength(val);
if(len == -1) len = HexStringToBytesLength(val); // wasn't base64, try hex
if(len >= 1)
{
savestate.resize(len);
StringToBytes(val,&savestate[0],len); // decodes either base64 or hex
}
}
}
int MovieData::dump(std::ostream *os, bool binary)
{
int start = os->tellp();
*os << "version " << version << endl;
*os << "emuVersion " << emuVersion << endl;
*os << "rerecordCount " << rerecordCount << endl;
*os << "romFilename " << romFilename << endl;
*os << "romChecksum " << BytesToString(romChecksum.data,MD5DATA::size) << endl;
*os << "guid " << guid.toString() << endl;
for(uint32 i=0;i<comments.size();i++)
*os << "comment " << wcstombs(comments[i]) << endl;
if(binary)
*os << "binary 1" << endl;
if(savestate.size() != 0)
*os << "savestate " << BytesToString(&savestate[0],savestate.size()) << endl;
if(binary)
{
////put one | to start the binary dump
//os->put('|');
//for(int i=0;i<(int)records.size();i++)
// records[i].dumpBinary(this,os,i);
}
else
for(int i=0;i<(int)records.size();i++)
records[i].dump(this,os,i);
int end = os->tellp();
return end-start;
}
//yuck... another custom text parser.
static bool LoadFM2(MovieData& movieData, std::istream* fp, int size, bool stopAfterHeader)
{
//TODO - start with something different. like 'desmume movie version 1"
std::ios::pos_type curr = fp->tellg();
//movie must start with "version 1"
char buf[9];
curr = fp->tellg();
fp->read(buf,9);
fp->seekg(curr);
if(fp->fail()) return false;
if(memcmp(buf,"version 1",9))
return false;
std::string key,value;
enum {
NEWLINE, KEY, SEPARATOR, VALUE, RECORD, COMMENT
} state = NEWLINE;
bool bail = false;
for(;;)
{
bool iswhitespace, isrecchar, isnewline;
int c;
if(size--<=0) goto bail;
c = fp->get();
if(c == -1)
goto bail;
iswhitespace = (c==' '||c=='\t');
isrecchar = (c=='|');
isnewline = (c==10||c==13);
if(isrecchar && movieData.binaryFlag && !stopAfterHeader)
{
//LoadFM2_binarychunk(movieData, fp, size);
return false;
}
switch(state)
{
case NEWLINE:
if(isnewline) goto done;
if(iswhitespace) goto done;
if(isrecchar)
goto dorecord;
//must be a key
key = "";
value = "";
goto dokey;
break;
case RECORD:
{
dorecord:
if (stopAfterHeader) return true;
int currcount = movieData.records.size();
movieData.records.resize(currcount+1);
int preparse = fp->tellg();
movieData.records[currcount].parse(&movieData, fp);
int postparse = fp->tellg();
size -= (postparse-preparse);
state = NEWLINE;
break;
}
case KEY:
dokey: //dookie
state = KEY;
if(iswhitespace) goto doseparator;
if(isnewline) goto commit;
key += c;
break;
case SEPARATOR:
doseparator:
state = SEPARATOR;
if(isnewline) goto commit;
if(!iswhitespace) goto dovalue;
break;
case VALUE:
dovalue:
state = VALUE;
if(isnewline) goto commit;
value += c;
}
goto done;
bail:
bail = true;
if(state == VALUE) goto commit;
goto done;
commit:
movieData.installValue(key,value);
state = NEWLINE;
done: ;
if(bail) break;
}
return true;
}
static void closeRecordingMovie()
{
if(osRecordingMovie)
{
delete osRecordingMovie;
osRecordingMovie = 0;
}
}
/// Stop movie playback.
static void StopPlayback()
{
//FCEU_DispMessageOnMovie("Movie playback stopped.");
movieMode = MOVIEMODE_INACTIVE;
}
/// Stop movie recording
static void StopRecording()
{
//FCEU_DispMessage("Movie recording stopped.");
movieMode = MOVIEMODE_INACTIVE;
closeRecordingMovie();
}
void FCEUI_StopMovie()
{
//if(suppressMovieStop)
// return;
if(movieMode == MOVIEMODE_PLAY)
StopPlayback();
else if(movieMode == MOVIEMODE_RECORD)
StopRecording();
curMovieFilename[0] = 0;
}
//begin playing an existing movie
void FCEUI_LoadMovie(const char *fname, bool _read_only, bool tasedit, int _pauseframe)
{
//if(!tasedit && !FCEU_IsValidUI(FCEUI_PLAYMOVIE))
// return;
assert(fname);
//mbg 6/10/08 - we used to call StopMovie here, but that cleared curMovieFilename and gave us crashes...
if(movieMode == MOVIEMODE_PLAY)
StopPlayback();
else if(movieMode == MOVIEMODE_RECORD)
StopRecording();
//--------------
currMovieData = MovieData();
strcpy(curMovieFilename, fname);
//FCEUFILE *fp = FCEU_fopen(fname,0,"rb",0);
//if (!fp) return;
//if(fp->isArchive() && !_read_only) {
// FCEU_PrintError("Cannot open a movie in read+write from an archive.");
// return;
//}
//LoadFM2(currMovieData, fp->stream, INT_MAX, false);
LoadFM2(currMovieData, &fstream(fname), INT_MAX, false);
//TODO
//fully reload the game to reinitialize everything before playing any movie
//poweron(true);
////WE NEED TO LOAD A SAVESTATE
//if(currMovieData.savestate.size() != 0)
//{
// bool success = MovieData::loadSavestateFrom(&currMovieData.savestate);
// if(!success) return;
//}
currFrameCounter = 0;
pauseframe = _pauseframe;
movie_readonly = _read_only;
movieMode = MOVIEMODE_PLAY;
currRerecordCount = currMovieData.rerecordCount;
//if(movie_readonly)
// FCEU_DispMessage("Replay started Read-Only.");
//else
// FCEU_DispMessage("Replay started Read+Write.");
}
static void openRecordingMovie(const char* fname)
{
//osRecordingMovie = FCEUD_UTF8_fstream(fname, "wb");
osRecordingMovie = new fstream(fname,std::ios_base::out);
/*if(!osRecordingMovie)
FCEU_PrintError("Error opening movie output file: %s",fname);*/
strcpy(curMovieFilename, fname);
}
//begin recording a new movie
//TODO - BUG - the record-from-another-savestate doesnt work.
void FCEUI_SaveMovie(const char *fname, std::wstring author)
{
//if(!FCEU_IsValidUI(FCEUI_RECORDMOVIE))
// return;
assert(fname);
FCEUI_StopMovie();
openRecordingMovie(fname);
currFrameCounter = 0;
//LagCounterReset();
currMovieData = MovieData();
currMovieData.guid.newGuid();
if(author != L"") currMovieData.comments.push_back(L"author " + author);
//currMovieData.romChecksum = GameInfo->MD5;
//currMovieData.romFilename = FileBase;
//todo ?
//poweron(true);
//else
// MovieData::dumpSavestateTo(&currMovieData.savestate,Z_BEST_COMPRESSION);
//we are going to go ahead and dump the header. from now on we will only be appending frames
currMovieData.dump(osRecordingMovie, false);
movieMode = MOVIEMODE_RECORD;
movie_readonly = false;
currRerecordCount = 0;
//FCEU_DispMessage("Movie recording started.");
}
//the main interaction point between the emulator and the movie system.
//either dumps the current joystick state or loads one state from the movie
void FCEUMOV_AddInputState()
{
//todo - for tasedit, either dump or load depending on whether input recording is enabled
//or something like that
//(input recording is just like standard read+write movie recording with input taken from gamepad)
//otherwise, it will come from the tasedit data.
if(movieMode == MOVIEMODE_PLAY)
{
//stop when we run out of frames
if(currFrameCounter == currMovieData.records.size())
{
StopPlayback();
}
else
{
MovieRecord* mr = &currMovieData.records[currFrameCounter];
//reset if necessary
if(mr->command_reset())
{}
//ResetNES();
NDS_setPadFromMovie(mr->pad);
}
//if we are on the last frame, then pause the emulator if the player requested it
if(currFrameCounter == currMovieData.records.size()-1)
{
/*if(FCEUD_PauseAfterPlayback())
{
FCEUI_ToggleEmulationPause();
}*/
}
//pause the movie at a specified frame
//if(FCEUMOV_ShouldPause() && FCEUI_EmulationPaused()==0)
//{
// FCEUI_ToggleEmulationPause();
// FCEU_DispMessage("Paused at specified movie frame");
//}
}
else if(movieMode == MOVIEMODE_RECORD)
{
MovieRecord mr;
mr.commands = 0;
mr.pad = nds.pad;
if(nds.isTouch) {
mr.touch.x = nds.touchX;
mr.touch.y = nds.touchY;
mr.touch.touch = 1;
} else {
mr.touch.x = 0;
mr.touch.y = 0;
mr.touch.touch = 0;
}
mr.dump(&currMovieData, osRecordingMovie,currMovieData.records.size());
currMovieData.records.push_back(mr);
}
currFrameCounter++;
/*extern uint8 joy[4];
memcpy(&cur_input_display,joy,4);*/
}
//TODO
void FCEUMOV_AddCommand(int cmd)
{
// do nothing if not recording a movie
if(movieMode != MOVIEMODE_RECORD)
return;
//printf("%d\n",cmd);
//MBG TODO BIG TODO TODO TODO
//DoEncode((cmd>>3)&0x3,cmd&0x7,1);
}
int FCEUMOV_WriteState(std::ostream* os)
{
//we are supposed to dump the movie data into the savestate
if(movieMode == MOVIEMODE_RECORD || movieMode == MOVIEMODE_PLAY)
return currMovieData.dump(os, true);
else return 0;
}
//TODO EVERYTHING BELOW
static bool load_successful;
bool FCEUMOV_ReadState(std::istream* is, uint32 size)
{
load_successful = false;
//a little rule: cant load states in read+write mode with a movie from an archive.
//so we are going to switch it to readonly mode in that case
if(!movie_readonly
//*&& FCEU_isFileInArchive(curMovieFilename)*/
) {
FCEU_PrintError("Cannot loadstate in Read+Write with movie from archive. Movie is now Read-Only.");
movie_readonly = true;
}
MovieData tempMovieData = MovieData();
std::ios::pos_type curr = is->tellg();
if(!LoadFM2(tempMovieData, is, size, false)) {
/*is->seekg((uint32)curr+size);
extern bool FCEU_state_loading_old_format;
if(FCEU_state_loading_old_format) {
if(movieMode == MOVIEMODE_PLAY || movieMode == MOVIEMODE_RECORD) {
FCEUI_StopMovie();
FCEU_PrintError("You have tried to use an old savestate while playing a movie. This is unsupported (since the old savestate has old-format movie data in it which can't be converted on the fly)");
}
}*/
return false;
}
//complex TAS logic for when a savestate is loaded:
//----------------
//if we are playing or recording and toggled read-only:
// then, the movie we are playing must match the guid of the one stored in the savestate or else error.
// the savestate is assumed to be in the same timeline as the current movie.
// if the current movie is not long enough to get to the savestate's frame#, then it is an error.
// the movie contained in the savestate will be discarded.
// the emulator will be put into play mode.
//if we are playing or recording and toggled read+write
// then, the movie we are playing must match the guid of the one stored in the savestate or else error.
// the movie contained in the savestate will be loaded into memory
// the frames in the movie after the savestate frame will be discarded
// the in-memory movie will have its rerecord count incremented
// the in-memory movie will be dumped to disk as fcm.
// the emulator will be put into record mode.
//if we are doing neither:
// then, we must discard this movie and just load the savestate
if(movieMode == MOVIEMODE_PLAY || movieMode == MOVIEMODE_RECORD)
{
//handle moviefile mismatch
if(tempMovieData.guid != currMovieData.guid)
{
//mbg 8/18/08 - this code can be used to turn the error message into an OK/CANCEL
#ifdef WIN32
//std::string msg = "There is a mismatch between savestate's movie and current movie.\ncurrent: " + currMovieData.guid.toString() + "\nsavestate: " + tempMovieData.guid.toString() + "\n\nThis means that you have loaded a savestate belonging to a different movie than the one you are playing now.\n\nContinue loading this savestate anyway?";
//extern HWND pwindow;
//int result = MessageBox(pwindow,msg.c_str(),"Error loading savestate",MB_OKCANCEL);
//if(result == IDCANCEL)
// return false;
#else
FCEU_PrintError("Mismatch between savestate's movie and current movie.\ncurrent: %s\nsavestate: %s\n",currMovieData.guid.toString().c_str(),tempMovieData.guid.toString().c_str());
return false;
#endif
}
closeRecordingMovie();
if(movie_readonly)
{
//if the frame counter is longer than our current movie, then error
if(currFrameCounter > (int)currMovieData.records.size())
{
FCEU_PrintError("Savestate is from a frame (%d) after the final frame in the movie (%d). This is not permitted.", currFrameCounter, currMovieData.records.size()-1);
return false;
}
movieMode = MOVIEMODE_PLAY;
}
else
{
//truncate before we copy, just to save some time
tempMovieData.truncateAt(currFrameCounter);
currMovieData = tempMovieData;
#ifdef _S9XLUA_H
if(!FCEU_LuaRerecordCountSkip())
currRerecordCount++;
#endif
currMovieData.rerecordCount = currRerecordCount;
openRecordingMovie(curMovieFilename);
currMovieData.dump(osRecordingMovie, false);
movieMode = MOVIEMODE_RECORD;
}
}
load_successful = true;
//// Maximus: Show the last input combination entered from the
//// movie within the state
//if(current!=0) // <- mz: only if playing or recording a movie
// memcpy(&cur_input_display, joop, 4);
return true;
}
void FCEUMOV_PreLoad(void)
{
load_successful=0;
}
bool FCEUMOV_PostLoad(void)
{
if(movieMode == MOVIEMODE_INACTIVE)
return true;
else
return load_successful;
}