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