void FCEUI_MakeBackupMovie(bool dispMessage) { //This function generates backup movie files string currentFn; //Current movie fillename string backupFn; //Target backup filename string tempFn; //temp used in back filename creation stringstream stream; int x; //Temp variable for string manip bool exist = false; //Used to test if filename exists bool overflow = false; //Used for special situation when backup numbering exceeds limit currentFn = curMovieFilename; //Get current moviefilename backupFn = curMovieFilename; //Make backup filename the same as current moviefilename x = backupFn.find_last_of("."); //Find file extension backupFn = backupFn.substr(0,x); //Remove extension tempFn = backupFn; //Store the filename at this point for (unsigned int backNum=0;backNum<999;backNum++) //999 = arbituary limit to backup files { stream.str(""); //Clear stream if (backNum > 99) stream << "-" << backNum; //assign backNum to stream else if (backNum <= 99 && backNum >= 10) stream << "-0" << backNum; //Make it 010, etc if two digits else stream << "-00" << backNum; //Make it 001, etc if single digit backupFn.append(stream.str()); //add number to bak filename backupFn.append(".bak"); //add extension exist = CheckFileExists(backupFn.c_str()); //Check if file exists if (!exist) break; //Yeah yeah, I should use a do loop or something else { backupFn = tempFn; //Before we loop again, reset the filename if (backNum == 999) //If 999 exists, we have overflowed, let's handle that { backupFn.append("-001.bak"); //We are going to simply overwrite 001.bak overflow = true; //Flag that we have exceeded limit break; //Just in case } } } MovieData md = currMovieData; //Get current movie data EMUFILE* outf = new EMUFILE_FILE(backupFn.c_str(),"wb"); //FCEUD_UTF8_fstream(backupFn, "wb"); //open/create file md.dump(outf,false); //dump movie data delete outf; //clean up, delete file object //TODO, decide if fstream successfully opened the file and print error message if it doesn't if (dispMessage) //If we should inform the user { // if (overflow) // FCEUI_DispMessage("Backup overflow, overwriting %s",backupFn.c_str()); //Inform user of overflow // else // FCEUI_DispMessage("%s created",backupFn.c_str()); //Inform user of backup filename } }
bool CheckTimelines(MovieData& stateMovie, MovieData& currMovie, int& errorFr) { bool isInTimeline = true; int length; //First check, make sure we are checking is for a post-movie savestate, we just want to adjust the length for now, we will handle this situation later in another function if (currFrameCounter <= stateMovie.getNumRecords()) length = currFrameCounter; //Note: currFrameCounter corresponds to the framecounter in the savestate else if (currFrameCounter > currMovie.getNumRecords()) //Now that we know the length of the records of the savestate we plan to load, let's match the length against the movie length = currMovie.getNumRecords(); //If length < currMovie records then this is a "future" event from the current movie, againt we will handle this situation later, we just want to find the right number of frames to compare else length = stateMovie.getNumRecords(); for (int x = 0; x < length; x++) { if (!stateMovie.records[x].Compare(currMovie.records[x])) { isInTimeline = false; errorFr = x; break; } } return isInTimeline; }
void FCEUI_CreateMovieFile(std::string fn) { MovieData md = currMovieData; //Get current movie data EMUFILE* outf = FCEUD_UTF8_fstream(fn, "wb"); //open/create file md.dump(outf,false); //dump movie data delete outf; //clean up, delete file object }
//begin recording a new movie //TODO - BUG - the record-from-another-savestate doesnt work. static 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."); }
int FCEUMOV_WriteState(EMUFILE* os) { //we are supposed to dump the movie data into the savestate if(movieMode == MOVIEMODE_RECORD || movieMode == MOVIEMODE_PLAY || movieMode == MOVIEMODE_FINISHED) return currMovieData.dump(os, true); else return 0; }
static 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; }
void mov_savestate(std::ostream *os)//std::ostream* os { if(movieMode == MOVIEMODE_RECORD || movieMode == MOVIEMODE_PLAY) { write32lemov(kMOVI,os); currMovieData.dump(os, true); } else { write32lemov(kNOMO,os); } }
void mov_savestate(EMUFILE* fp) { //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; if(movieMode != MOVIEMODE_INACTIVE) { write32le(kMOVI,fp); currMovieData.dump(fp, true); } else { write32le(kNOMO,fp); } }
//begin recording a new movie //TODO - BUG - the record-from-another-savestate doesnt work. void SaveMovie(const char *fname, std::string author, int controllers) { assert(fname); StopMovie(); openRecordingMovie(fname); currFrameCounter = 0; //LagCounterReset(); currMovieData = MovieData(); // currMovieData.guid.newGuid(); if(author != "") currMovieData.comments.push_back("author " + author); //currMovieData.romChecksum = GameInfo->MD5; // currMovieData.romFilename = cdip->gamename;//GetRomName(); // extern bool _HACK_DONT_STOPMOVIE; // _HACK_DONT_STOPMOVIE = true; pcejin.lagFrameCounter = 0; pcejin.isLagFrame = false; PCE_Power(); // _HACK_DONT_STOPMOVIE = false; currMovieData.ports = controllers; //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; ClearPCESRAM(); LagFrameCounter=0; DisplayMessage("Movie recording started."); }
//begin recording a new movie //TODO - BUG - the record-from-another-savestate doesnt work. void FCEUI_SaveMovie(const char *fname, EMOVIE_FLAG flags, std::wstring author) { if(!FCEU_IsValidUI(FCEUI_RECORDMOVIE)) return; assert(fname); FCEUI_StopMovie(); openRecordingMovie(fname); currFrameCounter = 0; LagCounterReset(); FCEUMOV_CreateCleanMovie(); #ifndef _GLIBCXX_USE_WCHAR_T if(author != "") currMovieData.comments.push_back("author " + author); #else if(author != L"") currMovieData.comments.push_back(L"author " + author); #endif if(flags & MOVIE_FLAG_FROM_POWERON) { movieFromPoweron = true; poweron(true); } else { movieFromPoweron = false; MovieData::dumpSavestateTo(&currMovieData.savestate,Z_BEST_COMPRESSION); } FCEUMOV_ClearCommands(); //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; FCEU_DispMessage("Movie recording started.",0); }
bool mov_loadstate(EMUFILE* fp, int size) { load_successful = false; u32 cookie; if(read32le(&cookie,fp) != 1) return false; if(cookie == kNOMO) { if(movieMode == MOVIEMODE_RECORD || movieMode == MOVIEMODE_PLAY) FinishPlayback(); return true; } else if(cookie != kMOVI) return false; size -= 4; if (!movie_readonly && autoMovieBackup && freshMovie) //If auto-backup is on, movie has not been altered this session and the movie is in read+write mode { FCEUI_MakeBackupMovie(false); //Backup the movie before the contents get altered, but do not display messages } //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(); //int curr = fp->ftell(); if(!LoadFM2(tempMovieData, fp, 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 loadstate //fully conforms to the savestate logic documented in the Laws of TAS //http://tasvideos.org/LawsOfTAS/OnSavestates.html //---------------- /* Playback or Recording + Read-only * Check that GUID of movie and savestate-movie must match or else error o on error: a message informing that the savestate doesn't belong to this movie. This is a GUID mismatch error. Give user a choice to load it anyway. + failstate: if use declines, loadstate attempt canceled, movie can resume as if not attempted if user has backup savstates enabled else stop movie * Check that movie and savestate-movie are in same timeline. If not then this is a wrong timeline error. o on error: a message informing that the savestate doesn't belong to this movie + failstate: loadstate attempt canceled, movie can resume as if not attempted if user has backup savestates enabled else stop movie * Check that savestate-movie is not greater than movie. If not then this is a future event error and is not allowed in read-only o on error: message informing that the savestate is from a frame after the last frame of the movie + failstate - loadstate attempt cancelled, movie can resume if user has backup savesattes enabled, else stop movie * Check that savestate framcount <= savestate movie length. If not this is a post-movie savestate o on post-movie: See post-movie event section. * All error checks have passed, state will be loaded * Movie contained in the savestate will be discarded * Movie is now in Playback mode Playback, Recording + Read+write * Check that GUID of movie and savestate-movie must match or else error o on error: a message informing that the savestate doesn't belong to this movie. This is a GUID mismatch error. Give user a choice to load it anyway. + failstate: if use declines, loadstate attempt canceled, movie can resume as if not attempted (stop movie if resume fails)canceled, movie can resume if backup savestates enabled else stopmovie * Check that savestate framcount <= savestate movie length. If not this is a post-movie savestate o on post-movie: See post-movie event section. * savestate passed all error checks and will now be loaded in its entirety and replace movie (note: there will be no truncation yet) * current framecount will be set to savestate framecount * on the next frame of input, movie will be truncated to framecount o (note: savestate-movie can be a future event of the movie timeline, or a completely new timeline and it is still allowed) * Rerecord count of movie will be incremented * Movie is now in record mode Post-movie savestate event * Whan a savestate is loaded and is determined that the savestate-movie length is less than the savestate framecount then it is a post-movie savestate. These occur when a savestate was made during Movie Finished mode. * If read+write, the entire savestate movie will be loaded and replace current movie. * If read-only, the savestate movie will be discarded * Mode will be switched to Move Finished * Savestate will be loaded * Current framecount changes to savestate framecount * User will have control of input as if Movie inactive mode */ if(movieMode != MOVIEMODE_INACTIVE) { //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 #if defined(WIN32) && !defined(WXPORT) 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?"; int result = MessageBox(MainWindow->getHWnd(),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) { currMovieData = tempMovieData; currMovieData.rerecordCount = currRerecordCount; } if(currFrameCounter > (int)currMovieData.records.size()) { // if the frame counter is longer than our current movie, // switch to "finished" mode. // this is a mode that behaves like "inactive" // except it permits switching to play/record by loading an earlier savestate. // (and we continue to store the finished movie in savestates made while finished) osd->setLineColor(255,0,0); // let's make the text red too to hopefully catch the user's attention a bit. FinishPlayback(); osd->setLineColor(255,255,255); //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; } else if(movie_readonly) { //------------------------------------------------------------- //this code would reload the movie from disk. allegedly it is helpful to hexers, but //it is way too slow with dsm format. so it is only here as a reminder, and in case someone //wants to play with it //------------------------------------------------------------- //{ // fstream fs (curMovieFilename); // if(!LoadFM2(tempMovieData, &fs, INT_MAX, false)) // { // FCEU_PrintError("Failed to reload DSM after loading savestate"); // } // fs.close(); // currMovieData = tempMovieData; //} //------------------------------------------------------------- movieMode = MOVIEMODE_PLAY; } else { // #ifdef _S9XLUA_H // if(!FCEU_LuaRerecordCountSkip()) currRerecordCount++; // #endif currMovieData.rerecordCount = currRerecordCount; currMovieData.truncateAt(currFrameCounter); openRecordingMovie(curMovieFilename); if(!osRecordingMovie) { osd->setLineColor(255, 0, 0); osd->addLine("Can't save movie file!"); } //printf("DUMPING MOVIE: %d FRAMES\n",currMovieData.records.size()); currMovieData.dump(osRecordingMovie, false); movieMode = MOVIEMODE_RECORD; } } load_successful = true; freshMovie = false; return true; }
static 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; }
//begin recording a new movie //TODO - BUG - the record-from-another-savestate doesnt work. void FCEUI_SaveMovie(const char *fname, std::wstring author, int flag, std::string sramfname, const DateTime &rtcstart) { //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.crc; currMovieData.romSerial = gameInfo.ROMserial; currMovieData.romFilename = path.GetRomName(); currMovieData.rtcStart = rtcstart; // reset firmware (some games can write to it) if (CommonSettings.UseExtFirmware == false) { NDS_CreateDummyFirmware(&CommonSettings.InternalFirmConf); } NDS_Reset(); //todo ? //poweron(true); //else // MovieData::dumpSavestateTo(&currMovieData.savestate,Z_BEST_COMPRESSION); if(flag == 1) EMUFILE::readAllBytes(&currMovieData.sram, sramfname); //we are going to go ahead and dump the header. from now on we will only be appending frames currMovieData.dump(osRecordingMovie, false); currFrameCounter=0; lagframecounter=0; LagFrameFlag=0; lastLag=0; TotalLagFrames=0; movieMode = MOVIEMODE_RECORD; movie_readonly = false; currRerecordCount = 0; MMU_new.backupDevice.movie_mode(); if(currMovieData.sram.size() != 0) { bool success = MovieData::loadSramFrom(&currMovieData.sram); if(!success) return; } driver->USR_InfoMessage("Movie recording started."); }
//yuck... another custom text parser. bool LoadFM2(MovieData& movieData, EMUFILE* fp, int size, bool stopAfterHeader) { //TODO - start with something different. like 'desmume movie version 1" int curr = fp->ftell(); //movie must start with "version 1" char buf[9]; curr = fp->ftell(); fp->fread(buf,9); fp->fseek(curr, SEEK_SET); // 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->fgetc(); 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 true; } 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->ftell(); movieData.records[currcount].parse(&movieData, fp); int postparse = fp->ftell(); 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; break; case COMMENT: default: break; } 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; }
/** * The main loop for the SDL. */ int main(int argc, char *argv[]) { // this is a hackish check for the --help arguemnts // these are normally processed by the config parser, but SDL_Init // must be run before the config parser: so if even SDL_Init fails, // these six lines will still print the help output if(argc > 1) { if(!strcmp(argv[1], "--help") || !strcmp(argv[1],"-h")) { ShowUsage(argv[0]); return 0; } } int error, frameskip; FCEUD_Message("Starting " FCEU_NAME_AND_VERSION "...\n"); #ifdef WIN32 /* Taken from win32 sdl_main.c */ SDL_SetModuleHandle(GetModuleHandle(NULL)); #endif /* SDL_INIT_VIDEO Needed for (joystick config) event processing? */ if(SDL_Init(SDL_INIT_VIDEO)) { printf("Could not initialize SDL: %s.\n", SDL_GetError()); return(-1); } #ifdef OPENGL SDL_GL_LoadLibrary(0); #endif // Initialize the configuration system g_config = InitConfig(); if(!g_config) { SDL_Quit(); return -1; } // initialize the infrastructure error = FCEUI_Initialize(); if(error != 1) { ShowUsage(argv[0]); SDL_Quit(); return -1; } // check for --help or -h and display usage; also check for --nogui for(int i=0; i<argc;i++) { if(strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { ShowUsage(argv[0]); SDL_Quit(); return 0; } #ifdef _GTK else if(strcmp(argv[i], "--nogui") == 0) { noGui = 1; argv[i] = ""; } #endif } int romIndex = g_config->parse(argc, argv); // This is here so that a default fceux.cfg will be created on first // run, even without a valid ROM to play. // Unless, of course, there's actually --no-config given // mbg 8/23/2008 - this is also here so that the inputcfg routines can have // a chance to dump the new inputcfg to the fceux.cfg in case you didnt // specify a rom filename g_config->getOption("SDL.NoConfig", &noconfig); if (!noconfig) g_config->save(); std::string s; g_config->getOption("SDL.InputCfg", &s); if(s.size() != 0) { InitVideo(GameInfo); InputCfg(s); } // set the FAMICOM PAD 2 Mic thing { int t; g_config->getOption("SDL.Input.FamicomPad2.EnableMic", &t); if(t) replaceP2StartWithMicrophone = t; } // update the input devices UpdateInput(g_config); // check for a .fcm file to convert to .fm2 g_config->getOption ("SDL.FCMConvert", &s); g_config->setOption ("SDL.FCMConvert", ""); if (!s.empty()) { int okcount = 0; std::string infname = s.c_str(); // produce output filename std::string outname; size_t dot = infname.find_last_of ("."); if (dot == std::string::npos) outname = infname + ".fm2"; else outname = infname.substr(0,dot) + ".fm2"; MovieData md; EFCM_CONVERTRESULT result = convert_fcm (md, infname); if (result == FCM_CONVERTRESULT_SUCCESS) { okcount++; // *outf = new EMUFILE; EMUFILE_FILE* outf = FCEUD_UTF8_fstream (outname, "wb"); md.dump (outf,false); delete outf; FCEUD_Message ("Your file has been converted to FM2.\n"); } else { FCEUD_Message ("Something went wrong while converting your file...\n"); } DriverKill(); SDL_Quit(); return 0; } // If x/y res set to 0, store current display res in SDL.LastX/YRes int yres, xres; g_config->getOption("SDL.XResolution", &xres); g_config->getOption("SDL.YResolution", &yres); #if SDL_VERSION_ATLEAST(2, 0, 0) // TODO _ SDL 2.0 #else const SDL_VideoInfo* vid_info = SDL_GetVideoInfo(); if(xres == 0) { if(vid_info != NULL) { g_config->setOption("SDL.LastXRes", vid_info->current_w); } else { g_config->setOption("SDL.LastXRes", 512); } } else { g_config->setOption("SDL.LastXRes", xres); } if(yres == 0) { if(vid_info != NULL) { g_config->setOption("SDL.LastYRes", vid_info->current_h); } else { g_config->setOption("SDL.LastYRes", 448); } } else { g_config->setOption("SDL.LastYRes", yres); } #endif int autoResume; g_config->getOption("SDL.AutoResume", &autoResume); if(autoResume) { AutoResumePlay = true; } else { AutoResumePlay = false; } // check to see if recording HUD to AVI is enabled int rh; g_config->getOption("SDL.RecordHUD", &rh); if( rh == 0) FCEUI_SetAviEnableHUDrecording(true); else FCEUI_SetAviEnableHUDrecording(false); // check to see if movie messages are disabled int mm; g_config->getOption("SDL.MovieMsg", &mm); if( mm == 0) FCEUI_SetAviDisableMovieMessages(true); else FCEUI_SetAviDisableMovieMessages(false); // check for a .fm2 file to rip the subtitles g_config->getOption("SDL.RipSubs", &s); g_config->setOption("SDL.RipSubs", ""); if (!s.empty()) { MovieData md; std::string infname; infname = s.c_str(); FCEUFILE *fp = FCEU_fopen(s.c_str(), 0, "rb", 0); // load the movie and and subtitles extern bool LoadFM2(MovieData&, EMUFILE*, int, bool); LoadFM2(md, fp->stream, INT_MAX, false); LoadSubtitles(md); // fill subtitleFrames and subtitleMessages delete fp; // produce .srt file's name and open it for writing std::string outname; size_t dot = infname.find_last_of ("."); if (dot == std::string::npos) outname = infname + ".srt"; else outname = infname.substr(0,dot) + ".srt"; FILE *srtfile; srtfile = fopen(outname.c_str(), "w"); if (srtfile != NULL) { extern std::vector<int> subtitleFrames; extern std::vector<std::string> subtitleMessages; float fps = (md.palFlag == 0 ? 60.0988 : 50.0069); // NTSC vs PAL float subduration = 3; // seconds for the subtitles to be displayed for (int i = 0; i < subtitleFrames.size(); i++) { fprintf(srtfile, "%i\n", i+1); // starts with 1, not 0 double seconds, ms, endseconds, endms; seconds = subtitleFrames[i]/fps; if (i+1 < subtitleFrames.size()) // there's another subtitle coming after this one { if (subtitleFrames[i+1]-subtitleFrames[i] < subduration*fps) // avoid two subtitles at the same time { endseconds = (subtitleFrames[i+1]-1)/fps; // frame x: subtitle1; frame x+1 subtitle2 } else { endseconds = seconds+subduration; } } else { endseconds = seconds+subduration; } ms = modf(seconds, &seconds); endms = modf(endseconds, &endseconds); // this is just beyond ugly, don't show it to your kids fprintf(srtfile, "%02.0f:%02d:%02d,%03d --> %02.0f:%02d:%02d,%03d\n", // hh:mm:ss,ms --> hh:mm:ss,ms floor(seconds/3600), (int)floor(seconds/60 ) % 60, (int)floor(seconds) % 60, (int)(ms*1000), floor(endseconds/3600), (int)floor(endseconds/60) % 60, (int)floor(endseconds) % 60, (int)(endms*1000)); fprintf(srtfile, "%s\n\n", subtitleMessages[i].c_str()); // new line for every subtitle } fclose(srtfile); printf("%d subtitles have been ripped.\n", (int)subtitleFrames.size()); } else { FCEUD_Message("Couldn't create output srt file...\n"); } DriverKill(); SDL_Quit(); return 0; } // if we're not compiling w/ the gui, exit if a rom isn't specified #ifndef _GTK if(romIndex <= 0) { ShowUsage(argv[0]); FCEUD_Message("\nError parsing command line arguments\n"); SDL_Quit(); return -1; } #endif // update the emu core UpdateEMUCore(g_config); #ifdef CREATE_AVI g_config->getOption("SDL.VideoLog", &s); g_config->setOption("SDL.VideoLog", ""); if(!s.empty()) { NESVideoSetVideoCmd(s.c_str()); LoggingEnabled = 1; g_config->getOption("SDL.MuteCapture", &mutecapture); } else { mutecapture = 0; } #endif { int id; g_config->getOption("SDL.InputDisplay", &id); extern int input_display; input_display = id; // not exactly an id as an true/false switch; still better than creating another int for that g_config->getOption("SDL.SubtitleDisplay", &id); extern int movieSubtitles; movieSubtitles = id; } // load the hotkeys from the config life setHotKeys(); #ifdef _GTK if(noGui == 0) { gtk_init(&argc, &argv); InitGTKSubsystem(argc, argv); while(gtk_events_pending()) gtk_main_iteration_do(FALSE); } #endif if(romIndex >= 0) { // load the specified game error = LoadGame(argv[romIndex]); if(error != 1) { DriverKill(); SDL_Quit(); return -1; } g_config->setOption("SDL.LastOpenFile", argv[romIndex]); g_config->save(); } // movie playback g_config->getOption("SDL.Movie", &s); g_config->setOption("SDL.Movie", ""); if (s != "") { if(s.find(".fm2") != std::string::npos || s.find(".fm3") != std::string::npos) { static int pauseframe; g_config->getOption("SDL.PauseFrame", &pauseframe); g_config->setOption("SDL.PauseFrame", 0); FCEUI_printf("Playing back movie located at %s\n", s.c_str()); FCEUI_LoadMovie(s.c_str(), false, pauseframe ? pauseframe : false); } else { FCEUI_printf("Sorry, I don't know how to play back %s\n", s.c_str()); } } int periodic_saves; int save_state; g_config->getOption("SDL.PeriodicSaves", &periodic_saves); g_config->getOption("SDL.AutoSaveState", &save_state); if(periodic_saves && save_state < 10 && save_state >= 0){ FCEUI_SelectState(save_state, 0); } else { periodic_saves = 0; } #ifdef _S9XLUA_H // load lua script if option passed g_config->getOption("SDL.LuaScript", &s); g_config->setOption("SDL.LuaScript", ""); if (s != "") { FCEU_LoadLuaCode(s.c_str()); } #endif { int id; g_config->getOption("SDL.NewPPU", &id); if (id) newppu = 1; } g_config->getOption("SDL.Frameskip", &frameskip); // loop playing the game #ifdef _GTK if(noGui == 0) { while(1) { if(GameInfo) DoFun(frameskip, periodic_saves); else SDL_Delay(1); while(gtk_events_pending()) gtk_main_iteration_do(FALSE); } } else { while(GameInfo) DoFun(frameskip, periodic_saves); } #else while(GameInfo) { DoFun(frameskip, periodic_saves); } #endif CloseGame(); // exit the infrastructure FCEUI_Kill(); SDL_Quit(); return 0; }
//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() { #if defined(WIN32) && !defined(DINGUX) if (movieMode == MOVIEMODE_TASEDITOR) { // if movie length is less or equal to currFrame, pad it with empty frames if (((int)currMovieData.records.size() - 1) < (currFrameCounter + 1)) currMovieData.insertEmpty(-1, (currFrameCounter + 1) - ((int)currMovieData.records.size() - 1)); MovieRecord* mr = &currMovieData.records[currFrameCounter]; if (isTaseditorRecording()) { // record commands and buttons mr->commands |= _currCommand; joyports[0].log(mr); joyports[1].log(mr); recordInputByTaseditor(); } // replay buttons joyports[0].load(mr); joyports[1].load(mr); // replay commands if (mr->command_power()) PowerNES(); if (mr->command_reset()) ResetNES(); if (mr->command_fds_insert()) FCEU_FDSInsert(); if (mr->command_fds_select()) FCEU_FDSSelect(); if (mr->command_vs_insertcoin()) FCEU_VSUniCoin(); _currCommand = 0; } else #endif if (movieMode == MOVIEMODE_PLAY) { //stop when we run out of frames if (currFrameCounter >= (int)currMovieData.records.size()) { FinishPlayback(); //tell all drivers to poll input and set up their logical states for(int port=0;port<2;port++) joyports[port].driver->Update(port,joyports[port].ptr,joyports[port].attrib); portFC.driver->Update(portFC.ptr,portFC.attrib); } else { MovieRecord* mr = &currMovieData.records[currFrameCounter]; //reset and power cycle if necessary if(mr->command_power()) PowerNES(); if(mr->command_reset()) ResetNES(); if(mr->command_fds_insert()) FCEU_FDSInsert(); if(mr->command_fds_select()) FCEU_FDSSelect(); if (mr->command_vs_insertcoin()) FCEU_VSUniCoin(); joyports[0].load(mr); joyports[1].load(mr); } //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",0); } } else if (movieMode == MOVIEMODE_RECORD) { MovieRecord mr; joyports[0].log(&mr); joyports[1].log(&mr); mr.commands = _currCommand; _currCommand = 0; //Adelikat: in normal mode, this is done at the time of loading a savestate in read+write mode //If the user chooses it can be delayed to here if (fullSaveStateLoads && (currFrameCounter < (int)currMovieData.records.size())) currMovieData.truncateAt(currFrameCounter); mr.dump(&currMovieData, osRecordingMovie,currMovieData.records.size()); // to disk currMovieData.records.push_back(mr); } currFrameCounter++; extern uint8 joy[4]; memcpy(&cur_input_display,joy,4); }
//yuck... another custom text parser. bool LoadFM2(MovieData& movieData, EMUFILE* fp, int size, bool stopAfterHeader) { // if there's no "binary" tag in the movie header, consider it as a movie in text format movieData.binaryFlag = false; // Non-TASEditor projects consume until EOF movieData.loadFrameCount = -1; std::ios::pos_type curr = fp->ftell(); if (!stopAfterHeader) { // first, look for an fcm signature char fcmbuf[3]; fp->fread(fcmbuf,3); fp->fseek(curr,SEEK_SET); if(!strncmp(fcmbuf,"FCM",3)) { FCEU_PrintError("FCM File format is no longer supported. Please use Tools > Convert FCM"); return false; } } //movie must start with "version 3" char buf[9]; curr = fp->ftell(); fp->fread(buf,9); fp->fseek(curr,SEEK_SET); if(fp->fail()) return false; if(memcmp(buf,"version 3",9)) return false; std::string key,value; enum { NEWLINE, KEY, SEPARATOR, VALUE, RECORD, COMMENT, SUBTITLE } state = NEWLINE; bool bail = false; bool iswhitespace, isrecchar, isnewline; int c; for(;;) { if(size--<=0) goto bail; c = fp->fgetc(); 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 true; } else if (isnewline && movieData.loadFrameCount == movieData.records.size()) // exit prematurely if loaded the specified amound of records return true; 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->ftell(); movieData.records[currcount].parse(&movieData, fp); int postparse = fp->ftell(); 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; }
bool FCEUMOV_ReadState(EMUFILE* is, uint32 size) { load_successful = false; if (!movie_readonly) { if (currMovieData.loadFrameCount >= 0) { #if defined(WIN32) && !defined(DINGUX) int result = MessageBox(hAppWnd, "This movie is a TAS Editor project file.\nIt can be modified in TAS Editor only.\n\nOpen it in TAS Editor now?", "Movie Replay", MB_YESNO); if (result == IDYES) mustEngageTaseditor = true; #else FCEUI_printf("This movie is a TAS Editor project file! It can be modified in TAS Editor only.\nMovie is now Read-Only.\n"); #endif movie_readonly = true; } if (FCEU_isFileInArchive(curMovieFilename)) { //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 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->ftell(); if(!LoadFM2(tempMovieData, is, size, false)) { is->fseek((uint32)curr+size,SEEK_SET); extern bool FCEU_state_loading_old_format; if(FCEU_state_loading_old_format) { if(movieMode == MOVIEMODE_PLAY || movieMode == MOVIEMODE_RECORD || movieMode == MOVIEMODE_FINISHED) { //FCEUI_StopMovie(); //No reason to stop the movie, nothing destructive has happened yet. 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 loadstate //fully conforms to the savestate logic documented in the Laws of TAS //http://tasvideos.org/LawsOfTAS/OnSavestates.html //---------------- /* Playback or Recording + Read-only * Check that GUID of movie and savestate-movie must match or else error o on error: a message informing that the savestate doesn't belong to this movie. This is a GUID mismatch error. Give user a choice to load it anyway. + failstate: if use declines, loadstate attempt canceled, movie can resume as if not attempted if user has backup savstates enabled else stop movie * Check that movie and savestate-movie are in same timeline. If not then this is a wrong timeline error. o on error: a message informing that the savestate doesn't belong to this movie + failstate: loadstate attempt canceled, movie can resume as if not attempted if user has backup savestates enabled else stop movie * Check that savestate-movie is not greater than movie. If it's greater then this is a future event error and is not allowed in read-only o on error: message informing that the savestate is from a frame after the last frame of the movie + failstate - loadstate attempt cancelled, movie can resume if user has backup savesattes enabled, else stop movie * Check that savestate framcount <= savestate movie length. If not this is a post-movie savestate and is not allowed in read-only o on error: message informing that the savestate is from a frame after the last frame of the savestated movie + failstate - loadstate attempt cancelled, movie can resume if user has backup savesattes enabled, else stop movie * All error checks have passed, state will be loaded * Movie contained in the savestate will be discarded * Movie is now in Playback mode Playback, Recording + Read+write * Check that GUID of movie and savestate-movie must match or else error o on error: a message informing that the savestate doesn't belong to this movie. This is a GUID mismatch error. Give user a choice to load it anyway. + failstate: if use declines, loadstate attempt canceled, movie can resume as if not attempted (stop movie if resume fails)canceled, movie can resume if backup savestates enabled else stopmovie * Check that savestate framcount <= savestate movie length. If not this is a post-movie savestate o on post-movie: See post-movie event section. * savestate passed all error checks and will now be loaded in its entirety and replace movie (note: there will be no truncation yet) * current framecount will be set to savestate framecount * on the next frame of input, movie will be truncated to framecount o (note: savestate-movie can be a future event of the movie timeline, or a completely new timeline and it is still allowed) * Rerecord count of movie will be incremented * Movie is now in record mode Post-movie savestate event * Whan a savestate is loaded and is determined that the savestate-movie length is less than the savestate framecount then it is a post-movie savestate. These occur when a savestate was made during Movie Finished mode. * If read+write, the entire savestate movie will be loaded and replace current movie. * If read-only, the savestate movie will be discarded * Mode will be switched to Move Finished * Savestate will be loaded * Current framecount changes to savestate framecount * User will have control of input as if Movie inactive mode */ if(movieMode == MOVIEMODE_PLAY || movieMode == MOVIEMODE_RECORD || movieMode == MOVIEMODE_FINISHED) { //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 #if defined(WIN32) && !defined(DINGUX) 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) { if (!backupSavestates) //If backups are disabled we can just resume normally since we can't restore so stop movie and inform user { FCEU_PrintError("Unable to restore backup, movie playback stopped."); FCEUI_StopMovie(); } return false; } #else if (!backupSavestates) //If backups are disabled we can just resume normally since we can't restore so stop movie and inform user { FCEU_PrintError("Mismatch between savestate's movie and current movie.\ncurrent: %s\nsavestate: %s\nUnable to restore backup, movie playback stopped.\n",currMovieData.guid.toString().c_str(),tempMovieData.guid.toString().c_str()); FCEUI_StopMovie(); } 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) { // currFrameCounter at this point represents the savestate framecount int frame_of_mismatch = CheckTimelines(tempMovieData, currMovieData); if (frame_of_mismatch >= 0) { // Wrong timeline, do apprioriate logic here if (!backupSavestates) //If backups are disabled we can just resume normally since we can't restore so stop movie and inform user { FCEU_PrintError("Error: Savestate not in the same timeline as movie!\nFrame %d branches from current timeline\nUnable to restore backup, movie playback stopped.", frame_of_mismatch); FCEUI_StopMovie(); } else FCEU_PrintError("Error: Savestate not in the same timeline as movie!\nFrame %d branches from current timeline", frame_of_mismatch); return false; } else if (movieMode == MOVIEMODE_FINISHED && currFrameCounter > (int)currMovieData.records.size() && currMovieData.records.size() == tempMovieData.records.size()) { // special case (in MOVIEMODE_FINISHED mode) // allow loading post-movie savestates that were made after finishing current movie } else if (currFrameCounter > (int)currMovieData.records.size()) { // this is future event state, don't allow it //TODO: turn frame counter to red to get attention if (!backupSavestates) //If backups are disabled we can just resume normally since we can't restore so stop movie and inform user { FCEU_PrintError("Error: Savestate is from a frame (%d) after the final frame in the movie (%d). This is not permitted.\nUnable to restore backup, movie playback stopped.", currFrameCounter, currMovieData.records.size()-1); FCEUI_StopMovie(); } else 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; } else if (currFrameCounter > (int)tempMovieData.records.size()) { // this is post-movie savestate, don't allow it //TODO: turn frame counter to red to get attention if (!backupSavestates) //If backups are disabled we can just resume normally since we can't restore so stop movie and inform user { FCEU_PrintError("Error: Savestate is from a frame (%d) after the final frame in the savestated movie (%d). This is not permitted.\nUnable to restore backup, movie playback stopped.", currFrameCounter, tempMovieData.records.size()-1); FCEUI_StopMovie(); } else FCEU_PrintError("Savestate is from a frame (%d) after the final frame in the savestated movie (%d). This is not permitted.", currFrameCounter, tempMovieData.records.size()-1); return false; } else { // Finally, this is a savestate file for this movie movieMode = MOVIEMODE_PLAY; } } else { //Read+Write mode if (currFrameCounter > (int)tempMovieData.records.size()) { //This is a post movie savestate, handle it differently //Replace movie contents but then switch to movie finished mode currMovieData = tempMovieData; openRecordingMovie(curMovieFilename); currMovieData.dump(osRecordingMovie, false/*currMovieData.binaryFlag*/); FinishPlayback(); } else { //truncate before we copy, just to save some time, unless the user selects a full copy option if (!fullSaveStateLoads) //we can only assume this here since we have checked that the frame counter is not greater than the movie data tempMovieData.truncateAt(currFrameCounter); currMovieData = tempMovieData; FCEUMOV_IncrementRerecordCount(); openRecordingMovie(curMovieFilename); currMovieData.dump(osRecordingMovie, false/*currMovieData.binaryFlag*/); movieMode = MOVIEMODE_RECORD; } } } load_successful = true; return true; }
/* * load and write a batch of users */ void IBBL_LargeFile::append_user_batch(int_set::iterator& batch_start, int_set::iterator& batch_end, int& offset, ofstream& user_offset_file, ofstream& user_file) { if (batch_start == batch_end) return; int_ur_mmap user_map; /* go through every movie file */ MovieData d; movie_rating r; user_rating u; int uid; for (int id = 1; id <= NUM_MOVIES; ++id) { // load the movie d.load_from_binary(this, id); // go through each user for (int_set::iterator itr = batch_start; itr != batch_end; ++itr) { uid = *itr; if (d.find_rating_by_id(uid, &r)) { // convert the rating to a user rating and add to that user's list u.rating = r.rating; u.movie_id = id; u.year = r.year; u.month = r.month; u.day = r.day; user_map.insert(make_pair(uid, u)); } } } /* save each user to the file */ int num_user_ratings; int_ur_mmap::iterator begin, end; for (int_set::iterator itr = batch_start; itr != batch_end; ++itr) { uid = *itr; pair<int_ur_mmap::iterator,int_ur_mmap::iterator> ret = user_map.equal_range(uid); // write the number of ratings num_user_ratings = user_map.count(uid); user_file.write((char*)&num_user_ratings, 4); // write and increment the offset user_offset_file.write((char*)&uid, 4); user_offset_file.write((char*)&offset, 4); offset += 4 + num_user_ratings * sizeof(user_rating); // write each rating begin = ret.first; end = ret.second; for (int_ur_mmap::iterator u_itr = begin; u_itr != end; ++u_itr) { u = u_itr->second; user_file.write((char*)&u, sizeof(user_rating)); } } }
bool mov_loadstate(std::istream* is, int size)//std::istream* is { load_successful = false; int cookie; if(read32lemov(&cookie,is) != 1) return false; if(cookie == kNOMO) return true; else if(cookie != kMOVI) return false; size -= 4; if (!movie_readonly && autoMovieBackup && freshMovie) //If auto-backup is on, movie has not been altered this session and the movie is in read+write mode MakeBackupMovie(false); //Backup the movie before the contents get altered, but do not display messages MovieData tempMovieData = MovieData(); std::ios::pos_type curr = is->tellg(); if(!LoadMC2(tempMovieData, is, size, false)) return false; //---------------- //complex TAS logic for loadstate //fully conforms to the savestate logic documented in the Laws of TAS //http://tasvideos.org/LawsOfTAS/OnSavestates.html //---------------- /* Playback or Recording + Read-only * Check that GUID of movie and savestate-movie must match or else error o on error: a message informing that the savestate doesn't belong to this movie. This is a GUID mismatch error. Give user a choice to load it anyway. + failstate: if use declines, loadstate attempt canceled, movie can resume as if not attempted if user has backup savstates enabled else stop movie * Check that movie and savestate-movie are in same timeline. If not then this is a wrong timeline error. o on error: a message informing that the savestate doesn't belong to this movie + failstate: loadstate attempt canceled, movie can resume as if not attempted if user has backup savestates enabled else stop movie * Check that savestate-movie is not greater than movie. If not then this is a future event error and is not allowed in read-only o on error: message informing that the savestate is from a frame after the last frame of the movie + failstate - loadstate attempt cancelled, movie can resume if user has backup savesattes enabled, else stop movie * Check that savestate framcount <= savestate movie length. If not this is a post-movie savestate o on post-movie: See post-movie event section. * All error checks have passed, state will be loaded * Movie contained in the savestate will be discarded * Movie is now in Playback mode Playback, Recording + Read+write * Check that GUID of movie and savestate-movie must match or else error o on error: a message informing that the savestate doesn't belong to this movie. This is a GUID mismatch error. Give user a choice to load it anyway. + failstate: if use declines, loadstate attempt canceled, movie can resume as if not attempted (stop movie if resume fails)canceled, movie can resume if backup savestates enabled else stopmovie * Check that savestate framcount <= savestate movie length. If not this is a post-movie savestate o on post-movie: See post-movie event section. * savestate passed all error checks and will now be loaded in its entirety and replace movie (note: there will be no truncation yet) * current framecount will be set to savestate framecount * on the next frame of input, movie will be truncated to framecount o (note: savestate-movie can be a future event of the movie timeline, or a completely new timeline and it is still allowed) * Rerecord count of movie will be incremented * Movie is now in record mode Post-movie savestate event * Whan a savestate is loaded and is determined that the savestate-movie length is less than the savestate framecount then it is a post-movie savestate. These occur when a savestate was made during Movie Finished mode. * If read+write, the entire savestate movie will be loaded and replace current movie. * If read-only, the savestate movie will be discarded * Mode will be switched to Move Finished * Savestate will be loaded * Current framecount changes to savestate framecount * User will have control of input as if Movie inactive mode */ 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 DisplayMessage("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) { //------------------------------------------------------------- //this code would reload the movie from disk. allegedly it is helpful to hexers, but //it is way too slow with dsm format. so it is only here as a reminder, and in case someone //wants to play with it //------------------------------------------------------------- //{ // fstream fs (curMovieFilename); // if(!LoadMC2(tempMovieData, &fs, INT_MAX, false)) // { // MDFN_PrintError("Failed to reload DSM after loading savestate"); // } // fs.close(); // currMovieData = tempMovieData; //} //------------------------------------------------------------- //if the frame counter is longer than our current movie, then error if(currFrameCounter > (int)currMovieData.records.size()) { MDFN_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; currRerecordCount++; currMovieData.rerecordCount = currRerecordCount; openRecordingMovie(curMovieFilename); currMovieData.dump(osRecordingMovie, false); movieMode = MOVIEMODE_RECORD; } } load_successful = true; freshMovie = false; //// 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; }