/*****************************************************************************\ Snes9x - Portable Super Nintendo Entertainment System (TM) emulator. This file is licensed under the Snes9x License. For further information, consult the LICENSE file in the root directory. \*****************************************************************************/ // Input recording/playback code // (c) Copyright 2004 blip #ifndef __WIN32__ #include #endif #include "snes9x.h" #include "memmap.h" #include "controls.h" #include "snapshot.h" #include "movie.h" #include "language.h" #ifdef NETPLAY_SUPPORT #include "netplay.h" #endif #ifdef __WIN32__ #include #ifndef W_OK #define W_OK 2 #endif #define ftruncate chsize #endif #define SMV_MAGIC 0x1a564d53 // SMV0x1a #define SMV_VERSION 5 #define SMV_HEADER_SIZE 64 #define SMV_EXTRAROMINFO_SIZE 30 #define BUFFER_GROWTH_SIZE 4096 enum MovieState { MOVIE_STATE_NONE = 0, MOVIE_STATE_PLAY, MOVIE_STATE_RECORD }; struct SMovie { enum MovieState State; FILE *File; char Filename[PATH_MAX + 1]; char ROMName[23]; uint32 ROMCRC32; uint32 MovieId; uint32 Version; uint32 SaveStateOffset; uint32 ControllerDataOffset; uint8 ControllersMask; uint8 Opts; uint8 SyncFlags; uint32 MaxFrame; uint32 MaxSample; uint32 CurrentFrame; uint32 CurrentSample; uint32 BytesPerSample; uint32 RerecordCount; bool8 ReadOnly; uint8 PortType[2]; int8 PortIDs[2][4]; uint8 *InputBuffer; uint8 *InputBufferPtr; uint32 InputBufferSize; }; static struct SMovie Movie; static uint8 prevPortType[2]; static int8 prevPortIDs[2][4]; static bool8 prevMouseMaster, prevSuperScopeMaster, prevJustifierMaster, prevMultiPlayer5Master; static uint8 Read8 (uint8 *&); static uint16 Read16 (uint8 *&); static uint32 Read32 (uint8 *&); static void Write8 (uint8, uint8 *&); static void Write16 (uint16, uint8 *&); static void Write32 (uint32, uint8 *&); static void store_previous_settings (void); static void restore_previous_settings (void); static void store_movie_settings (void); static void restore_movie_settings (void); static int bytes_per_sample (void); static void reserve_buffer_space (uint32); static void reset_controllers (void); static void read_frame_controller_data (bool); static void write_frame_controller_data (void); static void flush_movie (void); static void truncate_movie (void); static int read_movie_header (FILE *, SMovie *); static int read_movie_extrarominfo (FILE *, SMovie *); static void write_movie_header (FILE *, SMovie *); static void write_movie_extrarominfo (FILE *, SMovie *); static void change_state (MovieState); // HACK: reduce movie size by not storing changes that can only affect polled input in the movie for these types, // because currently no port sets these types to polling #define SKIPPED_POLLING_PORT_TYPE(x) (((x) == CTL_NONE) || ((x) == CTL_JOYPAD) || ((x) == CTL_MP5)) #ifndef max #define max(a, b) (((a) > (b)) ? (a) : (b)) #endif static uint8 Read8 (uint8 *&ptr) { uint8 v = *ptr++; return (v); } static uint16 Read16 (uint8 *&ptr) { uint16 v = READ_WORD(ptr); ptr += 2; return (v); } static uint32 Read32 (uint8 *&ptr) { uint32 v = READ_DWORD(ptr); ptr += 4; return (v); } static void Write8 (uint8 v, uint8 *&ptr) { *ptr++ = v; } static void Write16 (uint16 v, uint8 *&ptr) { WRITE_WORD(ptr, v); ptr += 2; } static void Write32 (uint32 v, uint8 *&ptr) { WRITE_DWORD(ptr, v); ptr += 4; } static void store_previous_settings (void) { for (int i = 0; i < 2; i++) { enum controllers pt; S9xGetController(i, &pt, &prevPortIDs[i][0], &prevPortIDs[i][1], &prevPortIDs[i][2], &prevPortIDs[i][3]); prevPortType[i] = (uint8) pt; } prevMouseMaster = Settings.MouseMaster; prevSuperScopeMaster = Settings.SuperScopeMaster; prevJustifierMaster = Settings.JustifierMaster; prevMultiPlayer5Master = Settings.MultiPlayer5Master; } static void restore_previous_settings (void) { Settings.MouseMaster = prevMouseMaster; Settings.SuperScopeMaster = prevSuperScopeMaster; Settings.JustifierMaster = prevJustifierMaster; Settings.MultiPlayer5Master = prevMultiPlayer5Master; S9xSetController(0, (enum controllers) prevPortType[0], prevPortIDs[0][0], prevPortIDs[0][1], prevPortIDs[0][2], prevPortIDs[0][3]); S9xSetController(1, (enum controllers) prevPortType[1], prevPortIDs[1][0], prevPortIDs[1][1], prevPortIDs[1][2], prevPortIDs[1][3]); } static void store_movie_settings (void) { for (int i = 0; i < 2; i++) { enum controllers pt; S9xGetController(i, &pt, &Movie.PortIDs[i][0], &Movie.PortIDs[i][1], &Movie.PortIDs[i][2], &Movie.PortIDs[i][3]); Movie.PortType[i] = (uint8) pt; } } static void restore_movie_settings (void) { Settings.MouseMaster = (Movie.PortType[0] == CTL_MOUSE || Movie.PortType[1] == CTL_MOUSE); Settings.SuperScopeMaster = (Movie.PortType[0] == CTL_SUPERSCOPE || Movie.PortType[1] == CTL_SUPERSCOPE); Settings.JustifierMaster = (Movie.PortType[0] == CTL_JUSTIFIER || Movie.PortType[1] == CTL_JUSTIFIER); Settings.MultiPlayer5Master = (Movie.PortType[0] == CTL_MP5 || Movie.PortType[1] == CTL_MP5); S9xSetController(0, (enum controllers) Movie.PortType[0], Movie.PortIDs[0][0], Movie.PortIDs[0][1], Movie.PortIDs[0][2], Movie.PortIDs[0][3]); S9xSetController(1, (enum controllers) Movie.PortType[1], Movie.PortIDs[1][0], Movie.PortIDs[1][1], Movie.PortIDs[1][2], Movie.PortIDs[1][3]); } static int bytes_per_sample (void) { int num_controllers = 0; for (int i = 0; i < 8; i++) { if (Movie.ControllersMask & (1 << i)) num_controllers++; } int bytes = CONTROLLER_DATA_SIZE * num_controllers; for (int p = 0; p < 2; p++) { if (Movie.PortType[p] == CTL_MOUSE) bytes += MOUSE_DATA_SIZE; else if (Movie.PortType[p] == CTL_SUPERSCOPE) bytes += SCOPE_DATA_SIZE; else if (Movie.PortType[p] == CTL_JUSTIFIER) bytes += JUSTIFIER_DATA_SIZE; } return (bytes); } static void reserve_buffer_space (uint32 space_needed) { if (space_needed > Movie.InputBufferSize) { uint32 ptr_offset = Movie.InputBufferPtr - Movie.InputBuffer; uint32 alloc_chunks = space_needed / BUFFER_GROWTH_SIZE; Movie.InputBufferSize = BUFFER_GROWTH_SIZE * (alloc_chunks + 1); Movie.InputBuffer = (uint8 *) realloc(Movie.InputBuffer, Movie.InputBufferSize); Movie.InputBufferPtr = Movie.InputBuffer + ptr_offset; } } static void reset_controllers (void) { for (int i = 0; i < 8; i++) MovieSetJoypad(i, 0); uint8 clearedMouse[MOUSE_DATA_SIZE]; memset(clearedMouse, 0, MOUSE_DATA_SIZE); clearedMouse[4] = 1; uint8 clearedScope[SCOPE_DATA_SIZE]; memset(clearedScope, 0, SCOPE_DATA_SIZE); uint8 clearedJustifier[JUSTIFIER_DATA_SIZE]; memset(clearedJustifier, 0, JUSTIFIER_DATA_SIZE); for (int p = 0; p < 2; p++) { MovieSetMouse(p, clearedMouse, true); MovieSetScope(p, clearedScope); MovieSetJustifier(p, clearedJustifier); } } static void read_frame_controller_data (bool addFrame) { // reset code check if (Movie.InputBufferPtr[0] == 0xff) { bool reset = true; for (int i = 1; i < (int) Movie.BytesPerSample; i++) { if (Movie.InputBufferPtr[i] != 0xff) { reset = false; break; } } if (reset) { Movie.InputBufferPtr += Movie.BytesPerSample; S9xSoftReset(); return; } } for (int i = 0; i < 8; i++) { if (Movie.ControllersMask & (1 << i)) MovieSetJoypad(i, Read16(Movie.InputBufferPtr)); else MovieSetJoypad(i, 0); // pretend the controller is disconnected } for (int p = 0; p < 2; p++) { if (Movie.PortType[p] == CTL_MOUSE) { uint8 buf[MOUSE_DATA_SIZE]; memcpy(buf, Movie.InputBufferPtr, MOUSE_DATA_SIZE); Movie.InputBufferPtr += MOUSE_DATA_SIZE; MovieSetMouse(p, buf, !addFrame); } else if (Movie.PortType[p] == CTL_SUPERSCOPE) { uint8 buf[SCOPE_DATA_SIZE]; memcpy(buf, Movie.InputBufferPtr, SCOPE_DATA_SIZE); Movie.InputBufferPtr += SCOPE_DATA_SIZE; MovieSetScope(p, buf); } else if (Movie.PortType[p] == CTL_JUSTIFIER) { uint8 buf[JUSTIFIER_DATA_SIZE]; memcpy(buf, Movie.InputBufferPtr, JUSTIFIER_DATA_SIZE); Movie.InputBufferPtr += JUSTIFIER_DATA_SIZE; MovieSetJustifier(p, buf); } } } static void write_frame_controller_data (void) { reserve_buffer_space((uint32) (Movie.InputBufferPtr + Movie.BytesPerSample - Movie.InputBuffer)); for (int i = 0; i < 8; i++) { if (Movie.ControllersMask & (1 << i)) Write16(MovieGetJoypad(i), Movie.InputBufferPtr); else MovieSetJoypad(i, 0); // pretend the controller is disconnected } for (int p = 0; p < 2; p++) { if (Movie.PortType[p] == CTL_MOUSE) { uint8 buf[MOUSE_DATA_SIZE]; MovieGetMouse(p, buf); memcpy(Movie.InputBufferPtr, buf, MOUSE_DATA_SIZE); Movie.InputBufferPtr += MOUSE_DATA_SIZE; } else if (Movie.PortType[p] == CTL_SUPERSCOPE) { uint8 buf[SCOPE_DATA_SIZE]; MovieGetScope(p, buf); memcpy(Movie.InputBufferPtr, buf, SCOPE_DATA_SIZE); Movie.InputBufferPtr += SCOPE_DATA_SIZE; } else if (Movie.PortType[p] == CTL_JUSTIFIER) { uint8 buf[JUSTIFIER_DATA_SIZE]; MovieGetJustifier(p, buf); memcpy(Movie.InputBufferPtr, buf, JUSTIFIER_DATA_SIZE); Movie.InputBufferPtr += JUSTIFIER_DATA_SIZE; } } } static void flush_movie (void) { if (!Movie.File) return; fseek(Movie.File, 0, SEEK_SET); write_movie_header(Movie.File, &Movie); fseek(Movie.File, Movie.ControllerDataOffset, SEEK_SET); if (!fwrite(Movie.InputBuffer, 1, Movie.BytesPerSample * (Movie.MaxSample + 1), Movie.File)) printf ("Movie flush failed.\n"); } static void truncate_movie (void) { if (!Movie.File || !Settings.MovieTruncate) return; if (Movie.SaveStateOffset > Movie.ControllerDataOffset) return; if (ftruncate(fileno(Movie.File), Movie.ControllerDataOffset + Movie.BytesPerSample * (Movie.MaxSample + 1))) printf ("Couldn't truncate file.\n"); } static int read_movie_header (FILE *fd, SMovie *movie) { uint32 value; uint8 buf[SMV_HEADER_SIZE], *ptr = buf; if (fread(buf, 1, SMV_HEADER_SIZE, fd) != SMV_HEADER_SIZE) return (WRONG_FORMAT); value = Read32(ptr); if (value != SMV_MAGIC) return (WRONG_FORMAT); value = Read32(ptr); if(value > SMV_VERSION || value < 4) return (WRONG_VERSION); movie->Version = value; movie->MovieId = Read32(ptr); movie->RerecordCount = Read32(ptr); movie->MaxFrame = Read32(ptr); movie->ControllersMask = Read8(ptr); movie->Opts = Read8(ptr); ptr++; movie->SyncFlags = Read8(ptr); movie->SaveStateOffset = Read32(ptr); movie->ControllerDataOffset = Read32(ptr); movie->MaxSample = Read32(ptr); movie->PortType[0] = Read8(ptr); movie->PortType[1] = Read8(ptr); for (int p = 0; p < 2; p++) { for (int i = 0; i < 4; i++) movie->PortIDs[p][i] = Read8(ptr); } if (movie->MaxSample < movie->MaxFrame) movie->MaxSample = movie->MaxFrame; return (SUCCESS); } static int read_movie_extrarominfo (FILE *fd, SMovie *movie) { uint8 buf[SMV_EXTRAROMINFO_SIZE], *ptr = buf; fseek(fd, movie->SaveStateOffset - SMV_EXTRAROMINFO_SIZE, SEEK_SET); if (fread(buf, 1, SMV_EXTRAROMINFO_SIZE, fd) != SMV_EXTRAROMINFO_SIZE) return (WRONG_FORMAT); ptr += 3; // zero bytes movie->ROMCRC32 = Read32(ptr); memcpy(movie->ROMName, (char *) ptr, 23); return (SUCCESS); } static void write_movie_header (FILE *fd, SMovie *movie) { uint8 buf[SMV_HEADER_SIZE], *ptr = buf; memset(buf, 0, sizeof(buf)); Write32(SMV_MAGIC, ptr); Write32(SMV_VERSION, ptr); Write32(movie->MovieId, ptr); Write32(movie->RerecordCount, ptr); Write32(movie->MaxFrame, ptr); Write8(movie->ControllersMask, ptr); Write8(movie->Opts, ptr); ptr++; Write8(movie->SyncFlags, ptr); Write32(movie->SaveStateOffset, ptr); Write32(movie->ControllerDataOffset, ptr); Write32(movie->MaxSample, ptr); Write8(movie->PortType[0], ptr); Write8(movie->PortType[1], ptr); for (int p = 0; p < 2; p++) { for (int i = 0; i < 4; i++) Write8(movie->PortIDs[p][i], ptr); } if (!fwrite(buf, 1, SMV_HEADER_SIZE, fd)) printf ("Couldn't write movie header.\n"); } static void write_movie_extrarominfo (FILE *fd, SMovie *movie) { uint8 buf[SMV_EXTRAROMINFO_SIZE], *ptr = buf; Write8(0, ptr); Write8(0, ptr); Write8(0, ptr); Write32(movie->ROMCRC32, ptr); strncpy((char *) ptr, movie->ROMName, 23); fwrite(buf, 1, SMV_EXTRAROMINFO_SIZE, fd); } static void change_state (MovieState new_state) { if (new_state == Movie.State) return; if (Movie.State == MOVIE_STATE_RECORD) flush_movie(); if (new_state == MOVIE_STATE_NONE) { truncate_movie(); fclose(Movie.File); Movie.File = NULL; if (S9xMoviePlaying() || S9xMovieRecording()) restore_previous_settings(); } Movie.State = new_state; } void S9xMovieFreeze (uint8 **buf, uint32 *size) { if (!S9xMovieActive()) return; uint32 size_needed; uint8 *ptr; size_needed = sizeof(Movie.MovieId) + sizeof(Movie.CurrentFrame) + sizeof(Movie.MaxFrame) + sizeof(Movie.CurrentSample) + sizeof(Movie.MaxSample); size_needed += (uint32) (Movie.BytesPerSample * (Movie.MaxSample + 1)); *size = size_needed; *buf = new uint8[size_needed]; ptr = *buf; if (!ptr) return; Write32(Movie.MovieId, ptr); Write32(Movie.CurrentFrame, ptr); Write32(Movie.MaxFrame, ptr); Write32(Movie.CurrentSample, ptr); Write32(Movie.MaxSample, ptr); memcpy(ptr, Movie.InputBuffer, Movie.BytesPerSample * (Movie.MaxSample + 1)); } int S9xMovieUnfreeze (uint8 *buf, uint32 size) { if (!S9xMovieActive()) return (FILE_NOT_FOUND); if (size < sizeof(Movie.MovieId) + sizeof(Movie.CurrentFrame) + sizeof(Movie.MaxFrame) + sizeof(Movie.CurrentSample) + sizeof(Movie.MaxSample)) return (WRONG_FORMAT); uint8 *ptr = buf; uint32 movie_id = Read32(ptr); uint32 current_frame = Read32(ptr); uint32 max_frame = Read32(ptr); uint32 current_sample = Read32(ptr); uint32 max_sample = Read32(ptr); uint32 space_needed = (Movie.BytesPerSample * (max_sample + 1)); if (current_frame > max_frame || current_sample > max_sample || space_needed > size) return (WRONG_MOVIE_SNAPSHOT); if (Settings.WrongMovieStateProtection) if (movie_id != Movie.MovieId) if (max_frame < Movie.MaxFrame || max_sample < Movie.MaxSample || memcmp(Movie.InputBuffer, ptr, space_needed)) return (WRONG_MOVIE_SNAPSHOT); if (!Movie.ReadOnly) { change_state(MOVIE_STATE_RECORD); Movie.CurrentFrame = current_frame; Movie.MaxFrame = max_frame; Movie.CurrentSample = current_sample; Movie.MaxSample = max_sample; Movie.RerecordCount++; store_movie_settings(); reserve_buffer_space(space_needed); memcpy(Movie.InputBuffer, ptr, space_needed); flush_movie(); fseek(Movie.File, Movie.ControllerDataOffset + (Movie.BytesPerSample * (Movie.CurrentSample + 1)), SEEK_SET); } else { uint32 space_processed = (Movie.BytesPerSample * (current_sample + 1)); if (current_frame > Movie.MaxFrame || current_sample > Movie.MaxSample || memcmp(Movie.InputBuffer, ptr, space_processed)) return (SNAPSHOT_INCONSISTENT); change_state(MOVIE_STATE_PLAY); Movie.CurrentFrame = current_frame; Movie.CurrentSample = current_sample; } Movie.InputBufferPtr = Movie.InputBuffer + (Movie.BytesPerSample * Movie.CurrentSample); read_frame_controller_data(true); return (SUCCESS); } int S9xMovieOpen (const char *filename, bool8 read_only) { FILE *fd; STREAM stream; int result; int fn; if (!(fd = fopen(filename, "rb+"))) { if (!(fd = fopen(filename, "rb"))) return (FILE_NOT_FOUND); else read_only = TRUE; } change_state(MOVIE_STATE_NONE); result = read_movie_header(fd, &Movie); if (result != SUCCESS) { fclose(fd); return (result); } read_movie_extrarominfo(fd, &Movie); fflush(fd); fn = fileno(fd); store_previous_settings(); restore_movie_settings(); lseek(fn, Movie.SaveStateOffset, SEEK_SET); // reopen stream to access as gzipped data stream = REOPEN_STREAM(fn, "rb"); if (!stream) return (FILE_NOT_FOUND); if (Movie.Opts & MOVIE_OPT_FROM_RESET) { S9xReset(); reset_controllers(); result = (READ_STREAM(Memory.SRAM, 0x20000, stream) == 0x20000) ? SUCCESS : WRONG_FORMAT; } else result = S9xUnfreezeFromStream(stream); // do not close stream but close FILE * // (msvcrt will try to close all open FILE *handles on exit - if we do CLOSE_STREAM here // the underlying file will be closed by zlib, causing problems when msvcrt tries to do it) delete stream; fclose(fd); if (result != SUCCESS) return (result); if (!(fd = fopen(filename, "rb+"))) { if (!(fd = fopen(filename, "rb"))) return (FILE_NOT_FOUND); else read_only = TRUE; } if (fseek(fd, Movie.ControllerDataOffset, SEEK_SET)) { fclose(fd); return (WRONG_FORMAT); } Movie.File = fd; Movie.BytesPerSample = bytes_per_sample(); Movie.InputBufferPtr = Movie.InputBuffer; reserve_buffer_space(Movie.BytesPerSample * (Movie.MaxSample + 1)); if (!fread(Movie.InputBufferPtr, 1, Movie.BytesPerSample * (Movie.MaxSample + 1), fd)) { printf ("Failed to read from movie file.\n"); fclose(fd); return (WRONG_FORMAT); } // read "baseline" controller data if (Movie.MaxSample && Movie.MaxFrame) read_frame_controller_data(true); Movie.CurrentFrame = 0; Movie.CurrentSample = 0; Movie.ReadOnly = read_only; strncpy(Movie.Filename, filename, PATH_MAX + 1); Movie.Filename[PATH_MAX] = 0; change_state(MOVIE_STATE_PLAY); S9xUpdateFrameCounter(-1); S9xMessage(S9X_INFO, S9X_MOVIE_INFO, MOVIE_INFO_REPLAY); return (SUCCESS); } int S9xMovieCreate (const char *filename, uint8 controllers_mask, uint8 opts, const wchar_t *metadata, int metadata_length) { FILE *fd; STREAM stream; if (controllers_mask == 0) return (WRONG_FORMAT); if (!(fd = fopen(filename, "wb"))) return (FILE_NOT_FOUND); if (metadata_length > MOVIE_MAX_METADATA) metadata_length = MOVIE_MAX_METADATA; change_state(MOVIE_STATE_NONE); store_previous_settings(); store_movie_settings(); Movie.MovieId = (uint32) time(NULL); Movie.RerecordCount = 0; Movie.MaxFrame = 0; Movie.MaxSample = 0; Movie.SaveStateOffset = SMV_HEADER_SIZE + (sizeof(uint16) * metadata_length) + SMV_EXTRAROMINFO_SIZE; Movie.ControllerDataOffset = 0; Movie.ControllersMask = controllers_mask; Movie.Opts = opts; Movie.SyncFlags = MOVIE_SYNC_DATA_EXISTS | MOVIE_SYNC_HASROMINFO; write_movie_header(fd, &Movie); // convert wchar_t metadata string/array to a uint16 array // XXX: UTF-8 is much better... if (metadata_length > 0) { uint8 meta_buf[sizeof(uint16) * MOVIE_MAX_METADATA]; for (int i = 0; i < metadata_length; i++) { uint16 c = (uint16) metadata[i]; meta_buf[i * 2] = (uint8) (c & 0xff); meta_buf[i * 2 + 1] = (uint8) ((c >> 8) & 0xff); } if (!fwrite(meta_buf, sizeof(uint16), metadata_length, fd)) printf ("Failed writing movie metadata.\n"); } Movie.ROMCRC32 = Memory.ROMCRC32; strncpy(Movie.ROMName, Memory.RawROMName, 23); write_movie_extrarominfo(fd, &Movie); fclose(fd); stream = OPEN_STREAM(filename, "ab"); if (!stream) return (FILE_NOT_FOUND); if (opts & MOVIE_OPT_FROM_RESET) { S9xReset(); reset_controllers(); WRITE_STREAM(Memory.SRAM, 0x20000, stream); } else S9xFreezeToStream(stream); CLOSE_STREAM(stream); if (!(fd = fopen(filename, "rb+"))) return (FILE_NOT_FOUND); fseek(fd, 0, SEEK_END); Movie.ControllerDataOffset = (uint32) ftell(fd); // 16-byte align the controller input, for hex-editing friendliness if nothing else while (Movie.ControllerDataOffset % 16) { fputc(0xcc, fd); // arbitrary Movie.ControllerDataOffset++; } // write "baseline" controller data Movie.File = fd; Movie.BytesPerSample = bytes_per_sample(); Movie.InputBufferPtr = Movie.InputBuffer; write_frame_controller_data(); Movie.CurrentFrame = 0; Movie.CurrentSample = 0; Movie.ReadOnly = false; strncpy(Movie.Filename, filename, PATH_MAX + 1); Movie.Filename[PATH_MAX] = 0; change_state(MOVIE_STATE_RECORD); S9xUpdateFrameCounter(-1); S9xMessage(S9X_INFO, S9X_MOVIE_INFO, MOVIE_INFO_RECORD); return (SUCCESS); } int S9xMovieGetInfo (const char *filename, struct MovieInfo *info) { FILE *fd; SMovie local_movie; int metadata_length; int result, i; flush_movie(); memset(info, 0, sizeof(*info)); if (!(fd = fopen(filename, "rb"))) return (FILE_NOT_FOUND); result = read_movie_header(fd, &local_movie); if (result != SUCCESS) { fclose(fd); return (result); } info->TimeCreated = (time_t) local_movie.MovieId; info->Version = local_movie.Version; info->Opts = local_movie.Opts; info->SyncFlags = local_movie.SyncFlags; info->ControllersMask = local_movie.ControllersMask; info->RerecordCount = local_movie.RerecordCount; info->LengthFrames = local_movie.MaxFrame; info->LengthSamples = local_movie.MaxSample; info->PortType[0] = local_movie.PortType[0]; info->PortType[1] = local_movie.PortType[1]; if (local_movie.SaveStateOffset > SMV_HEADER_SIZE) { uint8 meta_buf[sizeof(uint16) * MOVIE_MAX_METADATA]; int curRomInfoSize = (local_movie.SyncFlags & MOVIE_SYNC_HASROMINFO) ? SMV_EXTRAROMINFO_SIZE : 0; metadata_length = ((int) local_movie.SaveStateOffset - SMV_HEADER_SIZE - curRomInfoSize) / sizeof(uint16); metadata_length = (metadata_length >= MOVIE_MAX_METADATA) ? MOVIE_MAX_METADATA - 1 : metadata_length; metadata_length = (int) fread(meta_buf, sizeof(uint16), metadata_length, fd); for (i = 0; i < metadata_length; i++) { uint16 c = meta_buf[i * 2] | (meta_buf[i * 2 + 1] << 8); info->Metadata[i] = (wchar_t) c; } info->Metadata[i] = '\0'; } else info->Metadata[0] = '\0'; read_movie_extrarominfo(fd, &local_movie); info->ROMCRC32 = local_movie.ROMCRC32; strncpy(info->ROMName, local_movie.ROMName, 23); fclose(fd); if ((fd = fopen(filename, "r+")) == NULL) info->ReadOnly = true; else fclose(fd); return (SUCCESS); } void S9xMovieUpdate (bool addFrame) { switch (Movie.State) { case MOVIE_STATE_PLAY: { if (Movie.CurrentFrame >= Movie.MaxFrame || Movie.CurrentSample >= Movie.MaxSample) { change_state(MOVIE_STATE_NONE); S9xMessage(S9X_INFO, S9X_MOVIE_INFO, MOVIE_INFO_END); return; } else { if (addFrame) S9xUpdateFrameCounter(); else if (SKIPPED_POLLING_PORT_TYPE(Movie.PortType[0]) && SKIPPED_POLLING_PORT_TYPE(Movie.PortType[1])) return; read_frame_controller_data(addFrame); Movie.CurrentSample++; if (addFrame) Movie.CurrentFrame++; } break; } case MOVIE_STATE_RECORD: { if (addFrame) S9xUpdateFrameCounter(); else if (SKIPPED_POLLING_PORT_TYPE(Movie.PortType[0]) && SKIPPED_POLLING_PORT_TYPE(Movie.PortType[1])) return; write_frame_controller_data(); Movie.MaxSample = ++Movie.CurrentSample; if (addFrame) Movie.MaxFrame = ++Movie.CurrentFrame; if (!fwrite((Movie.InputBufferPtr - Movie.BytesPerSample), 1, Movie.BytesPerSample, Movie.File)) printf ("Error writing control data.\n"); break; } default: { if (addFrame) S9xUpdateFrameCounter(); break; } } } void S9xMovieUpdateOnReset (void) { if (Movie.State == MOVIE_STATE_RECORD) { reserve_buffer_space((uint32) (Movie.InputBufferPtr + Movie.BytesPerSample - Movie.InputBuffer)); memset(Movie.InputBufferPtr, 0xFF, Movie.BytesPerSample); Movie.InputBufferPtr += Movie.BytesPerSample; Movie.MaxSample = ++Movie.CurrentSample; Movie.MaxFrame = ++Movie.CurrentFrame; if (!fwrite((Movie.InputBufferPtr - Movie.BytesPerSample), 1, Movie.BytesPerSample, Movie.File)) printf ("Failed writing reset data.\n"); } } void S9xMovieInit (void) { memset(&Movie, 0, sizeof(Movie)); Movie.State = MOVIE_STATE_NONE; } void S9xMovieStop (bool8 suppress_message) { if (Movie.State != MOVIE_STATE_NONE) { change_state(MOVIE_STATE_NONE); if (!suppress_message) S9xMessage(S9X_INFO, S9X_MOVIE_INFO, MOVIE_INFO_STOP); } } void S9xMovieShutdown (void) { if (S9xMovieActive()) S9xMovieStop(TRUE); } bool8 S9xMovieActive (void) { return (Movie.State != MOVIE_STATE_NONE); } bool8 S9xMoviePlaying (void) { return (Movie.State == MOVIE_STATE_PLAY); } bool8 S9xMovieRecording (void) { return (Movie.State == MOVIE_STATE_RECORD); } uint8 S9xMovieControllers (void) { return (Movie.ControllersMask); } bool8 S9xMovieReadOnly (void) { if (!S9xMovieActive()) return (FALSE); return (Movie.ReadOnly); } uint32 S9xMovieGetId (void) { if (!S9xMovieActive()) return (0); return (Movie.MovieId); } uint32 S9xMovieGetLength (void) { if (!S9xMovieActive()) return (0); return (Movie.MaxFrame); } uint32 S9xMovieGetFrameCounter (void) { if (!S9xMovieActive()) return (0); return (Movie.CurrentFrame); } void S9xMovieToggleRecState (void) { Movie.ReadOnly = !Movie.ReadOnly; if (Movie.ReadOnly) S9xMessage(S9X_INFO, S9X_MOVIE_INFO, "Movie is now read-only."); else S9xMessage(S9X_INFO, S9X_MOVIE_INFO, "Movie is now read+write."); } void S9xMovieToggleFrameDisplay (void) { Settings.DisplayMovieFrame = !Settings.DisplayMovieFrame; S9xReRefresh(); } void S9xUpdateFrameCounter (int offset) { extern bool8 pad_read; offset++; if (!Settings.DisplayMovieFrame) *GFX.FrameDisplayString = 0; else if (Movie.State == MOVIE_STATE_RECORD) sprintf(GFX.FrameDisplayString, "Recording frame: %d%s", max(0, (int) (Movie.CurrentFrame + offset)), pad_read || !Settings.MovieNotifyIgnored ? "" : " (ignored)"); else if (Movie.State == MOVIE_STATE_PLAY) sprintf(GFX.FrameDisplayString, "Playing frame: %d / %d", max(0, (int) (Movie.CurrentFrame + offset)), Movie.MaxFrame); #ifdef NETPLAY_SUPPORT else if (Settings.NetPlay) sprintf(GFX.FrameDisplayString, "%s frame: %d", Settings.NetPlayServer ? "Server" : "Client", max(0, (int) (NetPlay.FrameCount + offset))); #endif }