rlm@1: #include rlm@1: #include rlm@1: #include rlm@1: #include rlm@1: #include rlm@1: #include rlm@1: rlm@1: using namespace std; rlm@1: rlm@1: #ifdef HAVE_STRINGS_H rlm@1: # include rlm@1: #endif rlm@1: rlm@1: #if defined(__unix) || defined(__linux) || defined(__sun) || defined(__DJGPP) rlm@1: # include rlm@1: # include rlm@1: # include rlm@1: # include rlm@1: # define stricmp strcasecmp rlm@1: // FIXME: this is wrong, but we don't want buffer overflow rlm@1: # if defined _MAX_PATH rlm@1: # undef _MAX_PATH rlm@1: //# define _MAX_PATH 128 rlm@1: # define _MAX_PATH 260 rlm@1: # endif rlm@1: #endif rlm@1: rlm@1: #ifdef WIN32 rlm@1: # include rlm@1: # ifndef W_OK rlm@1: # define W_OK 2 rlm@1: # endif rlm@1: # define ftruncate chsize rlm@1: #endif rlm@1: rlm@1: #include "movie.h" rlm@1: #include "System.h" rlm@1: #include "../gba/GBA.h" rlm@1: #include "../gba/GBAGlobals.h" rlm@1: #include "../gba/RTC.h" rlm@1: #include "../gb/GB.h" rlm@1: #include "../gb/gbGlobals.h" rlm@1: #include "inputGlobal.h" rlm@1: #include "unzip.h" rlm@1: #include "Util.h" rlm@1: rlm@1: #include "vbalua.h" rlm@1: rlm@1: #if (defined(WIN32) && !defined(SDL)) rlm@1: # include "../win32/stdafx.h" rlm@1: # include "../win32/MainWnd.h" rlm@1: # include "../win32/VBA.h" rlm@1: # include "../win32/WinMiscUtil.h" rlm@1: #endif rlm@1: rlm@1: extern int emulating; // from system.cpp rlm@1: extern u16 currentButtons[4]; // from System.cpp rlm@1: extern u16 lastKeys; rlm@1: rlm@1: SMovie Movie; rlm@1: bool loadingMovie = false; rlm@1: rlm@1: // probably bad idea to have so many global variables, but I hate to recompile almost everything after editing VBA.h rlm@1: bool autoConvertMovieWhenPlaying = false; rlm@1: rlm@1: static u16 initialInputs[4] = { 0 }; rlm@1: rlm@1: static bool resetSignaled = false; rlm@1: static bool resetSignaledLast = false; rlm@1: rlm@1: static int prevEmulatorType, prevBorder, prevWinBorder, prevBorderAuto; rlm@1: rlm@1: // little-endian integer pop/push functions: rlm@1: static inline uint32 Pop32(const uint8 * &ptr) rlm@1: { rlm@1: uint32 v = (ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24)); rlm@1: ptr += 4; rlm@1: return v; rlm@1: } rlm@1: rlm@1: static inline uint16 Pop16(const uint8 * &ptr) /* const version */ rlm@1: { rlm@1: uint16 v = (ptr[0] | (ptr[1] << 8)); rlm@1: ptr += 2; rlm@1: return v; rlm@1: } rlm@1: rlm@1: static inline uint16 Pop16(uint8 * &ptr) /* non-const version */ rlm@1: { rlm@1: uint16 v = (ptr[0] | (ptr[1] << 8)); rlm@1: ptr += 2; rlm@1: return v; rlm@1: } rlm@1: rlm@1: static inline uint8 Pop8(const uint8 * &ptr) rlm@1: { rlm@1: return *(ptr)++; rlm@1: } rlm@1: rlm@1: static inline void Push32(uint32 v, uint8 * &ptr) rlm@1: { rlm@1: ptr[0] = (uint8)(v & 0xff); rlm@1: ptr[1] = (uint8)((v >> 8) & 0xff); rlm@1: ptr[2] = (uint8)((v >> 16) & 0xff); rlm@1: ptr[3] = (uint8)((v >> 24) & 0xff); rlm@1: ptr += 4; rlm@1: } rlm@1: rlm@1: static inline void Push16(uint16 v, uint8 * &ptr) rlm@1: { rlm@1: ptr[0] = (uint8)(v & 0xff); rlm@1: ptr[1] = (uint8)((v >> 8) & 0xff); rlm@1: ptr += 2; rlm@1: } rlm@1: rlm@1: static inline void Push8(uint8 v, uint8 * &ptr) rlm@1: { rlm@1: *ptr++ = v; rlm@1: } rlm@1: rlm@1: // little-endian integer read/write functions: rlm@1: static inline uint16 Read16(const uint8 *ptr) rlm@1: { rlm@1: return ptr[0] | (ptr[1] << 8); rlm@1: } rlm@1: rlm@1: static inline void Write16(uint16 v, uint8 *ptr) rlm@1: { rlm@1: ptr[0] = uint8(v & 0xff); rlm@1: ptr[1] = uint8((v >> 8) & 0xff); rlm@1: } rlm@1: rlm@1: static long file_length(FILE *fp) rlm@1: { rlm@1: long cur_pos = ftell(fp); rlm@1: fseek(fp, 0, SEEK_END); rlm@1: long length = ftell(fp); rlm@1: fseek(fp, cur_pos, SEEK_SET); rlm@1: return length; rlm@1: } rlm@1: rlm@1: static int bytes_per_frame(SMovie &mov) rlm@1: { rlm@1: int num_controllers = 0; rlm@1: rlm@1: for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i) rlm@1: if (mov.header.controllerFlags & MOVIE_CONTROLLER(i)) rlm@1: ++num_controllers; rlm@1: rlm@1: return CONTROLLER_DATA_SIZE * num_controllers; rlm@1: } rlm@1: rlm@1: static void reserve_buffer_space(uint32 space_needed) rlm@1: { rlm@1: if (space_needed > Movie.inputBufferSize) rlm@1: { rlm@1: uint32 ptr_offset = Movie.inputBufferPtr - Movie.inputBuffer; rlm@1: uint32 alloc_chunks = (space_needed - 1) / BUFFER_GROWTH_SIZE + 1; rlm@1: uint32 old_size = Movie.inputBufferSize; rlm@1: Movie.inputBufferSize = BUFFER_GROWTH_SIZE * alloc_chunks; rlm@1: Movie.inputBuffer = (uint8 *)realloc(Movie.inputBuffer, Movie.inputBufferSize); rlm@1: // FIXME: this only fixes the random input problem during dma-frame-skip, but not the skip rlm@1: memset(Movie.inputBuffer + old_size, 0, Movie.inputBufferSize - old_size); rlm@1: Movie.inputBufferPtr = Movie.inputBuffer + ptr_offset; rlm@1: } rlm@1: } rlm@1: rlm@1: static int read_movie_header(FILE *file, SMovie &movie) rlm@1: { rlm@1: assert(file != NULL); rlm@1: assert(VBM_HEADER_SIZE == sizeof(SMovieFileHeader)); // sanity check on the header type definition rlm@1: rlm@1: uint8 headerData [VBM_HEADER_SIZE]; rlm@1: rlm@1: if (fread(headerData, 1, VBM_HEADER_SIZE, file) != VBM_HEADER_SIZE) rlm@1: return MOVIE_WRONG_FORMAT; // if we failed to read in all VBM_HEADER_SIZE bytes of the header rlm@1: rlm@1: const uint8 * ptr = headerData; rlm@1: SMovieFileHeader &header = movie.header; rlm@1: rlm@1: header.magic = Pop32(ptr); rlm@1: if (header.magic != VBM_MAGIC) rlm@1: return MOVIE_WRONG_FORMAT; rlm@1: rlm@1: header.version = Pop32(ptr); rlm@1: if (header.version != VBM_VERSION) rlm@1: return MOVIE_WRONG_VERSION; rlm@1: rlm@1: header.uid = Pop32(ptr); rlm@1: header.length_frames = Pop32(ptr) + 1; // HACK: add 1 to the length for compatibility rlm@1: header.rerecord_count = Pop32(ptr); rlm@1: rlm@1: header.startFlags = Pop8(ptr); rlm@1: header.controllerFlags = Pop8(ptr); rlm@1: header.typeFlags = Pop8(ptr); rlm@1: header.optionFlags = Pop8(ptr); rlm@1: rlm@1: header.saveType = Pop32(ptr); rlm@1: header.flashSize = Pop32(ptr); rlm@1: header.gbEmulatorType = Pop32(ptr); rlm@1: rlm@1: for (int i = 0; i < 12; i++) rlm@1: header.romTitle[i] = Pop8(ptr); rlm@1: rlm@1: header.minorVersion = Pop8(ptr); rlm@1: rlm@1: header.romCRC = Pop8(ptr); rlm@1: header.romOrBiosChecksum = Pop16(ptr); rlm@1: header.romGameCode = Pop32(ptr); rlm@1: rlm@1: header.offset_to_savestate = Pop32(ptr); rlm@1: header.offset_to_controller_data = Pop32(ptr); rlm@1: rlm@1: return MOVIE_SUCCESS; rlm@1: } rlm@1: rlm@1: static void write_movie_header(FILE *file, const SMovie &movie) rlm@1: { rlm@1: assert(ftell(file) == 0); // we assume file points to beginning of movie file rlm@1: rlm@1: uint8 headerData [VBM_HEADER_SIZE]; rlm@1: uint8 *ptr = headerData; rlm@1: const SMovieFileHeader &header = movie.header; rlm@1: rlm@1: Push32(header.magic, ptr); rlm@1: Push32(header.version, ptr); rlm@1: rlm@1: Push32(header.uid, ptr); rlm@1: Push32(header.length_frames - 1, ptr); // HACK: reduce the length by 1 for compatibility with certain faulty old tools rlm@1: // like TME rlm@1: Push32(header.rerecord_count, ptr); rlm@1: rlm@1: Push8(header.startFlags, ptr); rlm@1: Push8(header.controllerFlags, ptr); rlm@1: Push8(header.typeFlags, ptr); rlm@1: Push8(header.optionFlags, ptr); rlm@1: rlm@1: Push32(header.saveType, ptr); rlm@1: Push32(header.flashSize, ptr); rlm@1: Push32(header.gbEmulatorType, ptr); rlm@1: rlm@1: for (int i = 0; i < 12; ++i) rlm@1: Push8(header.romTitle[i], ptr); rlm@1: rlm@1: Push8(header.minorVersion, ptr); rlm@1: rlm@1: Push8(header.romCRC, ptr); rlm@1: Push16(header.romOrBiosChecksum, ptr); rlm@1: Push32(header.romGameCode, ptr); rlm@1: rlm@1: Push32(header.offset_to_savestate, ptr); rlm@1: Push32(header.offset_to_controller_data, ptr); rlm@1: rlm@1: fwrite(headerData, 1, VBM_HEADER_SIZE, file); rlm@1: } rlm@1: rlm@1: static void flush_movie_header() rlm@1: { rlm@1: assert(Movie.file != 0 && "logical error!"); rlm@1: if (!Movie.file) rlm@1: return; rlm@1: rlm@1: long originalPos = ftell(Movie.file); rlm@1: rlm@1: // (over-)write the header rlm@1: fseek(Movie.file, 0, SEEK_SET); rlm@1: write_movie_header(Movie.file, Movie); rlm@1: rlm@1: fflush(Movie.file); rlm@1: rlm@1: fseek(Movie.file, originalPos, SEEK_SET); rlm@1: } rlm@1: rlm@1: static void flush_movie_frames() rlm@1: { rlm@1: assert(Movie.file && "logical error!"); rlm@1: if (!Movie.file) rlm@1: return; rlm@1: rlm@1: long originalPos = ftell(Movie.file); rlm@1: rlm@1: // overwrite the controller data rlm@1: fseek(Movie.file, Movie.header.offset_to_controller_data, SEEK_SET); rlm@1: fwrite(Movie.inputBuffer, 1, Movie.bytesPerFrame * Movie.header.length_frames, Movie.file); rlm@1: rlm@1: fflush(Movie.file); rlm@1: rlm@1: fseek(Movie.file, originalPos, SEEK_SET); rlm@1: } rlm@1: rlm@1: static void truncate_movie(long length) rlm@1: { rlm@1: // truncate movie to length rlm@1: // NOTE: it's certain that the savestate block is never after the rlm@1: // controller data block, because the VBM format decrees it. rlm@1: rlm@1: assert(Movie.file && length >= 0); rlm@1: if (!Movie.file || length < 0) rlm@1: return; rlm@1: rlm@1: assert(Movie.header.offset_to_savestate <= Movie.header.offset_to_controller_data); rlm@1: if (Movie.header.offset_to_savestate > Movie.header.offset_to_controller_data) rlm@1: return; rlm@1: rlm@1: Movie.header.length_frames = length; rlm@1: flush_movie_header(); rlm@1: const long truncLen = long(Movie.header.offset_to_controller_data + Movie.bytesPerFrame * length); rlm@1: if (file_length(Movie.file) != truncLen) rlm@1: { rlm@1: ftruncate(fileno(Movie.file), truncLen); rlm@1: } rlm@1: } rlm@1: rlm@1: static void remember_input_state() rlm@1: { rlm@1: for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i) rlm@1: { rlm@1: if (systemCartridgeType == 0) rlm@1: { rlm@1: initialInputs[i] = u16(~P1 & 0x03FF); rlm@1: } rlm@1: else rlm@1: { rlm@1: extern int32 gbJoymask[4]; rlm@1: for (int i = 0; i < 4; ++i) rlm@1: initialInputs[i] = u16(gbJoymask[i] & 0xFFFF); rlm@1: } rlm@1: } rlm@1: } rlm@1: rlm@1: static void change_state(MovieState new_state) rlm@1: { rlm@1: #if (defined(WIN32) && !defined(SDL)) rlm@1: theApp.frameSearching = false; rlm@1: theApp.frameSearchSkipping = false; rlm@1: #endif rlm@1: rlm@1: if (new_state == MOVIE_STATE_NONE) rlm@1: { rlm@1: Movie.pauseFrame = -1; rlm@1: rlm@1: if (Movie.state == MOVIE_STATE_NONE) rlm@1: return; rlm@1: rlm@1: truncate_movie(Movie.header.length_frames); rlm@1: rlm@1: fclose(Movie.file); rlm@1: Movie.file = NULL; rlm@1: Movie.currentFrame = 0; rlm@1: #if (defined(WIN32) && !defined(SDL)) rlm@1: // undo changes to border settings rlm@1: { rlm@1: gbBorderOn = prevBorder; rlm@1: theApp.winGbBorderOn = prevWinBorder; rlm@1: gbBorderAutomatic = prevBorderAuto; rlm@1: systemGbBorderOn(); rlm@1: } rlm@1: #endif rlm@1: gbEmulatorType = prevEmulatorType; rlm@1: rlm@1: extern int32 gbDMASpeedVersion; rlm@1: gbDMASpeedVersion = 1; rlm@1: rlm@1: extern int32 gbEchoRAMFixOn; rlm@1: gbEchoRAMFixOn = 1; rlm@1: rlm@1: gbNullInputHackTempEnabled = gbNullInputHackEnabled; rlm@1: rlm@1: if (Movie.inputBuffer) rlm@1: { rlm@1: free(Movie.inputBuffer); rlm@1: Movie.inputBuffer = NULL; rlm@1: } rlm@1: } rlm@1: else if (new_state == MOVIE_STATE_PLAY) rlm@1: { rlm@1: assert(Movie.file); rlm@1: rlm@1: // this would cause problems if not dealt with rlm@1: if (Movie.currentFrame >= Movie.header.length_frames) rlm@1: { rlm@1: new_state = MOVIE_STATE_END; rlm@1: Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * Movie.header.length_frames; rlm@1: } rlm@1: } rlm@1: else if (new_state == MOVIE_STATE_RECORD) rlm@1: { rlm@1: assert(Movie.file); rlm@1: rlm@1: // this would cause problems if not dealt with rlm@1: if (Movie.currentFrame > Movie.header.length_frames) rlm@1: { rlm@1: new_state = MOVIE_STATE_END; rlm@1: Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * Movie.header.length_frames; rlm@1: } rlm@1: rlm@1: fseek(Movie.file, Movie.header.offset_to_controller_data + Movie.bytesPerFrame * Movie.currentFrame, SEEK_SET); rlm@1: } rlm@1: rlm@1: if (new_state == MOVIE_STATE_END && Movie.state != MOVIE_STATE_END) rlm@1: { rlm@1: #if defined(SDL) rlm@1: systemClearJoypads(); rlm@1: #endif rlm@1: systemScreenMessage("Movie end"); rlm@1: } rlm@1: rlm@1: Movie.state = new_state; rlm@1: rlm@1: // checking for movie end rlm@1: bool willPause = false; rlm@1: rlm@1: // if the movie's been set to pause at a certain frame rlm@1: if (Movie.state != MOVIE_STATE_NONE && Movie.pauseFrame >= 0 && Movie.currentFrame == (uint32)Movie.pauseFrame) rlm@1: { rlm@1: Movie.pauseFrame = -1; rlm@1: willPause = true; rlm@1: } rlm@1: rlm@1: if (Movie.state == MOVIE_STATE_END) rlm@1: { rlm@1: if (Movie.currentFrame == Movie.header.length_frames) rlm@1: { rlm@1: #if (defined(WIN32) && !defined(SDL)) rlm@1: if (theApp.movieOnEndPause) rlm@1: { rlm@1: willPause = true; rlm@1: } rlm@1: #else rlm@1: // SDL FIXME rlm@1: #endif rlm@1: rlm@1: #if (defined(WIN32) && !defined(SDL)) rlm@1: switch (theApp.movieOnEndBehavior) rlm@1: { rlm@1: case 1: rlm@1: // the old behavior rlm@1: //VBAMovieRestart(); rlm@1: break; rlm@1: case 2: rlm@1: #else rlm@1: // SDL FIXME rlm@1: #endif rlm@1: if (Movie.RecordedThisSession) rlm@1: { rlm@1: // if user has been recording this movie since the last time it started playing, rlm@1: // they probably don't want the movie to end now during playback, rlm@1: // so switch back to recording when it reaches the end rlm@1: VBAMovieSwitchToRecording(); rlm@1: systemScreenMessage("Recording resumed"); rlm@1: willPause = true; rlm@1: } rlm@1: #if (defined(WIN32) && !defined(SDL)) rlm@1: break; rlm@1: case 3: rlm@1: // keep open rlm@1: break; rlm@1: case 0: rlm@1: // fall through rlm@1: default: rlm@1: // close movie rlm@1: //VBAMovieStop(false); rlm@1: break; rlm@1: } rlm@1: #else rlm@1: // SDL FIXME rlm@1: #endif rlm@1: } rlm@1: #if 1 rlm@1: else if (Movie.currentFrame > Movie.header.length_frames) rlm@1: { rlm@1: #if (defined(WIN32) && !defined(SDL)) rlm@1: switch (theApp.movieOnEndBehavior) rlm@1: { rlm@1: case 1: rlm@1: // FIXME: this should be delayed till the current frame ends rlm@1: VBAMovieRestart(); rlm@1: break; rlm@1: case 2: rlm@1: // nothing rlm@1: break; rlm@1: case 3: rlm@1: // keep open rlm@1: break; rlm@1: case 0: rlm@1: // fall through rlm@1: default: rlm@1: // close movie rlm@1: VBAMovieStop(false); rlm@1: break; rlm@1: } rlm@1: #else rlm@1: // SDLFIXME rlm@1: #endif rlm@1: } rlm@1: #endif rlm@1: } // end if (Movie.state == MOVIE_STATE_END) rlm@1: rlm@1: if (willPause) rlm@1: { rlm@1: systemSetPause(true); rlm@1: } rlm@1: } rlm@1: rlm@1: void VBAMovieInit() rlm@1: { rlm@1: memset(&Movie, 0, sizeof(Movie)); rlm@1: Movie.state = MOVIE_STATE_NONE; rlm@1: Movie.pauseFrame = -1; rlm@1: rlm@1: resetSignaled = false; rlm@1: resetSignaledLast = false; rlm@1: } rlm@1: rlm@1: void VBAMovieGetRomInfo(const SMovie &movieInfo, char romTitle [12], uint32 &romGameCode, uint16 &checksum, uint8 &crc) rlm@1: { rlm@1: if (systemCartridgeType == 0) // GBA rlm@1: { rlm@1: extern u8 *bios, *rom; rlm@1: memcpy(romTitle, &rom[0xa0], 12); // GBA TITLE rlm@1: memcpy(&romGameCode, &rom[0xac], 4); // GBA ROM GAME CODE rlm@1: if ((movieInfo.header.optionFlags & MOVIE_SETTING_USEBIOSFILE) != 0) rlm@1: checksum = utilCalcBIOSChecksum(bios, 4); // GBA BIOS CHECKSUM rlm@1: else rlm@1: checksum = 0; rlm@1: crc = rom[0xbd]; // GBA ROM CRC rlm@1: } rlm@1: else // non-GBA rlm@1: { rlm@1: extern u8 *gbRom; rlm@1: memcpy(romTitle, &gbRom[0x134], 12); // GB TITLE (note this can be 15 but is truncated to 12) rlm@1: romGameCode = (uint32)gbRom[0x146]; // GB ROM UNIT CODE rlm@1: rlm@1: checksum = (gbRom[0x14e] << 8) | gbRom[0x14f]; // GB ROM CHECKSUM, read from big-endian rlm@1: crc = gbRom[0x14d]; // GB ROM CRC rlm@1: } rlm@1: } rlm@1: rlm@1: #ifdef SDL rlm@1: static void GetBatterySaveName(char *buffer) rlm@1: { rlm@1: extern char batteryDir[2048], filename[2048]; // from SDL.cpp rlm@1: extern char *sdlGetFilename(char *name); // from SDL.cpp rlm@1: if (batteryDir[0]) rlm@1: sprintf(buffer, "%s/%s.sav", batteryDir, sdlGetFilename(filename)); rlm@1: else rlm@1: sprintf(buffer, "%s.sav", filename); rlm@1: } rlm@1: rlm@1: #endif rlm@1: rlm@1: static void SetPlayEmuSettings() rlm@1: { rlm@1: prevEmulatorType = gbEmulatorType; rlm@1: gbEmulatorType = Movie.header.gbEmulatorType; rlm@1: rlm@1: #if (defined(WIN32) && !defined(SDL)) rlm@1: // theApp.removeIntros = false; rlm@1: theApp.skipBiosFile = (Movie.header.optionFlags & MOVIE_SETTING_SKIPBIOSFILE) != 0; rlm@1: theApp.useBiosFile = (Movie.header.optionFlags & MOVIE_SETTING_USEBIOSFILE) != 0; rlm@1: #else rlm@1: extern int saveType, sdlRtcEnable, sdlFlashSize; // from SDL.cpp rlm@1: extern bool8 useBios, skipBios, removeIntros; // from SDL.cpp rlm@1: useBios = (Movie.header.optionFlags & MOVIE_SETTING_USEBIOSFILE) != 0; rlm@1: skipBios = (Movie.header.optionFlags & MOVIE_SETTING_SKIPBIOSFILE) != 0; rlm@1: removeIntros = false /*(Movie.header.optionFlags & MOVIE_SETTING_REMOVEINTROS) != 0*/; rlm@1: #endif rlm@1: rlm@1: extern void SetPrefetchHack(bool); rlm@1: if (systemCartridgeType == 0) // lag disablement applies only to GBA rlm@1: SetPrefetchHack((Movie.header.optionFlags & MOVIE_SETTING_LAGHACK) != 0); rlm@1: rlm@1: gbNullInputHackTempEnabled = ((Movie.header.optionFlags & MOVIE_SETTING_GBINPUTHACK) != 0); rlm@1: rlm@1: // some GB/GBC games depend on the sound rate, so just use the highest one rlm@1: systemSoundSetQuality(1); rlm@1: useOldFrameTiming = false; rlm@1: rlm@1: extern int32 gbDMASpeedVersion; rlm@1: if ((Movie.header.optionFlags & MOVIE_SETTING_GBCFF55FIX) != 0) rlm@1: gbDMASpeedVersion = 1; rlm@1: else rlm@1: gbDMASpeedVersion = 0; // old CGB HDMA5 timing was used rlm@1: rlm@1: extern int32 gbEchoRAMFixOn; rlm@1: if ((Movie.header.optionFlags & MOVIE_SETTING_GBECHORAMFIX) != 0) rlm@1: gbEchoRAMFixOn = 1; rlm@1: else rlm@1: gbEchoRAMFixOn = 0; rlm@1: rlm@1: #if (defined(WIN32) && !defined(SDL)) rlm@1: rtcEnable((Movie.header.optionFlags & MOVIE_SETTING_RTCENABLE) != 0); rlm@1: theApp.winSaveType = Movie.header.saveType; rlm@1: theApp.winFlashSize = Movie.header.flashSize; rlm@1: rlm@1: prevBorder = gbBorderOn; rlm@1: prevWinBorder = theApp.winGbBorderOn; rlm@1: prevBorderAuto = gbBorderAutomatic; rlm@1: if ((gbEmulatorType == 2 || gbEmulatorType == 5) rlm@1: && !theApp.hideMovieBorder) // games played in SGB mode can have a border rlm@1: { rlm@1: gbBorderOn = true; rlm@1: theApp.winGbBorderOn = true; rlm@1: gbBorderAutomatic = false; rlm@1: } rlm@1: else rlm@1: { rlm@1: gbBorderOn = false; rlm@1: theApp.winGbBorderOn = false; rlm@1: gbBorderAutomatic = false; rlm@1: if (theApp.hideMovieBorder) rlm@1: { rlm@1: theApp.hideMovieBorder = false; rlm@1: prevBorder = false; // it might be expected behaviour that it stays hidden after the movie rlm@1: } rlm@1: } rlm@1: systemGbBorderOn(); rlm@1: #else rlm@1: sdlRtcEnable = (Movie.header.optionFlags & MOVIE_SETTING_RTCENABLE) != 0; rlm@1: saveType = Movie.header.saveType; rlm@1: sdlFlashSize = Movie.header.flashSize; rlm@1: #endif rlm@1: } rlm@1: rlm@1: static void HardResetAndSRAMClear() rlm@1: { rlm@1: #if (defined(WIN32) && !defined(SDL)) rlm@1: winEraseBatteryFile(); // delete the damn SRAM file and keep it from being resurrected from RAM rlm@1: MainWnd *temp = ((MainWnd *)theApp.m_pMainWnd); rlm@1: if (!temp->winFileRun(true)) // restart running the game rlm@1: { rlm@1: temp->winFileClose(); rlm@1: } rlm@1: #else rlm@1: char fname [1024]; rlm@1: GetBatterySaveName(fname); rlm@1: remove(fname); // delete the damn SRAM file rlm@1: rlm@1: // Henceforth, emuCleanUp means "clear out SRAM" rlm@1: //theEmulator.emuCleanUp(); // keep it from being resurrected from RAM <--This is wrong, it'll deallocate all variables --Felipe rlm@1: rlm@1: /// FIXME the correct SDL code to call for a full restart isn't in a function yet rlm@1: theEmulator.emuReset(false); rlm@1: #endif rlm@1: } rlm@1: rlm@1: int VBAMovieOpen(const char *filename, bool8 read_only) rlm@1: { rlm@1: loadingMovie = true; rlm@1: uint8 movieReadOnly = read_only ? 1 : 0; rlm@1: rlm@1: FILE * file; rlm@1: STREAM stream; rlm@1: int result; rlm@1: int fn; rlm@1: rlm@1: char movie_filename[_MAX_PATH]; rlm@1: #ifdef WIN32 rlm@1: _fullpath(movie_filename, filename, _MAX_PATH); rlm@1: #else rlm@1: // SDL FIXME: convert to fullpath rlm@1: strncpy(movie_filename, filename, _MAX_PATH); rlm@1: movie_filename[_MAX_PATH - 1] = '\0'; rlm@1: #endif rlm@1: rlm@1: if (movie_filename[0] == '\0') rlm@1: { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; } rlm@1: rlm@1: if (!emulating) rlm@1: { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; } rlm@1: rlm@1: // bool alreadyOpen = (Movie.file != NULL && _stricmp(movie_filename, Movie.filename) == 0); rlm@1: rlm@1: // if (alreadyOpen) rlm@1: change_state(MOVIE_STATE_NONE); // have to stop current movie before trying to re-open it rlm@1: rlm@1: if (!(file = fopen(movie_filename, "rb+"))) rlm@1: if (!(file = fopen(movie_filename, "rb"))) rlm@1: { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; } rlm@1: //else rlm@1: // movieReadOnly = 2; // we have to open the movie twice, no need to do this both times rlm@1: rlm@1: // if (!alreadyOpen) rlm@1: // change_state(MOVIE_STATE_NONE); // stop current movie when we're able to open the other one rlm@1: // rlm@1: // if (!(file = fopen(movie_filename, "rb+"))) rlm@1: // if(!(file = fopen(movie_filename, "rb"))) rlm@1: // {loadingMovie = false; return MOVIE_FILE_NOT_FOUND;} rlm@1: // else rlm@1: // movieReadOnly = 2; rlm@1: rlm@1: // clear out the current movie rlm@1: VBAMovieInit(); rlm@1: rlm@1: // read header rlm@1: if ((result = read_movie_header(file, Movie)) != MOVIE_SUCCESS) rlm@1: { rlm@1: fclose(file); rlm@1: { loadingMovie = false; return result; } rlm@1: } rlm@1: rlm@1: // set emulator settings that make the movie more likely to stay synchronized rlm@1: SetPlayEmuSettings(); rlm@1: rlm@1: // extern bool systemLoadBIOS(); rlm@1: // if (!systemLoadBIOS()) rlm@1: // { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; } rlm@1: rlm@1: // read the metadata / author info from file rlm@1: fread(Movie.authorInfo, 1, MOVIE_METADATA_SIZE, file); rlm@1: fn = dup(fileno(file)); // XXX: why does this fail?? it returns -1 but errno == 0 rlm@1: fclose(file); rlm@1: rlm@1: // apparently this lseek is necessary rlm@1: lseek(fn, Movie.header.offset_to_savestate, SEEK_SET); rlm@1: if (!(stream = utilGzReopen(fn, "rb"))) rlm@1: if (!(stream = utilGzOpen(movie_filename, "rb"))) rlm@1: { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; } rlm@1: else rlm@1: fn = dup(fileno(file)); rlm@1: // in case the above dup failed but opening the file normally doesn't fail rlm@1: rlm@1: if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT) rlm@1: { rlm@1: // load the snapshot rlm@1: result = theEmulator.emuReadStateFromStream(stream) ? MOVIE_SUCCESS : MOVIE_WRONG_FORMAT; rlm@1: rlm@1: // FIXME: Kludge for conversion rlm@1: remember_input_state(); rlm@1: } rlm@1: else if (Movie.header.startFlags & MOVIE_START_FROM_SRAM) rlm@1: { rlm@1: // 'soft' reset: rlm@1: theEmulator.emuReset(false); rlm@1: rlm@1: // load the SRAM rlm@1: result = theEmulator.emuReadBatteryFromStream(stream) ? MOVIE_SUCCESS : MOVIE_WRONG_FORMAT; rlm@1: } rlm@1: else rlm@1: { rlm@1: HardResetAndSRAMClear(); rlm@1: } rlm@1: rlm@1: utilGzClose(stream); rlm@1: rlm@1: if (result != MOVIE_SUCCESS) rlm@1: { loadingMovie = false; return result; } rlm@1: rlm@1: // if (!(file = fopen(movie_filename, /*read_only ? "rb" :*/ "rb+"))) // want to be able to switch out of read-only later rlm@1: // { rlm@1: // if(!Movie.readOnly || !(file = fopen(movie_filename, "rb"))) // try read-only if failed rlm@1: // return MOVIE_FILE_NOT_FOUND; rlm@1: // } rlm@1: if (!(file = fopen(movie_filename, "rb+"))) rlm@1: if (!(file = fopen(movie_filename, "rb"))) rlm@1: { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; } rlm@1: else rlm@1: movieReadOnly = 2; rlm@1: rlm@1: // recalculate length of movie from the file size rlm@1: Movie.bytesPerFrame = bytes_per_frame(Movie); rlm@1: fseek(file, 0, SEEK_END); rlm@1: long fileSize = ftell(file); rlm@1: Movie.header.length_frames = (fileSize - Movie.header.offset_to_controller_data) / Movie.bytesPerFrame; rlm@1: rlm@1: if (fseek(file, Movie.header.offset_to_controller_data, SEEK_SET)) rlm@1: { fclose(file); loadingMovie = false; return MOVIE_WRONG_FORMAT; } rlm@1: rlm@1: strcpy(Movie.filename, movie_filename); rlm@1: Movie.file = file; rlm@1: Movie.inputBufferPtr = Movie.inputBuffer; rlm@1: Movie.currentFrame = 0; rlm@1: Movie.readOnly = movieReadOnly; rlm@1: Movie.RecordedThisSession = false; rlm@1: rlm@1: // read controller data rlm@1: uint32 to_read = Movie.bytesPerFrame * Movie.header.length_frames; rlm@1: reserve_buffer_space(to_read); rlm@1: fread(Movie.inputBuffer, 1, to_read, file); rlm@1: rlm@1: change_state(MOVIE_STATE_PLAY); rlm@1: rlm@1: char messageString[64] = "Movie "; rlm@1: bool converted = false; rlm@1: if (autoConvertMovieWhenPlaying) rlm@1: { rlm@1: int result = VBAMovieConvertCurrent(); rlm@1: if (result == MOVIE_SUCCESS) rlm@1: strcat(messageString, "converted and "); rlm@1: else if (result == MOVIE_WRONG_VERSION) rlm@1: strcat(messageString, "higher revision "); rlm@1: } rlm@1: rlm@1: if (Movie.state == MOVIE_STATE_PLAY) rlm@1: strcat(messageString, "replaying "); rlm@1: else rlm@1: strcat(messageString, "finished "); rlm@1: if (Movie.readOnly) rlm@1: strcat(messageString, "(read)"); rlm@1: else rlm@1: strcat(messageString, "(edit)"); rlm@1: systemScreenMessage(messageString); rlm@1: rlm@1: VBAUpdateButtonPressDisplay(); rlm@1: VBAUpdateFrameCountDisplay(); rlm@1: systemRefreshScreen(); rlm@1: rlm@1: { loadingMovie = false; return MOVIE_SUCCESS; } rlm@1: } rlm@1: rlm@1: static void SetRecordEmuSettings() rlm@1: { rlm@1: Movie.header.optionFlags = 0; rlm@1: #if (defined(WIN32) && !defined(SDL)) rlm@1: if (theApp.useBiosFile) rlm@1: Movie.header.optionFlags |= MOVIE_SETTING_USEBIOSFILE; rlm@1: if (theApp.skipBiosFile) rlm@1: Movie.header.optionFlags |= MOVIE_SETTING_SKIPBIOSFILE; rlm@1: if (rtcIsEnabled()) rlm@1: Movie.header.optionFlags |= MOVIE_SETTING_RTCENABLE; rlm@1: Movie.header.saveType = theApp.winSaveType; rlm@1: Movie.header.flashSize = theApp.winFlashSize; rlm@1: #else rlm@1: extern int saveType, sdlRtcEnable, sdlFlashSize; // from SDL.cpp rlm@1: extern bool8 useBios, skipBios; // from SDL.cpp rlm@1: if (useBios) rlm@1: Movie.header.optionFlags |= MOVIE_SETTING_USEBIOSFILE; rlm@1: if (skipBios) rlm@1: Movie.header.optionFlags |= MOVIE_SETTING_SKIPBIOSFILE; rlm@1: if (sdlRtcEnable) rlm@1: Movie.header.optionFlags |= MOVIE_SETTING_RTCENABLE; rlm@1: Movie.header.saveType = saveType; rlm@1: Movie.header.flashSize = sdlFlashSize; rlm@1: #endif rlm@1: prevEmulatorType = Movie.header.gbEmulatorType = gbEmulatorType; rlm@1: rlm@1: if (!memLagTempEnabled) rlm@1: Movie.header.optionFlags |= MOVIE_SETTING_LAGHACK; rlm@1: rlm@1: if (gbNullInputHackTempEnabled) rlm@1: Movie.header.optionFlags |= MOVIE_SETTING_GBINPUTHACK; rlm@1: rlm@1: Movie.header.optionFlags |= MOVIE_SETTING_GBCFF55FIX; rlm@1: extern int32 gbDMASpeedVersion; rlm@1: gbDMASpeedVersion = 1; rlm@1: rlm@1: Movie.header.optionFlags |= MOVIE_SETTING_GBECHORAMFIX; rlm@1: extern int32 gbEchoRAMFixOn; rlm@1: gbEchoRAMFixOn = 1; rlm@1: rlm@1: // some GB/GBC games depend on the sound rate, so just use the highest one rlm@1: systemSoundSetQuality(1); rlm@1: rlm@1: useOldFrameTiming = false; rlm@1: rlm@1: #if (defined(WIN32) && !defined(SDL)) rlm@1: // theApp.removeIntros = false; rlm@1: rlm@1: prevBorder = gbBorderOn; rlm@1: prevWinBorder = theApp.winGbBorderOn; rlm@1: prevBorderAuto = gbBorderAutomatic; rlm@1: if (gbEmulatorType == 2 || gbEmulatorType == 5) // only games played in SGB mode will have a border rlm@1: { rlm@1: gbBorderOn = true; rlm@1: theApp.winGbBorderOn = true; rlm@1: gbBorderAutomatic = false; rlm@1: } rlm@1: else rlm@1: { rlm@1: gbBorderOn = false; rlm@1: theApp.winGbBorderOn = false; rlm@1: gbBorderAutomatic = false; rlm@1: } rlm@1: systemGbBorderOn(); rlm@1: #else rlm@1: /// SDLFIXME rlm@1: #endif rlm@1: } rlm@1: rlm@1: uint16 VBAMovieGetCurrentInputOf(int controllerNum, bool normalOnly) rlm@1: { rlm@1: if (controllerNum < 0 || controllerNum >= MOVIE_NUM_OF_POSSIBLE_CONTROLLERS) rlm@1: return 0; rlm@1: rlm@1: return normalOnly ? (currentButtons[controllerNum] & BUTTON_REGULAR_MASK) : currentButtons[controllerNum]; rlm@1: } rlm@1: rlm@1: int VBAMovieCreate(const char *filename, const char *authorInfo, uint8 startFlags, uint8 controllerFlags, uint8 typeFlags) rlm@1: { rlm@1: // make sure at least one controller is enabled rlm@1: if ((controllerFlags & MOVIE_CONTROLLERS_ANY_MASK) == 0) rlm@1: return MOVIE_WRONG_FORMAT; rlm@1: rlm@1: if (!emulating) rlm@1: return MOVIE_UNKNOWN_ERROR; rlm@1: rlm@1: loadingMovie = true; rlm@1: rlm@1: FILE * file; rlm@1: STREAM stream; rlm@1: int fn; rlm@1: rlm@1: char movie_filename [_MAX_PATH]; rlm@1: #ifdef WIN32 rlm@1: _fullpath(movie_filename, filename, _MAX_PATH); rlm@1: #else rlm@1: // FIXME: convert to fullpath rlm@1: strncpy(movie_filename, filename, _MAX_PATH); rlm@1: movie_filename[_MAX_PATH - 1] = '\0'; rlm@1: #endif rlm@1: rlm@1: bool alreadyOpen = (Movie.file != NULL && stricmp(movie_filename, Movie.filename) == 0); rlm@1: rlm@1: if (alreadyOpen) rlm@1: change_state(MOVIE_STATE_NONE); // have to stop current movie before trying to re-open it rlm@1: rlm@1: if (movie_filename[0] == '\0') rlm@1: { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; } rlm@1: rlm@1: if (!(file = fopen(movie_filename, "wb"))) rlm@1: { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; } rlm@1: rlm@1: if (!alreadyOpen) rlm@1: change_state(MOVIE_STATE_NONE); // stop current movie when we're able to open the other one rlm@1: rlm@1: // clear out the current movie rlm@1: VBAMovieInit(); rlm@1: rlm@1: // fill in the movie's header rlm@1: Movie.header.uid = (uint32)time(NULL); rlm@1: Movie.header.magic = VBM_MAGIC; rlm@1: Movie.header.version = VBM_VERSION; rlm@1: Movie.header.rerecord_count = 0; rlm@1: Movie.header.length_frames = 0; rlm@1: Movie.header.startFlags = startFlags; rlm@1: Movie.header.controllerFlags = controllerFlags; rlm@1: Movie.header.typeFlags = typeFlags; rlm@1: Movie.header.minorVersion = VBM_REVISION; rlm@1: rlm@1: // set emulator settings that make the movie more likely to stay synchronized when it's later played back rlm@1: SetRecordEmuSettings(); rlm@1: rlm@1: // set ROM and BIOS checksums and stuff rlm@1: VBAMovieGetRomInfo(Movie, Movie.header.romTitle, Movie.header.romGameCode, Movie.header.romOrBiosChecksum, Movie.header.romCRC); rlm@1: rlm@1: // write the header to file rlm@1: write_movie_header(file, Movie); rlm@1: rlm@1: // copy over the metadata / author info rlm@1: VBAMovieSetMetadata(authorInfo); rlm@1: rlm@1: // write the metadata / author info to file rlm@1: fwrite(Movie.authorInfo, 1, sizeof(char) * MOVIE_METADATA_SIZE, file); rlm@1: rlm@1: // write snapshot or SRAM if applicable rlm@1: if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT rlm@1: || Movie.header.startFlags & MOVIE_START_FROM_SRAM) rlm@1: { rlm@1: Movie.header.offset_to_savestate = (uint32)ftell(file); rlm@1: rlm@1: // close the file and reopen it as a stream: rlm@1: rlm@1: fn = dup(fileno(file)); rlm@1: fclose(file); rlm@1: rlm@1: if (!(stream = utilGzReopen(fn, "ab"))) // append mode to start at end, no seek necessary rlm@1: { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; } rlm@1: rlm@1: // write the save data: rlm@1: if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT) rlm@1: { rlm@1: // save snapshot rlm@1: if (!theEmulator.emuWriteStateToStream(stream)) rlm@1: { rlm@1: utilGzClose(stream); rlm@1: { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; } rlm@1: } rlm@1: } rlm@1: else if (Movie.header.startFlags & MOVIE_START_FROM_SRAM) rlm@1: { rlm@1: // save SRAM rlm@1: if (!theEmulator.emuWriteBatteryToStream(stream)) rlm@1: { rlm@1: utilGzClose(stream); rlm@1: { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; } rlm@1: } rlm@1: rlm@1: // 'soft' reset: rlm@1: theEmulator.emuReset(false); rlm@1: } rlm@1: rlm@1: utilGzClose(stream); rlm@1: rlm@1: // reopen the file and seek back to the end rlm@1: rlm@1: if (!(file = fopen(movie_filename, "rb+"))) rlm@1: { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; } rlm@1: rlm@1: fseek(file, 0, SEEK_END); rlm@1: } rlm@1: else // no snapshot or SRAM rlm@1: { rlm@1: HardResetAndSRAMClear(); rlm@1: } rlm@1: rlm@1: Movie.header.offset_to_controller_data = (uint32)ftell(file); rlm@1: rlm@1: strcpy(Movie.filename, movie_filename); rlm@1: Movie.file = file; rlm@1: Movie.bytesPerFrame = bytes_per_frame(Movie); rlm@1: Movie.inputBufferPtr = Movie.inputBuffer; rlm@1: Movie.currentFrame = 0; rlm@1: Movie.readOnly = false; rlm@1: Movie.RecordedThisSession = true; rlm@1: rlm@1: change_state(MOVIE_STATE_RECORD); rlm@1: rlm@1: systemScreenMessage("Recording movie..."); rlm@1: { loadingMovie = false; return MOVIE_SUCCESS; } rlm@1: } rlm@1: rlm@1: void VBAUpdateButtonPressDisplay() rlm@1: { rlm@1: uint32 keys = currentButtons[0] & BUTTON_REGULAR_RECORDING_MASK; rlm@1: rlm@1: const static char KeyMap[] = { 'A', 'B', 's', 'S', '>', '<', '^', 'v', 'R', 'L', '!', '?', '{', '}', 'v', '^' }; rlm@1: const static int KeyOrder[] = { 5, 6, 4, 7, 0, 1, 9, 8, 3, 2, 12, 15, 13, 14, 11, 10 }; // < ^ > v A B L R S s { = } _ rlm@1: // ? ! rlm@1: char buffer[256]; rlm@1: sprintf(buffer, " "); rlm@1: rlm@1: #ifndef WIN32 rlm@1: // don't bother color-coding autofire and such rlm@1: int i; rlm@1: for (i = 0; i < 15; i++) rlm@1: { rlm@1: int j = KeyOrder[i]; rlm@1: int mask = (1 << (j)); rlm@1: buffer[strlen(" ") + i] = ((keys & mask) != 0) ? KeyMap[j] : ' '; rlm@1: } rlm@1: rlm@1: systemScreenMessage(buffer, 2, -1); rlm@1: #else rlm@1: const bool eraseAll = !theApp.inputDisplay; rlm@1: uint32 autoHeldKeys = eraseAll ? 0 : theApp.autoHold & BUTTON_REGULAR_RECORDING_MASK; rlm@1: uint32 autoFireKeys = eraseAll ? 0 : (theApp.autoFire | theApp.autoFire2) & BUTTON_REGULAR_RECORDING_MASK; rlm@1: uint32 pressedKeys = eraseAll ? 0 : keys; rlm@1: rlm@1: char colorList[64]; rlm@1: memset(colorList, 1, strlen(buffer)); rlm@1: rlm@1: if (!eraseAll) rlm@1: { rlm@1: for (int i = 0; i < 15; i++) rlm@1: { rlm@1: const int j = KeyOrder[i]; rlm@1: const int mask = (1 << (j)); rlm@1: bool pressed = (pressedKeys & mask) != 0; rlm@1: const bool autoHeld = (autoHeldKeys & mask) != 0; rlm@1: const bool autoFired = (autoFireKeys & mask) != 0; rlm@1: const bool erased = (lastKeys & mask) != 0 && (!pressed && !autoHeld && !autoFired); rlm@1: extern int textMethod; rlm@1: if (textMethod != 2 && (autoHeld || (autoFired && !pressed) || erased)) rlm@1: { rlm@1: int colorNum = 1; // default is white rlm@1: if (autoHeld) rlm@1: colorNum += (pressed ? 2 : 1); // yellow if pressed, red if not rlm@1: else if (autoFired) rlm@1: colorNum += 5; // blue if autofired and not currently pressed rlm@1: else if (erased) rlm@1: colorNum += 8; // black on black rlm@1: rlm@1: colorList[strlen(" ") + i] = colorNum; rlm@1: pressed = true; rlm@1: } rlm@1: buffer[strlen(" ") + i] = pressed ? KeyMap[j] : ' '; rlm@1: } rlm@1: } rlm@1: rlm@1: lastKeys = currentButtons[0]; rlm@1: lastKeys |= theApp.autoHold & BUTTON_REGULAR_RECORDING_MASK; rlm@1: lastKeys |= (theApp.autoFire | theApp.autoFire2) & BUTTON_REGULAR_RECORDING_MASK; rlm@1: rlm@1: systemScreenMessage(buffer, 2, -1, colorList); rlm@1: #endif rlm@1: } rlm@1: rlm@1: void VBAUpdateFrameCountDisplay() rlm@1: { rlm@1: const int MAGICAL_NUMBER = 64; // FIXME: this won't do any better, but only to remind you of sz issues rlm@1: char frameDisplayString[MAGICAL_NUMBER]; rlm@1: char lagFrameDisplayString[MAGICAL_NUMBER]; rlm@1: char extraCountDisplayString[MAGICAL_NUMBER]; rlm@1: rlm@1: #if (defined(WIN32) && !defined(SDL)) rlm@1: if (theApp.frameCounter) rlm@1: #else rlm@1: /// SDL FIXME rlm@1: #endif rlm@1: { rlm@1: switch (Movie.state) rlm@1: { rlm@1: case MOVIE_STATE_PLAY: rlm@1: case MOVIE_STATE_END: rlm@1: { rlm@1: sprintf(frameDisplayString, "%d / %d", Movie.currentFrame, Movie.header.length_frames); rlm@1: if (!Movie.readOnly) rlm@1: strcat(frameDisplayString, " (edit)"); rlm@1: break; rlm@1: } rlm@1: case MOVIE_STATE_RECORD: rlm@1: { rlm@1: sprintf(frameDisplayString, "%d (record)", Movie.currentFrame); rlm@1: break; rlm@1: } rlm@1: default: rlm@1: { rlm@1: sprintf(frameDisplayString, "%d (no movie)", systemCounters.frameCount); rlm@1: break; rlm@1: } rlm@1: } rlm@1: rlm@1: #if (defined(WIN32) && !defined(SDL)) rlm@1: if (theApp.lagCounter) rlm@1: #else rlm@1: /// SDL FIXME rlm@1: #endif rlm@1: { rlm@1: // sprintf(lagFrameDisplayString, " %c %d", systemCounters.laggedLast ? '*' : '|', systemCounters.lagCount); rlm@1: sprintf(lagFrameDisplayString, " | %d%s", systemCounters.lagCount, systemCounters.laggedLast ? " *" : ""); rlm@1: strcat(frameDisplayString, lagFrameDisplayString); rlm@1: } rlm@1: rlm@1: #if (defined(WIN32) && !defined(SDL)) rlm@1: if (theApp.extraCounter) rlm@1: #else rlm@1: /// SDL FIXME rlm@1: #endif rlm@1: { rlm@1: sprintf(extraCountDisplayString, " | %d", systemCounters.frameCount - systemCounters.extraCount); rlm@1: strcat(frameDisplayString, extraCountDisplayString); rlm@1: } rlm@1: } rlm@1: #if (defined(WIN32) && !defined(SDL)) rlm@1: else rlm@1: { rlm@1: frameDisplayString[0] = '\0'; rlm@1: } rlm@1: #else rlm@1: /// SDL FIXME rlm@1: #endif rlm@1: systemScreenMessage(frameDisplayString, 1, -1); rlm@1: } rlm@1: rlm@1: // this function should only be called once every frame rlm@1: void VBAMovieUpdateState() rlm@1: { rlm@1: ++Movie.currentFrame; rlm@1: rlm@1: if (Movie.state == MOVIE_STATE_PLAY) rlm@1: { rlm@1: Movie.inputBufferPtr += Movie.bytesPerFrame; rlm@1: if (Movie.currentFrame >= Movie.header.length_frames) rlm@1: { rlm@1: // the movie ends anyway; what to do next depends on the settings rlm@1: change_state(MOVIE_STATE_END); rlm@1: } rlm@1: } rlm@1: else if (Movie.state == MOVIE_STATE_RECORD) rlm@1: { rlm@1: // use first fseek? rlm@1: fwrite(Movie.inputBufferPtr, 1, Movie.bytesPerFrame, Movie.file); rlm@1: Movie.header.length_frames = Movie.currentFrame; rlm@1: Movie.inputBufferPtr += Movie.bytesPerFrame; rlm@1: Movie.RecordedThisSession = true; rlm@1: flush_movie_header(); rlm@1: } rlm@1: else if (Movie.state == MOVIE_STATE_END) rlm@1: { rlm@1: change_state(MOVIE_STATE_END); rlm@1: } rlm@1: } rlm@1: rlm@1: void VBAMovieRead(int i, bool /*sensor*/) rlm@1: { rlm@1: if (Movie.state != MOVIE_STATE_PLAY) rlm@1: return; rlm@1: rlm@1: if (i < 0 || i >= MOVIE_NUM_OF_POSSIBLE_CONTROLLERS) rlm@1: return; // not a controller we're recognizing rlm@1: rlm@1: if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i)) rlm@1: { rlm@1: currentButtons[i] = Read16(Movie.inputBufferPtr + CONTROLLER_DATA_SIZE * i); rlm@1: } rlm@1: else rlm@1: { rlm@1: currentButtons[i] = 0; // pretend the controller is disconnected rlm@1: } rlm@1: rlm@1: if ((currentButtons[i] & BUTTON_MASK_NEW_RESET) != 0) rlm@1: resetSignaled = true; rlm@1: } rlm@1: rlm@1: void VBAMovieWrite(int i, bool /*sensor*/) rlm@1: { rlm@1: if (Movie.state != MOVIE_STATE_RECORD) rlm@1: return; rlm@1: rlm@1: if (i < 0 || i >= MOVIE_NUM_OF_POSSIBLE_CONTROLLERS) rlm@1: return; // not a controller we're recognizing rlm@1: rlm@1: reserve_buffer_space((uint32)((Movie.inputBufferPtr - Movie.inputBuffer) + Movie.bytesPerFrame)); rlm@1: rlm@1: if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i)) rlm@1: { rlm@1: // get the current controller data rlm@1: uint16 buttonData = currentButtons[i]; rlm@1: rlm@1: // mask away the irrelevent bits rlm@1: buttonData &= BUTTON_REGULAR_MASK | BUTTON_MOTION_MASK; rlm@1: rlm@1: // soft-reset "button" for 1 frame if the game is reset while recording rlm@1: if (resetSignaled) rlm@1: { rlm@1: buttonData |= BUTTON_MASK_NEW_RESET; rlm@1: } rlm@1: rlm@1: // backward compatibility kludge rlm@1: if (resetSignaledLast) rlm@1: { rlm@1: buttonData |= BUTTON_MASK_OLD_RESET; rlm@1: } rlm@1: rlm@1: Write16(buttonData, Movie.inputBufferPtr + CONTROLLER_DATA_SIZE * i); rlm@1: rlm@1: // and for display rlm@1: currentButtons[i] = buttonData; rlm@1: } rlm@1: else rlm@1: { rlm@1: // pretend the controller is disconnected (otherwise input it gives could cause desync since we're not writing it to the rlm@1: // movie) rlm@1: currentButtons[i] = 0; rlm@1: } rlm@1: } rlm@1: rlm@1: void VBAMovieStop(bool8 suppress_message) rlm@1: { rlm@1: if (Movie.state != MOVIE_STATE_NONE) rlm@1: { rlm@1: change_state(MOVIE_STATE_NONE); rlm@1: if (!suppress_message) rlm@1: systemScreenMessage("Movie stop"); rlm@1: } rlm@1: } rlm@1: rlm@1: int VBAMovieGetInfo(const char *filename, SMovie *info) rlm@1: { rlm@1: assert(info != NULL); rlm@1: if (info == NULL) rlm@1: return -1; rlm@1: rlm@1: FILE * file; rlm@1: int result; rlm@1: SMovie &local_movie = *info; rlm@1: rlm@1: memset(info, 0, sizeof(*info)); rlm@1: if (filename[0] == '\0') rlm@1: return MOVIE_FILE_NOT_FOUND; rlm@1: if (!(file = fopen(filename, "rb"))) rlm@1: return MOVIE_FILE_NOT_FOUND; rlm@1: rlm@1: // read header rlm@1: if ((result = (read_movie_header(file, local_movie))) != MOVIE_SUCCESS) rlm@1: { rlm@1: fclose(file); rlm@1: return result; rlm@1: } rlm@1: rlm@1: // read the metadata / author info from file rlm@1: fread(local_movie.authorInfo, 1, sizeof(char) * MOVIE_METADATA_SIZE, file); rlm@1: rlm@1: strncpy(local_movie.filename, filename, _MAX_PATH); rlm@1: local_movie.filename[_MAX_PATH - 1] = '\0'; rlm@1: rlm@1: if (Movie.file != NULL && stricmp(local_movie.filename, Movie.filename) == 0) // alreadyOpen rlm@1: { rlm@1: local_movie.bytesPerFrame = Movie.bytesPerFrame; rlm@1: local_movie.header.length_frames = Movie.header.length_frames; rlm@1: } rlm@1: else rlm@1: { rlm@1: // recalculate length of movie from the file size rlm@1: local_movie.bytesPerFrame = bytes_per_frame(local_movie); rlm@1: fseek(file, 0, SEEK_END); rlm@1: int fileSize = ftell(file); rlm@1: local_movie.header.length_frames = rlm@1: (fileSize - local_movie.header.offset_to_controller_data) / local_movie.bytesPerFrame; rlm@1: } rlm@1: rlm@1: fclose(file); rlm@1: rlm@1: if (access(filename, W_OK)) rlm@1: info->readOnly = true; rlm@1: rlm@1: return MOVIE_SUCCESS; rlm@1: } rlm@1: rlm@1: bool8 VBAMovieActive() rlm@1: { rlm@1: return (Movie.state != MOVIE_STATE_NONE); rlm@1: } rlm@1: rlm@1: bool8 VBAMovieLoading() rlm@1: { rlm@1: return loadingMovie; rlm@1: } rlm@1: rlm@1: bool8 VBAMoviePlaying() rlm@1: { rlm@1: return (Movie.state == MOVIE_STATE_PLAY); rlm@1: } rlm@1: rlm@1: bool8 VBAMovieRecording() rlm@1: { rlm@1: return (Movie.state == MOVIE_STATE_RECORD); rlm@1: } rlm@1: rlm@1: bool8 VBAMovieReadOnly() rlm@1: { rlm@1: if (!VBAMovieActive()) rlm@1: return false; rlm@1: rlm@1: return Movie.readOnly; rlm@1: } rlm@1: rlm@1: void VBAMovieToggleReadOnly() rlm@1: { rlm@1: if (!VBAMovieActive()) rlm@1: return; rlm@1: rlm@1: if (Movie.readOnly != 2) rlm@1: { rlm@1: Movie.readOnly = !Movie.readOnly; rlm@1: rlm@1: systemScreenMessage(Movie.readOnly ? "Movie now read-only" : "Movie now editable"); rlm@1: } rlm@1: else rlm@1: { rlm@1: systemScreenMessage("Can't toggle read-only movie"); rlm@1: } rlm@1: } rlm@1: rlm@1: uint32 VBAMovieGetVersion() rlm@1: { rlm@1: if (!VBAMovieActive()) rlm@1: return 0; rlm@1: rlm@1: return Movie.header.version; rlm@1: } rlm@1: rlm@1: uint32 VBAMovieGetMinorVersion() rlm@1: { rlm@1: if (!VBAMovieActive()) rlm@1: return 0; rlm@1: rlm@1: return Movie.header.minorVersion; rlm@1: } rlm@1: rlm@1: uint32 VBAMovieGetId() rlm@1: { rlm@1: if (!VBAMovieActive()) rlm@1: return 0; rlm@1: rlm@1: return Movie.header.uid; rlm@1: } rlm@1: rlm@1: uint32 VBAMovieGetLength() rlm@1: { rlm@1: if (!VBAMovieActive()) rlm@1: return 0; rlm@1: rlm@1: return Movie.header.length_frames; rlm@1: } rlm@1: rlm@1: uint32 VBAMovieGetFrameCounter() rlm@1: { rlm@1: if (!VBAMovieActive()) rlm@1: return 0; rlm@1: rlm@1: return Movie.currentFrame; rlm@1: } rlm@1: rlm@1: uint32 VBAMovieGetRerecordCount() rlm@1: { rlm@1: if (!VBAMovieActive()) rlm@1: return 0; rlm@1: rlm@1: return Movie.header.rerecord_count; rlm@1: } rlm@1: rlm@1: uint32 VBAMovieSetRerecordCount(uint32 newRerecordCount) rlm@1: { rlm@1: uint32 oldRerecordCount = 0; rlm@1: if (!VBAMovieActive()) rlm@1: return 0; rlm@1: rlm@1: oldRerecordCount = Movie.header.rerecord_count; rlm@1: Movie.header.rerecord_count = newRerecordCount; rlm@1: return oldRerecordCount; rlm@1: } rlm@1: rlm@1: std::string VBAMovieGetAuthorInfo() rlm@1: { rlm@1: if (!VBAMovieActive()) rlm@1: return ""; rlm@1: rlm@1: return Movie.authorInfo; rlm@1: } rlm@1: rlm@1: std::string VBAMovieGetFilename() rlm@1: { rlm@1: if (!VBAMovieActive()) rlm@1: return ""; rlm@1: rlm@1: return Movie.filename; rlm@1: } rlm@1: rlm@1: void VBAMovieFreeze(uint8 * *buf, uint32 *size) rlm@1: { rlm@1: // sanity check rlm@1: if (!VBAMovieActive()) rlm@1: { rlm@1: return; rlm@1: } rlm@1: rlm@1: *buf = NULL; rlm@1: *size = 0; rlm@1: rlm@1: // compute size needed for the buffer rlm@1: // room for header.uid, currentFrame, and header.length_frames rlm@1: uint32 size_needed = sizeof(Movie.header.uid) + sizeof(Movie.currentFrame) + sizeof(Movie.header.length_frames); rlm@1: size_needed += (uint32)(Movie.bytesPerFrame * Movie.header.length_frames); rlm@1: *buf = new uint8[size_needed]; rlm@1: *size = size_needed; rlm@1: rlm@1: uint8 *ptr = *buf; rlm@1: if (!ptr) rlm@1: { rlm@1: return; rlm@1: } rlm@1: rlm@1: Push32(Movie.header.uid, ptr); rlm@1: Push32(Movie.currentFrame, ptr); rlm@1: Push32(Movie.header.length_frames - 1, ptr); // HACK: shorten the length by 1 for backward compatibility rlm@1: rlm@1: memcpy(ptr, Movie.inputBuffer, Movie.bytesPerFrame * Movie.header.length_frames); rlm@1: } rlm@1: rlm@1: int VBAMovieUnfreeze(const uint8 *buf, uint32 size) rlm@1: { rlm@1: // sanity check rlm@1: if (!VBAMovieActive()) rlm@1: { rlm@1: return MOVIE_NOT_FROM_A_MOVIE; rlm@1: } rlm@1: rlm@1: const uint8 *ptr = buf; rlm@1: if (size < sizeof(Movie.header.uid) + sizeof(Movie.currentFrame) + sizeof(Movie.header.length_frames)) rlm@1: { rlm@1: return MOVIE_WRONG_FORMAT; rlm@1: } rlm@1: rlm@1: uint32 movie_id = Pop32(ptr); rlm@1: uint32 current_frame = Pop32(ptr); rlm@1: uint32 end_frame = Pop32(ptr) + 1; // HACK: restore the length for backward compatibility rlm@1: uint32 space_needed = Movie.bytesPerFrame * end_frame; rlm@1: rlm@1: if (movie_id != Movie.header.uid) rlm@1: return MOVIE_NOT_FROM_THIS_MOVIE; rlm@1: rlm@1: if (space_needed > size) rlm@1: return MOVIE_WRONG_FORMAT; rlm@1: rlm@1: if (Movie.readOnly) rlm@1: { rlm@1: // here, we are going to keep the input data from the movie file rlm@1: // and simply rewind to the currentFrame pointer rlm@1: // this will cause a desync if the savestate is not in sync // <-- NOT ANYMORE rlm@1: // with the on-disk recording data, but it's easily solved rlm@1: // by loading another savestate or playing the movie from the beginning rlm@1: rlm@1: // don't allow loading a state inconsistent with the current movie rlm@1: uint32 length_history = min(current_frame, Movie.header.length_frames); rlm@1: if (end_frame < length_history) rlm@1: return MOVIE_SNAPSHOT_INCONSISTENT; rlm@1: rlm@1: uint32 space_shared = Movie.bytesPerFrame * length_history; rlm@1: if (memcmp(Movie.inputBuffer, ptr, space_shared)) rlm@1: return MOVIE_SNAPSHOT_INCONSISTENT; rlm@1: rlm@1: Movie.currentFrame = current_frame; rlm@1: Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * min(current_frame, Movie.header.length_frames); rlm@1: } rlm@1: else rlm@1: { rlm@1: // here, we are going to take the input data from the savestate rlm@1: // and make it the input data for the current movie, then continue rlm@1: // writing new input data at the currentFrame pointer rlm@1: Movie.currentFrame = current_frame; rlm@1: Movie.header.length_frames = end_frame; rlm@1: if (!VBALuaRerecordCountSkip()) rlm@1: ++Movie.header.rerecord_count; rlm@1: rlm@1: Movie.RecordedThisSession = true; rlm@1: rlm@1: // do this before calling reserve_buffer_space() rlm@1: Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * min(current_frame, Movie.header.length_frames); rlm@1: reserve_buffer_space(space_needed); rlm@1: memcpy(Movie.inputBuffer, ptr, space_needed); rlm@1: rlm@1: // for consistency, no auto movie conversion here since we don't auto convert the corresponding savestate rlm@1: flush_movie_header(); rlm@1: flush_movie_frames(); rlm@1: } rlm@1: rlm@1: change_state(MOVIE_STATE_PLAY); // check for movie end rlm@1: rlm@1: // necessary! rlm@1: resetSignaled = false; rlm@1: resetSignaledLast = false; rlm@1: rlm@1: // necessary to check if there's a reset signal at the previous frame rlm@1: if (current_frame > 0) rlm@1: { rlm@1: const u8 NEW_RESET = u8(BUTTON_MASK_NEW_RESET >> 8); rlm@1: for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i) rlm@1: { rlm@1: if ((Movie.header.controllerFlags & MOVIE_CONTROLLER(i)) && (*(Movie.inputBufferPtr+1- Movie.bytesPerFrame) & NEW_RESET)) rlm@1: { rlm@1: resetSignaledLast = true; rlm@1: break; rlm@1: } rlm@1: } rlm@1: } rlm@1: rlm@1: return MOVIE_SUCCESS; rlm@1: } rlm@1: rlm@1: bool VBAMovieEnded() rlm@1: { rlm@1: return (Movie.state == MOVIE_STATE_END); rlm@1: // return (Movie.state != MOVIE_STATE_NONE && Movie.currentFrame >= Movie.header.length_frames); rlm@1: } rlm@1: rlm@1: bool VBAMovieAllowsRerecording() rlm@1: { rlm@1: bool allows = (Movie.state != MOVIE_STATE_NONE) && (Movie.currentFrame <= Movie.header.length_frames); rlm@1: return /*!VBAMovieReadOnly() &&*/ allows; rlm@1: } rlm@1: rlm@1: bool VBAMovieSwitchToPlaying() rlm@1: { rlm@1: if (!VBAMovieActive()) rlm@1: return false; rlm@1: rlm@1: if (!Movie.readOnly) rlm@1: { rlm@1: VBAMovieToggleReadOnly(); rlm@1: } rlm@1: rlm@1: change_state(MOVIE_STATE_PLAY); rlm@1: if (Movie.state == MOVIE_STATE_PLAY) rlm@1: systemScreenMessage("Movie replay (continue)"); rlm@1: else rlm@1: systemScreenMessage("Movie end"); rlm@1: rlm@1: return true; rlm@1: } rlm@1: rlm@1: bool VBAMovieSwitchToRecording() rlm@1: { rlm@1: if (!VBAMovieAllowsRerecording()) rlm@1: return false; rlm@1: rlm@1: if (Movie.readOnly) rlm@1: { rlm@1: VBAMovieToggleReadOnly(); rlm@1: } rlm@1: rlm@1: if (!VBALuaRerecordCountSkip()) rlm@1: ++Movie.header.rerecord_count; rlm@1: rlm@1: change_state(MOVIE_STATE_RECORD); rlm@1: systemScreenMessage("Movie re-record"); rlm@1: rlm@1: //truncate_movie(Movie.currentFrame); rlm@1: rlm@1: return true; rlm@1: } rlm@1: rlm@1: uint32 VBAMovieGetState() rlm@1: { rlm@1: // ? rlm@1: if (!VBAMovieActive()) rlm@1: return MOVIE_STATE_NONE; rlm@1: rlm@1: return Movie.state; rlm@1: } rlm@1: rlm@1: void VBAMovieSignalReset() rlm@1: { rlm@1: if (VBAMovieActive()) rlm@1: resetSignaled = true; rlm@1: } rlm@1: rlm@1: void VBAMovieResetIfRequested() rlm@1: { rlm@1: if (resetSignaled) rlm@1: { rlm@1: theEmulator.emuReset(false); rlm@1: resetSignaled = false; rlm@1: resetSignaledLast = true; rlm@1: } rlm@1: else rlm@1: { rlm@1: resetSignaledLast = false; rlm@1: } rlm@1: } rlm@1: rlm@1: void VBAMovieSetMetadata(const char *info) rlm@1: { rlm@1: if (!memcmp(Movie.authorInfo, info, MOVIE_METADATA_SIZE)) rlm@1: return; rlm@1: rlm@1: memcpy(Movie.authorInfo, info, MOVIE_METADATA_SIZE); // strncpy would omit post-0 bytes rlm@1: Movie.authorInfo[MOVIE_METADATA_SIZE - 1] = '\0'; rlm@1: rlm@1: if (Movie.file) rlm@1: { rlm@1: // (over-)write the header rlm@1: fseek(Movie.file, 0, SEEK_SET); rlm@1: write_movie_header(Movie.file, Movie); rlm@1: rlm@1: // write the metadata / author info to file rlm@1: fwrite(Movie.authorInfo, 1, sizeof(char) * MOVIE_METADATA_SIZE, Movie.file); rlm@1: rlm@1: fflush(Movie.file); rlm@1: } rlm@1: } rlm@1: rlm@1: void VBAMovieRestart() rlm@1: { rlm@1: if (VBAMovieActive()) rlm@1: { rlm@1: systemSoundClearBuffer(); rlm@1: rlm@1: bool8 modified = Movie.RecordedThisSession; rlm@1: rlm@1: VBAMovieStop(true); rlm@1: rlm@1: char movieName [_MAX_PATH]; rlm@1: strncpy(movieName, Movie.filename, _MAX_PATH); rlm@1: movieName[_MAX_PATH - 1] = '\0'; rlm@1: VBAMovieOpen(movieName, Movie.readOnly); // can't just pass in Movie.filename, since VBAMovieOpen clears out Movie's rlm@1: // variables rlm@1: rlm@1: Movie.RecordedThisSession = modified; rlm@1: rlm@1: systemScreenMessage("Movie replay (restart)"); rlm@1: } rlm@1: } rlm@1: rlm@1: int VBAMovieGetPauseAt() rlm@1: { rlm@1: return Movie.pauseFrame; rlm@1: } rlm@1: rlm@1: void VBAMovieSetPauseAt(int at) rlm@1: { rlm@1: Movie.pauseFrame = at; rlm@1: } rlm@1: rlm@1: /////////////////////// rlm@1: // movie tools rlm@1: rlm@1: // FIXME: is it safe to convert/flush a movie while recording it (considering fseek() problem)? rlm@1: int VBAMovieConvertCurrent() rlm@1: { rlm@1: if (!VBAMovieActive()) rlm@1: { rlm@1: return MOVIE_NOTHING; rlm@1: } rlm@1: rlm@1: if (Movie.header.minorVersion > VBM_REVISION) rlm@1: { rlm@1: return MOVIE_WRONG_VERSION; rlm@1: } rlm@1: rlm@1: if (Movie.header.minorVersion == VBM_REVISION) rlm@1: { rlm@1: return MOVIE_NOTHING; rlm@1: } rlm@1: rlm@1: Movie.header.minorVersion = VBM_REVISION; rlm@1: rlm@1: if (Movie.header.length_frames == 0) // this could happen rlm@1: { rlm@1: truncate_movie(0); rlm@1: return MOVIE_SUCCESS; rlm@1: } rlm@1: rlm@1: // fix movies recorded from snapshots rlm@1: if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT) rlm@1: { rlm@1: uint8 *firstFramePtr = Movie.inputBuffer; rlm@1: for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i) rlm@1: { rlm@1: if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i)) rlm@1: { rlm@1: Push16(initialInputs[i], firstFramePtr); rlm@1: // note: this is correct since Push16 advances the dest pointer by sizeof u16 rlm@1: } rlm@1: } rlm@1: } rlm@1: rlm@1: // convert old resets to new ones rlm@1: const u8 OLD_RESET = u8(BUTTON_MASK_OLD_RESET >> 8); rlm@1: const u8 NEW_RESET = u8(BUTTON_MASK_NEW_RESET >> 8); rlm@1: for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i) rlm@1: { rlm@1: if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i)) rlm@1: { rlm@1: uint8 *startPtr = Movie.inputBuffer + sizeof(u16) * i + 1; rlm@1: uint8 *endPtr = Movie.inputBuffer + Movie.bytesPerFrame * (Movie.header.length_frames - 1); rlm@1: for (; startPtr < endPtr; startPtr += Movie.bytesPerFrame) rlm@1: { rlm@1: if (startPtr[Movie.bytesPerFrame] & OLD_RESET) rlm@1: { rlm@1: startPtr[0] |= NEW_RESET; rlm@1: } rlm@1: } rlm@1: } rlm@1: } rlm@1: rlm@1: flush_movie_header(); rlm@1: flush_movie_frames(); rlm@1: return MOVIE_SUCCESS; rlm@1: } rlm@1: rlm@1: bool VBAMovieTuncateAtCurrentFrame() rlm@1: { rlm@1: if (!VBAMovieActive()) rlm@1: return false; rlm@1: rlm@1: truncate_movie(Movie.currentFrame); rlm@1: change_state(MOVIE_STATE_END); rlm@1: systemScreenMessage("Movie truncated"); rlm@1: rlm@1: return true; rlm@1: } rlm@1: rlm@1: bool VBAMovieFixHeader() rlm@1: { rlm@1: if (!VBAMovieActive()) rlm@1: return false; rlm@1: rlm@1: flush_movie_header(); rlm@1: systemScreenMessage("Movie header fixed"); rlm@1: return true; rlm@1: }