Mercurial > vba-clojure
view src/common/movie.cpp @ 34:6bde5b67a2b8
saving progress
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Mon, 05 Mar 2012 01:58:52 -0600 |
parents | 44974c3e093b |
children | b82b18185103 |
line wrap: on
line source
1 #include <cstdio>2 #include <cctype>3 #include <cstdlib>4 #include <cstring>5 #include <cassert>6 #include <algorithm>8 using namespace std;10 #ifdef HAVE_STRINGS_H11 # include <strings.h>12 #endif14 #if defined(__unix) || defined(__linux) || defined(__sun) || defined(__DJGPP)15 # include <unistd.h>16 # include <sys/types.h>17 # include <sys/stat.h>18 # include <climits>19 # define stricmp strcasecmp20 // FIXME: this is wrong, but we don't want buffer overflow21 # if defined _MAX_PATH22 # undef _MAX_PATH23 //# define _MAX_PATH 12824 # define _MAX_PATH 26025 # endif26 #endif28 #ifdef WIN3229 # include <io.h>30 # ifndef W_OK31 # define W_OK 232 # endif33 # define ftruncate chsize34 #endif36 #include "movie.h"37 #include "System.h"38 #include "../gba/GBA.h"39 #include "../gba/GBAGlobals.h"40 #include "../gba/RTC.h"41 #include "../gb/GB.h"42 #include "../gb/gbGlobals.h"43 #include "inputGlobal.h"44 #include "unzip.h"45 #include "Util.h"47 #include "vbalua.h"49 #if (defined(WIN32) && !defined(SDL))50 # include "../win32/stdafx.h"51 # include "../win32/MainWnd.h"52 # include "../win32/VBA.h"53 # include "../win32/WinMiscUtil.h"54 #endif56 extern int emulating; // from system.cpp57 extern u16 currentButtons[4]; // from System.cpp58 extern u16 lastKeys;60 SMovie Movie;61 bool loadingMovie = false;63 // probably bad idea to have so many global variables, but I hate to recompile almost everything after editing VBA.h64 bool autoConvertMovieWhenPlaying = false;66 static u16 initialInputs[4] = { 0 };68 static bool resetSignaled = false;69 static bool resetSignaledLast = false;71 static int prevEmulatorType, prevBorder, prevWinBorder, prevBorderAuto;73 // little-endian integer pop/push functions:74 static inline uint32 Pop32(const uint8 * &ptr)75 {76 uint32 v = (ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24));77 ptr += 4;78 return v;79 }81 static inline uint16 Pop16(const uint8 * &ptr) /* const version */82 {83 uint16 v = (ptr[0] | (ptr[1] << 8));84 ptr += 2;85 return v;86 }88 static inline uint16 Pop16(uint8 * &ptr) /* non-const version */89 {90 uint16 v = (ptr[0] | (ptr[1] << 8));91 ptr += 2;92 return v;93 }95 static inline uint8 Pop8(const uint8 * &ptr)96 {97 return *(ptr)++;98 }100 static inline void Push32(uint32 v, uint8 * &ptr)101 {102 ptr[0] = (uint8)(v & 0xff);103 ptr[1] = (uint8)((v >> 8) & 0xff);104 ptr[2] = (uint8)((v >> 16) & 0xff);105 ptr[3] = (uint8)((v >> 24) & 0xff);106 ptr += 4;107 }109 static inline void Push16(uint16 v, uint8 * &ptr)110 {111 ptr[0] = (uint8)(v & 0xff);112 ptr[1] = (uint8)((v >> 8) & 0xff);113 ptr += 2;114 }116 static inline void Push8(uint8 v, uint8 * &ptr)117 {118 *ptr++ = v;119 }121 // little-endian integer read/write functions:122 static inline uint16 Read16(const uint8 *ptr)123 {124 return ptr[0] | (ptr[1] << 8);125 }127 static inline void Write16(uint16 v, uint8 *ptr)128 {129 ptr[0] = uint8(v & 0xff);130 ptr[1] = uint8((v >> 8) & 0xff);131 }133 static long file_length(FILE *fp)134 {135 long cur_pos = ftell(fp);136 fseek(fp, 0, SEEK_END);137 long length = ftell(fp);138 fseek(fp, cur_pos, SEEK_SET);139 return length;140 }142 static int bytes_per_frame(SMovie &mov)143 {144 int num_controllers = 0;146 for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i)147 if (mov.header.controllerFlags & MOVIE_CONTROLLER(i))148 ++num_controllers;150 return CONTROLLER_DATA_SIZE * num_controllers;151 }153 static void reserve_buffer_space(uint32 space_needed)154 {155 if (space_needed > Movie.inputBufferSize)156 {157 uint32 ptr_offset = Movie.inputBufferPtr - Movie.inputBuffer;158 uint32 alloc_chunks = (space_needed - 1) / BUFFER_GROWTH_SIZE + 1;159 uint32 old_size = Movie.inputBufferSize;160 Movie.inputBufferSize = BUFFER_GROWTH_SIZE * alloc_chunks;161 Movie.inputBuffer = (uint8 *)realloc(Movie.inputBuffer, Movie.inputBufferSize);162 // FIXME: this only fixes the random input problem during dma-frame-skip, but not the skip163 memset(Movie.inputBuffer + old_size, 0, Movie.inputBufferSize - old_size);164 Movie.inputBufferPtr = Movie.inputBuffer + ptr_offset;165 }166 }168 static int read_movie_header(FILE *file, SMovie &movie)169 {170 assert(file != NULL);171 assert(VBM_HEADER_SIZE == sizeof(SMovieFileHeader)); // sanity check on the header type definition173 uint8 headerData [VBM_HEADER_SIZE];175 if (fread(headerData, 1, VBM_HEADER_SIZE, file) != VBM_HEADER_SIZE)176 return MOVIE_WRONG_FORMAT; // if we failed to read in all VBM_HEADER_SIZE bytes of the header178 const uint8 * ptr = headerData;179 SMovieFileHeader &header = movie.header;181 header.magic = Pop32(ptr);182 if (header.magic != VBM_MAGIC)183 return MOVIE_WRONG_FORMAT;185 header.version = Pop32(ptr);186 if (header.version != VBM_VERSION)187 return MOVIE_WRONG_VERSION;189 header.uid = Pop32(ptr);190 header.length_frames = Pop32(ptr) + 1; // HACK: add 1 to the length for compatibility191 header.rerecord_count = Pop32(ptr);193 header.startFlags = Pop8(ptr);194 header.controllerFlags = Pop8(ptr);195 header.typeFlags = Pop8(ptr);196 header.optionFlags = Pop8(ptr);198 header.saveType = Pop32(ptr);199 header.flashSize = Pop32(ptr);200 header.gbEmulatorType = Pop32(ptr);202 for (int i = 0; i < 12; i++)203 header.romTitle[i] = Pop8(ptr);205 header.minorVersion = Pop8(ptr);207 header.romCRC = Pop8(ptr);208 header.romOrBiosChecksum = Pop16(ptr);209 header.romGameCode = Pop32(ptr);211 header.offset_to_savestate = Pop32(ptr);212 header.offset_to_controller_data = Pop32(ptr);214 return MOVIE_SUCCESS;215 }217 static void write_movie_header(FILE *file, const SMovie &movie)218 {219 assert(ftell(file) == 0); // we assume file points to beginning of movie file221 uint8 headerData [VBM_HEADER_SIZE];222 uint8 *ptr = headerData;223 const SMovieFileHeader &header = movie.header;225 Push32(header.magic, ptr);226 Push32(header.version, ptr);228 Push32(header.uid, ptr);229 Push32(header.length_frames - 1, ptr); // HACK: reduce the length by 1 for compatibility with certain faulty old tools230 // like TME231 Push32(header.rerecord_count, ptr);233 Push8(header.startFlags, ptr);234 Push8(header.controllerFlags, ptr);235 Push8(header.typeFlags, ptr);236 Push8(header.optionFlags, ptr);238 Push32(header.saveType, ptr);239 Push32(header.flashSize, ptr);240 Push32(header.gbEmulatorType, ptr);242 for (int i = 0; i < 12; ++i)243 Push8(header.romTitle[i], ptr);245 Push8(header.minorVersion, ptr);247 Push8(header.romCRC, ptr);248 Push16(header.romOrBiosChecksum, ptr);249 Push32(header.romGameCode, ptr);251 Push32(header.offset_to_savestate, ptr);252 Push32(header.offset_to_controller_data, ptr);254 fwrite(headerData, 1, VBM_HEADER_SIZE, file);255 }257 static void flush_movie_header()258 {259 assert(Movie.file != 0 && "logical error!");260 if (!Movie.file)261 return;263 long originalPos = ftell(Movie.file);265 // (over-)write the header266 fseek(Movie.file, 0, SEEK_SET);267 write_movie_header(Movie.file, Movie);269 fflush(Movie.file);271 fseek(Movie.file, originalPos, SEEK_SET);272 }274 static void flush_movie_frames()275 {276 assert(Movie.file && "logical error!");277 if (!Movie.file)278 return;280 long originalPos = ftell(Movie.file);282 // overwrite the controller data283 fseek(Movie.file, Movie.header.offset_to_controller_data, SEEK_SET);284 fwrite(Movie.inputBuffer, 1, Movie.bytesPerFrame * Movie.header.length_frames, Movie.file);286 fflush(Movie.file);288 fseek(Movie.file, originalPos, SEEK_SET);289 }291 static void truncate_movie(long length)292 {293 // truncate movie to length294 // NOTE: it's certain that the savestate block is never after the295 // controller data block, because the VBM format decrees it.297 assert(Movie.file && length >= 0);298 if (!Movie.file || length < 0)299 return;301 assert(Movie.header.offset_to_savestate <= Movie.header.offset_to_controller_data);302 if (Movie.header.offset_to_savestate > Movie.header.offset_to_controller_data)303 return;305 Movie.header.length_frames = length;306 flush_movie_header();307 const long truncLen = long(Movie.header.offset_to_controller_data + Movie.bytesPerFrame * length);308 if (file_length(Movie.file) != truncLen)309 {310 ftruncate(fileno(Movie.file), truncLen);311 }312 }314 static void remember_input_state()315 {316 for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i)317 {318 if (systemCartridgeType == 0)319 {320 initialInputs[i] = u16(~P1 & 0x03FF);321 }322 else323 {324 extern int32 gbJoymask[4];325 for (int i = 0; i < 4; ++i)326 initialInputs[i] = u16(gbJoymask[i] & 0xFFFF);327 }328 }329 }331 static void change_state(MovieState new_state)332 {333 #if (defined(WIN32) && !defined(SDL))334 theApp.frameSearching = false;335 theApp.frameSearchSkipping = false;336 #endif338 if (new_state == MOVIE_STATE_NONE)339 {340 Movie.pauseFrame = -1;342 if (Movie.state == MOVIE_STATE_NONE)343 return;345 truncate_movie(Movie.header.length_frames);347 fclose(Movie.file);348 Movie.file = NULL;349 Movie.currentFrame = 0;350 #if (defined(WIN32) && !defined(SDL))351 // undo changes to border settings352 {353 gbBorderOn = prevBorder;354 theApp.winGbBorderOn = prevWinBorder;355 gbBorderAutomatic = prevBorderAuto;356 systemGbBorderOn();357 }358 #endif359 gbEmulatorType = prevEmulatorType;361 extern int32 gbDMASpeedVersion;362 gbDMASpeedVersion = 1;364 extern int32 gbEchoRAMFixOn;365 gbEchoRAMFixOn = 1;367 gbNullInputHackTempEnabled = gbNullInputHackEnabled;369 if (Movie.inputBuffer)370 {371 free(Movie.inputBuffer);372 Movie.inputBuffer = NULL;373 }374 }375 else if (new_state == MOVIE_STATE_PLAY)376 {377 assert(Movie.file);379 // this would cause problems if not dealt with380 if (Movie.currentFrame >= Movie.header.length_frames)381 {382 new_state = MOVIE_STATE_END;383 Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * Movie.header.length_frames;384 }385 }386 else if (new_state == MOVIE_STATE_RECORD)387 {388 assert(Movie.file);390 // this would cause problems if not dealt with391 if (Movie.currentFrame > Movie.header.length_frames)392 {393 new_state = MOVIE_STATE_END;394 Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * Movie.header.length_frames;395 }397 fseek(Movie.file, Movie.header.offset_to_controller_data + Movie.bytesPerFrame * Movie.currentFrame, SEEK_SET);398 }400 if (new_state == MOVIE_STATE_END && Movie.state != MOVIE_STATE_END)401 {402 #if defined(SDL)403 systemClearJoypads();404 #endif405 systemScreenMessage("Movie end");406 }408 Movie.state = new_state;410 // checking for movie end411 bool willPause = false;413 // if the movie's been set to pause at a certain frame414 if (Movie.state != MOVIE_STATE_NONE && Movie.pauseFrame >= 0 && Movie.currentFrame == (uint32)Movie.pauseFrame)415 {416 Movie.pauseFrame = -1;417 willPause = true;418 }420 if (Movie.state == MOVIE_STATE_END)421 {422 if (Movie.currentFrame == Movie.header.length_frames)423 {424 #if (defined(WIN32) && !defined(SDL))425 if (theApp.movieOnEndPause)426 {427 willPause = true;428 }429 #else430 // SDL FIXME431 #endif433 #if (defined(WIN32) && !defined(SDL))434 switch (theApp.movieOnEndBehavior)435 {436 case 1:437 // the old behavior438 //VBAMovieRestart();439 break;440 case 2:441 #else442 // SDL FIXME443 #endif444 if (Movie.RecordedThisSession)445 {446 // if user has been recording this movie since the last time it started playing,447 // they probably don't want the movie to end now during playback,448 // so switch back to recording when it reaches the end449 VBAMovieSwitchToRecording();450 systemScreenMessage("Recording resumed");451 willPause = true;452 }453 #if (defined(WIN32) && !defined(SDL))454 break;455 case 3:456 // keep open457 break;458 case 0:459 // fall through460 default:461 // close movie462 //VBAMovieStop(false);463 break;464 }465 #else466 // SDL FIXME467 #endif468 }469 #if 1470 else if (Movie.currentFrame > Movie.header.length_frames)471 {472 #if (defined(WIN32) && !defined(SDL))473 switch (theApp.movieOnEndBehavior)474 {475 case 1:476 // FIXME: this should be delayed till the current frame ends477 VBAMovieRestart();478 break;479 case 2:480 // nothing481 break;482 case 3:483 // keep open484 break;485 case 0:486 // fall through487 default:488 // close movie489 VBAMovieStop(false);490 break;491 }492 #else493 // SDLFIXME494 #endif495 }496 #endif497 } // end if (Movie.state == MOVIE_STATE_END)499 if (willPause)500 {501 systemSetPause(true);502 }503 }505 void VBAMovieInit()506 {507 memset(&Movie, 0, sizeof(Movie));508 Movie.state = MOVIE_STATE_NONE;509 Movie.pauseFrame = -1;511 resetSignaled = false;512 resetSignaledLast = false;513 }515 void VBAMovieGetRomInfo(const SMovie &movieInfo, char romTitle [12], uint32 &romGameCode, uint16 &checksum, uint8 &crc)516 {517 if (systemCartridgeType == 0) // GBA518 {519 extern u8 *bios, *rom;520 memcpy(romTitle, &rom[0xa0], 12); // GBA TITLE521 memcpy(&romGameCode, &rom[0xac], 4); // GBA ROM GAME CODE522 if ((movieInfo.header.optionFlags & MOVIE_SETTING_USEBIOSFILE) != 0)523 checksum = utilCalcBIOSChecksum(bios, 4); // GBA BIOS CHECKSUM524 else525 checksum = 0;526 crc = rom[0xbd]; // GBA ROM CRC527 }528 else // non-GBA529 {530 extern u8 *gbRom;531 memcpy(romTitle, &gbRom[0x134], 12); // GB TITLE (note this can be 15 but is truncated to 12)532 romGameCode = (uint32)gbRom[0x146]; // GB ROM UNIT CODE534 checksum = (gbRom[0x14e] << 8) | gbRom[0x14f]; // GB ROM CHECKSUM, read from big-endian535 crc = gbRom[0x14d]; // GB ROM CRC536 }537 }539 #ifdef SDL540 static void GetBatterySaveName(char *buffer)541 {542 extern char batteryDir[2048], filename[2048]; // from SDL.cpp543 extern char *sdlGetFilename(char *name); // from SDL.cpp544 if (batteryDir[0])545 sprintf(buffer, "%s/%s.sav", batteryDir, sdlGetFilename(filename));546 else547 sprintf(buffer, "%s.sav", filename);548 }550 #endif552 static void SetPlayEmuSettings()553 {554 prevEmulatorType = gbEmulatorType;555 gbEmulatorType = Movie.header.gbEmulatorType;557 #if (defined(WIN32) && !defined(SDL))558 // theApp.removeIntros = false;559 theApp.skipBiosFile = (Movie.header.optionFlags & MOVIE_SETTING_SKIPBIOSFILE) != 0;560 theApp.useBiosFile = (Movie.header.optionFlags & MOVIE_SETTING_USEBIOSFILE) != 0;561 #else562 extern int saveType, sdlRtcEnable, sdlFlashSize; // from SDL.cpp563 extern bool8 useBios, skipBios, removeIntros; // from SDL.cpp564 useBios = (Movie.header.optionFlags & MOVIE_SETTING_USEBIOSFILE) != 0;565 skipBios = (Movie.header.optionFlags & MOVIE_SETTING_SKIPBIOSFILE) != 0;566 removeIntros = false /*(Movie.header.optionFlags & MOVIE_SETTING_REMOVEINTROS) != 0*/;567 #endif569 extern void SetPrefetchHack(bool);570 if (systemCartridgeType == 0) // lag disablement applies only to GBA571 SetPrefetchHack((Movie.header.optionFlags & MOVIE_SETTING_LAGHACK) != 0);573 gbNullInputHackTempEnabled = ((Movie.header.optionFlags & MOVIE_SETTING_GBINPUTHACK) != 0);575 // some GB/GBC games depend on the sound rate, so just use the highest one576 systemSoundSetQuality(1);577 useOldFrameTiming = false;579 extern int32 gbDMASpeedVersion;580 if ((Movie.header.optionFlags & MOVIE_SETTING_GBCFF55FIX) != 0)581 gbDMASpeedVersion = 1;582 else583 gbDMASpeedVersion = 0; // old CGB HDMA5 timing was used585 extern int32 gbEchoRAMFixOn;586 if ((Movie.header.optionFlags & MOVIE_SETTING_GBECHORAMFIX) != 0)587 gbEchoRAMFixOn = 1;588 else589 gbEchoRAMFixOn = 0;591 #if (defined(WIN32) && !defined(SDL))592 rtcEnable((Movie.header.optionFlags & MOVIE_SETTING_RTCENABLE) != 0);593 theApp.winSaveType = Movie.header.saveType;594 theApp.winFlashSize = Movie.header.flashSize;596 prevBorder = gbBorderOn;597 prevWinBorder = theApp.winGbBorderOn;598 prevBorderAuto = gbBorderAutomatic;599 if ((gbEmulatorType == 2 || gbEmulatorType == 5)600 && !theApp.hideMovieBorder) // games played in SGB mode can have a border601 {602 gbBorderOn = true;603 theApp.winGbBorderOn = true;604 gbBorderAutomatic = false;605 }606 else607 {608 gbBorderOn = false;609 theApp.winGbBorderOn = false;610 gbBorderAutomatic = false;611 if (theApp.hideMovieBorder)612 {613 theApp.hideMovieBorder = false;614 prevBorder = false; // it might be expected behaviour that it stays hidden after the movie615 }616 }617 systemGbBorderOn();618 #else619 sdlRtcEnable = (Movie.header.optionFlags & MOVIE_SETTING_RTCENABLE) != 0;620 saveType = Movie.header.saveType;621 sdlFlashSize = Movie.header.flashSize;622 #endif623 }625 static void HardResetAndSRAMClear()626 {627 #if (defined(WIN32) && !defined(SDL))628 winEraseBatteryFile(); // delete the damn SRAM file and keep it from being resurrected from RAM629 MainWnd *temp = ((MainWnd *)theApp.m_pMainWnd);630 if (!temp->winFileRun(true)) // restart running the game631 {632 temp->winFileClose();633 }634 #else635 char fname [1024];636 GetBatterySaveName(fname);637 remove(fname); // delete the damn SRAM file639 // Henceforth, emuCleanUp means "clear out SRAM"640 //theEmulator.emuCleanUp(); // keep it from being resurrected from RAM <--This is wrong, it'll deallocate all variables --Felipe642 /// FIXME the correct SDL code to call for a full restart isn't in a function yet643 theEmulator.emuReset(false);644 #endif645 }647 int VBAMovieOpen(const char *filename, bool8 read_only)648 {649 loadingMovie = true;650 uint8 movieReadOnly = read_only ? 1 : 0;652 FILE * file;653 STREAM stream;654 int result;655 int fn;657 char movie_filename[_MAX_PATH];658 #ifdef WIN32659 _fullpath(movie_filename, filename, _MAX_PATH);660 #else661 // SDL FIXME: convert to fullpath662 strncpy(movie_filename, filename, _MAX_PATH);663 movie_filename[_MAX_PATH - 1] = '\0';664 #endif666 if (movie_filename[0] == '\0')667 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }669 if (!emulating)670 { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; }672 // bool alreadyOpen = (Movie.file != NULL && _stricmp(movie_filename, Movie.filename) == 0);674 // if (alreadyOpen)675 change_state(MOVIE_STATE_NONE); // have to stop current movie before trying to re-open it677 if (!(file = fopen(movie_filename, "rb+")))678 if (!(file = fopen(movie_filename, "rb")))679 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }680 //else681 // movieReadOnly = 2; // we have to open the movie twice, no need to do this both times683 // if (!alreadyOpen)684 // change_state(MOVIE_STATE_NONE); // stop current movie when we're able to open the other one685 //686 // if (!(file = fopen(movie_filename, "rb+")))687 // if(!(file = fopen(movie_filename, "rb")))688 // {loadingMovie = false; return MOVIE_FILE_NOT_FOUND;}689 // else690 // movieReadOnly = 2;692 // clear out the current movie693 VBAMovieInit();695 // read header696 if ((result = read_movie_header(file, Movie)) != MOVIE_SUCCESS)697 {698 fclose(file);699 { loadingMovie = false; return result; }700 }702 // set emulator settings that make the movie more likely to stay synchronized703 SetPlayEmuSettings();705 // extern bool systemLoadBIOS();706 // if (!systemLoadBIOS())707 // { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; }709 // read the metadata / author info from file710 fread(Movie.authorInfo, 1, MOVIE_METADATA_SIZE, file);711 fn = dup(fileno(file)); // XXX: why does this fail?? it returns -1 but errno == 0712 fclose(file);714 // apparently this lseek is necessary715 lseek(fn, Movie.header.offset_to_savestate, SEEK_SET);716 if (!(stream = utilGzReopen(fn, "rb")))717 if (!(stream = utilGzOpen(movie_filename, "rb")))718 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }719 else720 fn = dup(fileno(file));721 // in case the above dup failed but opening the file normally doesn't fail723 if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT)724 {725 // load the snapshot726 result = theEmulator.emuReadStateFromStream(stream) ? MOVIE_SUCCESS : MOVIE_WRONG_FORMAT;728 // FIXME: Kludge for conversion729 remember_input_state();730 }731 else if (Movie.header.startFlags & MOVIE_START_FROM_SRAM)732 {733 // 'soft' reset:734 theEmulator.emuReset(false);736 // load the SRAM737 result = theEmulator.emuReadBatteryFromStream(stream) ? MOVIE_SUCCESS : MOVIE_WRONG_FORMAT;738 }739 else740 {741 HardResetAndSRAMClear();742 }744 utilGzClose(stream);746 if (result != MOVIE_SUCCESS)747 { loadingMovie = false; return result; }749 // if (!(file = fopen(movie_filename, /*read_only ? "rb" :*/ "rb+"))) // want to be able to switch out of read-only later750 // {751 // if(!Movie.readOnly || !(file = fopen(movie_filename, "rb"))) // try read-only if failed752 // return MOVIE_FILE_NOT_FOUND;753 // }754 if (!(file = fopen(movie_filename, "rb+")))755 if (!(file = fopen(movie_filename, "rb")))756 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }757 else758 movieReadOnly = 2;760 // recalculate length of movie from the file size761 Movie.bytesPerFrame = bytes_per_frame(Movie);762 fseek(file, 0, SEEK_END);763 long fileSize = ftell(file);764 Movie.header.length_frames = (fileSize - Movie.header.offset_to_controller_data) / Movie.bytesPerFrame;766 if (fseek(file, Movie.header.offset_to_controller_data, SEEK_SET))767 { fclose(file); loadingMovie = false; return MOVIE_WRONG_FORMAT; }769 strcpy(Movie.filename, movie_filename);770 Movie.file = file;771 Movie.inputBufferPtr = Movie.inputBuffer;772 Movie.currentFrame = 0;773 Movie.readOnly = movieReadOnly;774 Movie.RecordedThisSession = false;776 // read controller data777 uint32 to_read = Movie.bytesPerFrame * Movie.header.length_frames;778 reserve_buffer_space(to_read);779 fread(Movie.inputBuffer, 1, to_read, file);781 change_state(MOVIE_STATE_PLAY);783 char messageString[64] = "Movie ";784 bool converted = false;785 if (autoConvertMovieWhenPlaying)786 {787 int result = VBAMovieConvertCurrent();788 if (result == MOVIE_SUCCESS)789 strcat(messageString, "converted and ");790 else if (result == MOVIE_WRONG_VERSION)791 strcat(messageString, "higher revision ");792 }794 if (Movie.state == MOVIE_STATE_PLAY)795 strcat(messageString, "replaying ");796 else797 strcat(messageString, "finished ");798 if (Movie.readOnly)799 strcat(messageString, "(read)");800 else801 strcat(messageString, "(edit)");802 systemScreenMessage(messageString);804 VBAUpdateButtonPressDisplay();805 VBAUpdateFrameCountDisplay();806 systemRefreshScreen();808 { loadingMovie = false; return MOVIE_SUCCESS; }809 }811 static void SetRecordEmuSettings()812 {813 Movie.header.optionFlags = 0;814 #if (defined(WIN32) && !defined(SDL))815 if (theApp.useBiosFile)816 Movie.header.optionFlags |= MOVIE_SETTING_USEBIOSFILE;817 if (theApp.skipBiosFile)818 Movie.header.optionFlags |= MOVIE_SETTING_SKIPBIOSFILE;819 if (rtcIsEnabled())820 Movie.header.optionFlags |= MOVIE_SETTING_RTCENABLE;821 Movie.header.saveType = theApp.winSaveType;822 Movie.header.flashSize = theApp.winFlashSize;823 #else824 extern int saveType, sdlRtcEnable, sdlFlashSize; // from SDL.cpp825 extern bool8 useBios, skipBios; // from SDL.cpp826 if (useBios)827 Movie.header.optionFlags |= MOVIE_SETTING_USEBIOSFILE;828 if (skipBios)829 Movie.header.optionFlags |= MOVIE_SETTING_SKIPBIOSFILE;830 if (sdlRtcEnable)831 Movie.header.optionFlags |= MOVIE_SETTING_RTCENABLE;832 Movie.header.saveType = saveType;833 Movie.header.flashSize = sdlFlashSize;834 #endif835 prevEmulatorType = Movie.header.gbEmulatorType = gbEmulatorType;837 if (!memLagTempEnabled)838 Movie.header.optionFlags |= MOVIE_SETTING_LAGHACK;840 if (gbNullInputHackTempEnabled)841 Movie.header.optionFlags |= MOVIE_SETTING_GBINPUTHACK;843 Movie.header.optionFlags |= MOVIE_SETTING_GBCFF55FIX;844 extern int32 gbDMASpeedVersion;845 gbDMASpeedVersion = 1;847 Movie.header.optionFlags |= MOVIE_SETTING_GBECHORAMFIX;848 extern int32 gbEchoRAMFixOn;849 gbEchoRAMFixOn = 1;851 // some GB/GBC games depend on the sound rate, so just use the highest one852 systemSoundSetQuality(1);854 useOldFrameTiming = false;856 #if (defined(WIN32) && !defined(SDL))857 // theApp.removeIntros = false;859 prevBorder = gbBorderOn;860 prevWinBorder = theApp.winGbBorderOn;861 prevBorderAuto = gbBorderAutomatic;862 if (gbEmulatorType == 2 || gbEmulatorType == 5) // only games played in SGB mode will have a border863 {864 gbBorderOn = true;865 theApp.winGbBorderOn = true;866 gbBorderAutomatic = false;867 }868 else869 {870 gbBorderOn = false;871 theApp.winGbBorderOn = false;872 gbBorderAutomatic = false;873 }874 systemGbBorderOn();875 #else876 /// SDLFIXME877 #endif878 }880 uint16 VBAMovieGetCurrentInputOf(int controllerNum, bool normalOnly)881 {882 if (controllerNum < 0 || controllerNum >= MOVIE_NUM_OF_POSSIBLE_CONTROLLERS)883 return 0;885 return normalOnly ? (currentButtons[controllerNum] & BUTTON_REGULAR_MASK) : currentButtons[controllerNum];886 }888 int VBAMovieCreate(const char *filename, const char *authorInfo, uint8 startFlags, uint8 controllerFlags, uint8 typeFlags)889 {890 // make sure at least one controller is enabled891 if ((controllerFlags & MOVIE_CONTROLLERS_ANY_MASK) == 0)892 return MOVIE_WRONG_FORMAT;894 if (!emulating)895 return MOVIE_UNKNOWN_ERROR;897 loadingMovie = true;899 FILE * file;900 STREAM stream;901 int fn;903 char movie_filename [_MAX_PATH];904 #ifdef WIN32905 _fullpath(movie_filename, filename, _MAX_PATH);906 #else907 // FIXME: convert to fullpath908 strncpy(movie_filename, filename, _MAX_PATH);909 movie_filename[_MAX_PATH - 1] = '\0';910 #endif912 bool alreadyOpen = (Movie.file != NULL && stricmp(movie_filename, Movie.filename) == 0);914 if (alreadyOpen)915 change_state(MOVIE_STATE_NONE); // have to stop current movie before trying to re-open it917 if (movie_filename[0] == '\0')918 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }920 if (!(file = fopen(movie_filename, "wb")))921 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }923 if (!alreadyOpen)924 change_state(MOVIE_STATE_NONE); // stop current movie when we're able to open the other one926 // clear out the current movie927 printf("RLM: movie init\n");929 VBAMovieInit();931 // fill in the movie's header932 Movie.header.uid = (uint32)time(NULL);933 Movie.header.magic = VBM_MAGIC;934 Movie.header.version = VBM_VERSION;935 Movie.header.rerecord_count = 0;936 Movie.header.length_frames = 0;937 Movie.header.startFlags = startFlags;938 Movie.header.controllerFlags = controllerFlags;939 Movie.header.typeFlags = typeFlags;940 Movie.header.minorVersion = VBM_REVISION;942 // set emulator settings that make the movie more likely to stay synchronized when it's later played back943 SetRecordEmuSettings();945 // set ROM and BIOS checksums and stuff946 VBAMovieGetRomInfo(Movie, Movie.header.romTitle, Movie.header.romGameCode, Movie.header.romOrBiosChecksum, Movie.header.romCRC);948 printf("RLM: Writing movie header\n");949 // write the header to file950 write_movie_header(file, Movie);952 printf("RLM: setting metadata\n");954 // copy over the metadata / author info955 VBAMovieSetMetadata("________________Robert McIntyre______________________________________________________________________________________________________________________________________________________________________________________________________________________");957 printf("RLM: writing metadata\n");959 // write the metadata / author info to file962 fwrite(Movie.authorInfo, 1, sizeof(char) * MOVIE_METADATA_SIZE, file);964 // write snapshot or SRAM if applicable965 if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT966 || Movie.header.startFlags & MOVIE_START_FROM_SRAM)967 {968 Movie.header.offset_to_savestate = (uint32)ftell(file);970 // close the file and reopen it as a stream:972 fn = dup(fileno(file));973 fclose(file);975 if (!(stream = utilGzReopen(fn, "ab"))) // append mode to start at end, no seek necessary976 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }978 // write the save data:979 if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT)980 {981 // save snapshot982 if (!theEmulator.emuWriteStateToStream(stream))983 {984 utilGzClose(stream);985 { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; }986 }987 }988 else if (Movie.header.startFlags & MOVIE_START_FROM_SRAM)989 {990 // save SRAM991 if (!theEmulator.emuWriteBatteryToStream(stream))992 {993 utilGzClose(stream);994 { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; }995 }997 // 'soft' reset:998 theEmulator.emuReset(false);999 }1001 utilGzClose(stream);1003 // reopen the file and seek back to the end1005 if (!(file = fopen(movie_filename, "rb+")))1006 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }1008 fseek(file, 0, SEEK_END);1009 }1010 else // no snapshot or SRAM1011 {1012 HardResetAndSRAMClear();1013 }1015 Movie.header.offset_to_controller_data = (uint32)ftell(file);1017 strcpy(Movie.filename, movie_filename);1018 Movie.file = file;1019 Movie.bytesPerFrame = bytes_per_frame(Movie);1020 Movie.inputBufferPtr = Movie.inputBuffer;1021 Movie.currentFrame = 0;1022 Movie.readOnly = false;1023 Movie.RecordedThisSession = true;1025 change_state(MOVIE_STATE_RECORD);1027 systemScreenMessage("Recording movie...");1028 { loadingMovie = false; return MOVIE_SUCCESS; }1029 }1031 void VBAUpdateButtonPressDisplay()1032 {1033 uint32 keys = currentButtons[0] & BUTTON_REGULAR_RECORDING_MASK;1035 const static char KeyMap[] = { 'A', 'B', 's', 'S', '>', '<', '^', 'v', 'R', 'L', '!', '?', '{', '}', 'v', '^' };1036 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 { = } _1037 // ? !1038 char buffer[256];1039 sprintf(buffer, " ");1041 #ifndef WIN321042 // don't bother color-coding autofire and such1043 int i;1044 for (i = 0; i < 15; i++)1045 {1046 int j = KeyOrder[i];1047 int mask = (1 << (j));1048 buffer[strlen(" ") + i] = ((keys & mask) != 0) ? KeyMap[j] : ' ';1049 }1051 systemScreenMessage(buffer, 2, -1);1052 #else1053 const bool eraseAll = !theApp.inputDisplay;1054 uint32 autoHeldKeys = eraseAll ? 0 : theApp.autoHold & BUTTON_REGULAR_RECORDING_MASK;1055 uint32 autoFireKeys = eraseAll ? 0 : (theApp.autoFire | theApp.autoFire2) & BUTTON_REGULAR_RECORDING_MASK;1056 uint32 pressedKeys = eraseAll ? 0 : keys;1058 char colorList[64];1059 memset(colorList, 1, strlen(buffer));1061 if (!eraseAll)1062 {1063 for (int i = 0; i < 15; i++)1064 {1065 const int j = KeyOrder[i];1066 const int mask = (1 << (j));1067 bool pressed = (pressedKeys & mask) != 0;1068 const bool autoHeld = (autoHeldKeys & mask) != 0;1069 const bool autoFired = (autoFireKeys & mask) != 0;1070 const bool erased = (lastKeys & mask) != 0 && (!pressed && !autoHeld && !autoFired);1071 extern int textMethod;1072 if (textMethod != 2 && (autoHeld || (autoFired && !pressed) || erased))1073 {1074 int colorNum = 1; // default is white1075 if (autoHeld)1076 colorNum += (pressed ? 2 : 1); // yellow if pressed, red if not1077 else if (autoFired)1078 colorNum += 5; // blue if autofired and not currently pressed1079 else if (erased)1080 colorNum += 8; // black on black1082 colorList[strlen(" ") + i] = colorNum;1083 pressed = true;1084 }1085 buffer[strlen(" ") + i] = pressed ? KeyMap[j] : ' ';1086 }1087 }1089 lastKeys = currentButtons[0];1090 lastKeys |= theApp.autoHold & BUTTON_REGULAR_RECORDING_MASK;1091 lastKeys |= (theApp.autoFire | theApp.autoFire2) & BUTTON_REGULAR_RECORDING_MASK;1093 systemScreenMessage(buffer, 2, -1, colorList);1094 #endif1095 }1097 void VBAUpdateFrameCountDisplay()1098 {1099 const int MAGICAL_NUMBER = 64; // FIXME: this won't do any better, but only to remind you of sz issues1100 char frameDisplayString[MAGICAL_NUMBER];1101 char lagFrameDisplayString[MAGICAL_NUMBER];1102 char extraCountDisplayString[MAGICAL_NUMBER];1104 #if (defined(WIN32) && !defined(SDL))1105 if (theApp.frameCounter)1106 #else1107 /// SDL FIXME1108 #endif1109 {1110 switch (Movie.state)1111 {1112 case MOVIE_STATE_PLAY:1113 case MOVIE_STATE_END:1114 {1115 sprintf(frameDisplayString, "%d / %d", Movie.currentFrame, Movie.header.length_frames);1116 if (!Movie.readOnly)1117 strcat(frameDisplayString, " (edit)");1118 break;1119 }1120 case MOVIE_STATE_RECORD:1121 {1122 sprintf(frameDisplayString, "%d (record)", Movie.currentFrame);1123 break;1124 }1125 default:1126 {1127 sprintf(frameDisplayString, "%d (no movie)", systemCounters.frameCount);1128 break;1129 }1130 }1132 #if (defined(WIN32) && !defined(SDL))1133 if (theApp.lagCounter)1134 #else1135 /// SDL FIXME1136 #endif1137 {1138 // sprintf(lagFrameDisplayString, " %c %d", systemCounters.laggedLast ? '*' : '|', systemCounters.lagCount);1139 sprintf(lagFrameDisplayString, " | %d%s", systemCounters.lagCount, systemCounters.laggedLast ? " *" : "");1140 strcat(frameDisplayString, lagFrameDisplayString);1141 }1143 #if (defined(WIN32) && !defined(SDL))1144 if (theApp.extraCounter)1145 #else1146 /// SDL FIXME1147 #endif1148 {1149 sprintf(extraCountDisplayString, " | %d", systemCounters.frameCount - systemCounters.extraCount);1150 strcat(frameDisplayString, extraCountDisplayString);1151 }1152 }1153 #if (defined(WIN32) && !defined(SDL))1154 else1155 {1156 frameDisplayString[0] = '\0';1157 }1158 #else1159 /// SDL FIXME1160 #endif1161 systemScreenMessage(frameDisplayString, 1, -1);1162 }1164 // this function should only be called once every frame1165 void VBAMovieUpdateState()1166 {1167 ++Movie.currentFrame;1168 printf("RLM: inside updateState\n");1169 if (Movie.state == MOVIE_STATE_PLAY)1170 {1171 Movie.inputBufferPtr += Movie.bytesPerFrame;1172 if (Movie.currentFrame >= Movie.header.length_frames)1173 {1174 // the movie ends anyway; what to do next depends on the settings1175 change_state(MOVIE_STATE_END);1176 }1177 }1178 else if (Movie.state == MOVIE_STATE_RECORD)1179 {1180 printf("RLM: Movie_STATE_RECORD\n");1181 // use first fseek?1182 //TODO: THis is the problem.1183 fwrite(Movie.inputBufferPtr, 1, Movie.bytesPerFrame, Movie.file);1184 printf("RLM: write successful.\n");1185 Movie.header.length_frames = Movie.currentFrame;1186 Movie.inputBufferPtr += Movie.bytesPerFrame;1187 Movie.RecordedThisSession = true;1188 flush_movie_header();1189 }1190 else if (Movie.state == MOVIE_STATE_END)1191 {1192 change_state(MOVIE_STATE_END);1193 }1194 }1196 void VBAMovieRead(int i, bool /*sensor*/)1197 {1198 if (Movie.state != MOVIE_STATE_PLAY)1199 return;1201 if (i < 0 || i >= MOVIE_NUM_OF_POSSIBLE_CONTROLLERS)1202 return; // not a controller we're recognizing1204 if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i))1205 {1206 currentButtons[i] = Read16(Movie.inputBufferPtr + CONTROLLER_DATA_SIZE * i);1207 }1208 else1209 {1210 currentButtons[i] = 0; // pretend the controller is disconnected1211 }1213 if ((currentButtons[i] & BUTTON_MASK_NEW_RESET) != 0)1214 resetSignaled = true;1215 }1217 void VBAMovieWrite(int i, bool /*sensor*/)1218 {1219 if (Movie.state != MOVIE_STATE_RECORD)1220 return;1222 if (i < 0 || i >= MOVIE_NUM_OF_POSSIBLE_CONTROLLERS)1223 return; // not a controller we're recognizing1225 reserve_buffer_space((uint32)((Movie.inputBufferPtr - Movie.inputBuffer) + Movie.bytesPerFrame));1227 if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i))1228 {1229 // get the current controller data1230 uint16 buttonData = currentButtons[i];1232 // mask away the irrelevent bits1233 buttonData &= BUTTON_REGULAR_MASK | BUTTON_MOTION_MASK;1235 // soft-reset "button" for 1 frame if the game is reset while recording1236 if (resetSignaled)1237 {1238 buttonData |= BUTTON_MASK_NEW_RESET;1239 }1241 // backward compatibility kludge1242 if (resetSignaledLast)1243 {1244 buttonData |= BUTTON_MASK_OLD_RESET;1245 }1247 Write16(buttonData, Movie.inputBufferPtr + CONTROLLER_DATA_SIZE * i);1249 // and for display1250 currentButtons[i] = buttonData;1251 }1252 else1253 {1254 // pretend the controller is disconnected (otherwise input it gives could cause desync since we're not writing it to the1255 // movie)1256 currentButtons[i] = 0;1257 }1258 }1260 void VBAMovieStop(bool8 suppress_message)1261 {1262 if (Movie.state != MOVIE_STATE_NONE)1263 {1264 change_state(MOVIE_STATE_NONE);1265 if (!suppress_message)1266 systemScreenMessage("Movie stop");1267 }1268 }1270 int VBAMovieGetInfo(const char *filename, SMovie *info)1271 {1272 assert(info != NULL);1273 if (info == NULL)1274 return -1;1276 FILE * file;1277 int result;1278 SMovie &local_movie = *info;1280 memset(info, 0, sizeof(*info));1281 if (filename[0] == '\0')1282 return MOVIE_FILE_NOT_FOUND;1283 if (!(file = fopen(filename, "rb")))1284 return MOVIE_FILE_NOT_FOUND;1286 // read header1287 if ((result = (read_movie_header(file, local_movie))) != MOVIE_SUCCESS)1288 {1289 fclose(file);1290 return result;1291 }1293 // read the metadata / author info from file1294 fread(local_movie.authorInfo, 1, sizeof(char) * MOVIE_METADATA_SIZE, file);1296 strncpy(local_movie.filename, filename, _MAX_PATH);1297 local_movie.filename[_MAX_PATH - 1] = '\0';1299 if (Movie.file != NULL && stricmp(local_movie.filename, Movie.filename) == 0) // alreadyOpen1300 {1301 local_movie.bytesPerFrame = Movie.bytesPerFrame;1302 local_movie.header.length_frames = Movie.header.length_frames;1303 }1304 else1305 {1306 // recalculate length of movie from the file size1307 local_movie.bytesPerFrame = bytes_per_frame(local_movie);1308 fseek(file, 0, SEEK_END);1309 int fileSize = ftell(file);1310 local_movie.header.length_frames =1311 (fileSize - local_movie.header.offset_to_controller_data) / local_movie.bytesPerFrame;1312 }1314 fclose(file);1316 if (access(filename, W_OK))1317 info->readOnly = true;1319 return MOVIE_SUCCESS;1320 }1322 bool8 VBAMovieActive()1323 {1324 return (Movie.state != MOVIE_STATE_NONE);1325 }1327 bool8 VBAMovieLoading()1328 {1329 return loadingMovie;1330 }1332 bool8 VBAMoviePlaying()1333 {1334 return (Movie.state == MOVIE_STATE_PLAY);1335 }1337 bool8 VBAMovieRecording()1338 {1339 return (Movie.state == MOVIE_STATE_RECORD);1340 }1342 bool8 VBAMovieReadOnly()1343 {1344 if (!VBAMovieActive())1345 return false;1347 return Movie.readOnly;1348 }1350 void VBAMovieToggleReadOnly()1351 {1352 if (!VBAMovieActive())1353 return;1355 if (Movie.readOnly != 2)1356 {1357 Movie.readOnly = !Movie.readOnly;1359 systemScreenMessage(Movie.readOnly ? "Movie now read-only" : "Movie now editable");1360 }1361 else1362 {1363 systemScreenMessage("Can't toggle read-only movie");1364 }1365 }1367 uint32 VBAMovieGetVersion()1368 {1369 if (!VBAMovieActive())1370 return 0;1372 return Movie.header.version;1373 }1375 uint32 VBAMovieGetMinorVersion()1376 {1377 if (!VBAMovieActive())1378 return 0;1380 return Movie.header.minorVersion;1381 }1383 uint32 VBAMovieGetId()1384 {1385 if (!VBAMovieActive())1386 return 0;1388 return Movie.header.uid;1389 }1391 uint32 VBAMovieGetLength()1392 {1393 if (!VBAMovieActive())1394 return 0;1396 return Movie.header.length_frames;1397 }1399 uint32 VBAMovieGetFrameCounter()1400 {1401 if (!VBAMovieActive())1402 return 0;1404 return Movie.currentFrame;1405 }1407 uint32 VBAMovieGetRerecordCount()1408 {1409 if (!VBAMovieActive())1410 return 0;1412 return Movie.header.rerecord_count;1413 }1415 uint32 VBAMovieSetRerecordCount(uint32 newRerecordCount)1416 {1417 uint32 oldRerecordCount = 0;1418 if (!VBAMovieActive())1419 return 0;1421 oldRerecordCount = Movie.header.rerecord_count;1422 Movie.header.rerecord_count = newRerecordCount;1423 return oldRerecordCount;1424 }1426 std::string VBAMovieGetAuthorInfo()1427 {1428 if (!VBAMovieActive())1429 return "";1431 return Movie.authorInfo;1432 }1434 std::string VBAMovieGetFilename()1435 {1436 if (!VBAMovieActive())1437 return "";1439 return Movie.filename;1440 }1442 void VBAMovieFreeze(uint8 * *buf, uint32 *size)1443 {1444 // sanity check1445 if (!VBAMovieActive())1446 {1447 return;1448 }1450 *buf = NULL;1451 *size = 0;1453 // compute size needed for the buffer1454 // room for header.uid, currentFrame, and header.length_frames1455 uint32 size_needed = sizeof(Movie.header.uid) + sizeof(Movie.currentFrame) + sizeof(Movie.header.length_frames);1456 size_needed += (uint32)(Movie.bytesPerFrame * Movie.header.length_frames);1457 *buf = new uint8[size_needed];1458 *size = size_needed;1460 uint8 *ptr = *buf;1461 if (!ptr)1462 {1463 return;1464 }1466 Push32(Movie.header.uid, ptr);1467 Push32(Movie.currentFrame, ptr);1468 Push32(Movie.header.length_frames - 1, ptr); // HACK: shorten the length by 1 for backward compatibility1470 memcpy(ptr, Movie.inputBuffer, Movie.bytesPerFrame * Movie.header.length_frames);1471 }1473 int VBAMovieUnfreeze(const uint8 *buf, uint32 size)1474 {1475 // sanity check1476 if (!VBAMovieActive())1477 {1478 return MOVIE_NOT_FROM_A_MOVIE;1479 }1481 const uint8 *ptr = buf;1482 if (size < sizeof(Movie.header.uid) + sizeof(Movie.currentFrame) + sizeof(Movie.header.length_frames))1483 {1484 return MOVIE_WRONG_FORMAT;1485 }1487 uint32 movie_id = Pop32(ptr);1488 uint32 current_frame = Pop32(ptr);1489 uint32 end_frame = Pop32(ptr) + 1; // HACK: restore the length for backward compatibility1490 uint32 space_needed = Movie.bytesPerFrame * end_frame;1492 if (movie_id != Movie.header.uid)1493 return MOVIE_NOT_FROM_THIS_MOVIE;1495 if (space_needed > size)1496 return MOVIE_WRONG_FORMAT;1498 if (Movie.readOnly)1499 {1500 // here, we are going to keep the input data from the movie file1501 // and simply rewind to the currentFrame pointer1502 // this will cause a desync if the savestate is not in sync // <-- NOT ANYMORE1503 // with the on-disk recording data, but it's easily solved1504 // by loading another savestate or playing the movie from the beginning1506 // don't allow loading a state inconsistent with the current movie1507 uint32 length_history = min(current_frame, Movie.header.length_frames);1508 if (end_frame < length_history)1509 return MOVIE_SNAPSHOT_INCONSISTENT;1511 uint32 space_shared = Movie.bytesPerFrame * length_history;1512 if (memcmp(Movie.inputBuffer, ptr, space_shared))1513 return MOVIE_SNAPSHOT_INCONSISTENT;1515 Movie.currentFrame = current_frame;1516 Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * min(current_frame, Movie.header.length_frames);1517 }1518 else1519 {1520 // here, we are going to take the input data from the savestate1521 // and make it the input data for the current movie, then continue1522 // writing new input data at the currentFrame pointer1523 Movie.currentFrame = current_frame;1524 Movie.header.length_frames = end_frame;1525 if (!VBALuaRerecordCountSkip())1526 ++Movie.header.rerecord_count;1528 Movie.RecordedThisSession = true;1530 // do this before calling reserve_buffer_space()1531 Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * min(current_frame, Movie.header.length_frames);1532 reserve_buffer_space(space_needed);1533 memcpy(Movie.inputBuffer, ptr, space_needed);1535 // for consistency, no auto movie conversion here since we don't auto convert the corresponding savestate1536 flush_movie_header();1537 flush_movie_frames();1538 }1540 change_state(MOVIE_STATE_PLAY); // check for movie end1542 // necessary!1543 resetSignaled = false;1544 resetSignaledLast = false;1546 // necessary to check if there's a reset signal at the previous frame1547 if (current_frame > 0)1548 {1549 const u8 NEW_RESET = u8(BUTTON_MASK_NEW_RESET >> 8);1550 for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i)1551 {1552 if ((Movie.header.controllerFlags & MOVIE_CONTROLLER(i)) && (*(Movie.inputBufferPtr+1- Movie.bytesPerFrame) & NEW_RESET))1553 {1554 resetSignaledLast = true;1555 break;1556 }1557 }1558 }1560 return MOVIE_SUCCESS;1561 }1563 bool VBAMovieEnded()1564 {1565 return (Movie.state == MOVIE_STATE_END);1566 // return (Movie.state != MOVIE_STATE_NONE && Movie.currentFrame >= Movie.header.length_frames);1567 }1569 bool VBAMovieAllowsRerecording()1570 {1571 bool allows = (Movie.state != MOVIE_STATE_NONE) && (Movie.currentFrame <= Movie.header.length_frames);1572 return /*!VBAMovieReadOnly() &&*/ allows;1573 }1575 bool VBAMovieSwitchToPlaying()1576 {1577 if (!VBAMovieActive())1578 return false;1580 if (!Movie.readOnly)1581 {1582 VBAMovieToggleReadOnly();1583 }1585 change_state(MOVIE_STATE_PLAY);1586 if (Movie.state == MOVIE_STATE_PLAY)1587 systemScreenMessage("Movie replay (continue)");1588 else1589 systemScreenMessage("Movie end");1591 return true;1592 }1594 bool VBAMovieSwitchToRecording()1595 {1596 if (!VBAMovieAllowsRerecording())1597 return false;1599 if (Movie.readOnly)1600 {1601 VBAMovieToggleReadOnly();1602 }1604 if (!VBALuaRerecordCountSkip())1605 ++Movie.header.rerecord_count;1607 change_state(MOVIE_STATE_RECORD);1608 systemScreenMessage("Movie re-record");1610 //truncate_movie(Movie.currentFrame);1612 return true;1613 }1615 uint32 VBAMovieGetState()1616 {1617 // ?1618 if (!VBAMovieActive())1619 return MOVIE_STATE_NONE;1621 return Movie.state;1622 }1624 void VBAMovieSignalReset()1625 {1626 if (VBAMovieActive())1627 resetSignaled = true;1628 }1630 void VBAMovieResetIfRequested()1631 {1632 if (resetSignaled)1633 {1634 theEmulator.emuReset(false);1635 resetSignaled = false;1636 resetSignaledLast = true;1637 }1638 else1639 {1640 resetSignaledLast = false;1641 }1642 }1644 void VBAMovieSetMetadata(const char *info)1645 {1646 if (!memcmp(Movie.authorInfo, info, MOVIE_METADATA_SIZE))1647 return;1649 memcpy(Movie.authorInfo, info, MOVIE_METADATA_SIZE); // strncpy would omit post-0 bytes1650 Movie.authorInfo[MOVIE_METADATA_SIZE - 1] = '\0';1652 if (Movie.file)1653 {1654 // (over-)write the header1655 fseek(Movie.file, 0, SEEK_SET);1657 write_movie_header(Movie.file, Movie);1659 // write the metadata / author info to file1660 fwrite(Movie.authorInfo, 1, sizeof(char) * MOVIE_METADATA_SIZE, Movie.file);1662 fflush(Movie.file);1663 }1664 printf("RLM: setMetadata called\n");1666 }1668 void VBAMovieRestart()1669 {1670 if (VBAMovieActive())1671 {1672 systemSoundClearBuffer();1674 bool8 modified = Movie.RecordedThisSession;1676 VBAMovieStop(true);1678 char movieName [_MAX_PATH];1679 strncpy(movieName, Movie.filename, _MAX_PATH);1680 movieName[_MAX_PATH - 1] = '\0';1681 VBAMovieOpen(movieName, Movie.readOnly); // can't just pass in Movie.filename, since VBAMovieOpen clears out Movie's1682 // variables1684 Movie.RecordedThisSession = modified;1686 systemScreenMessage("Movie replay (restart)");1687 }1688 }1690 int VBAMovieGetPauseAt()1691 {1692 return Movie.pauseFrame;1693 }1695 void VBAMovieSetPauseAt(int at)1696 {1697 Movie.pauseFrame = at;1698 }1700 ///////////////////////1701 // movie tools1703 // FIXME: is it safe to convert/flush a movie while recording it (considering fseek() problem)?1704 int VBAMovieConvertCurrent()1705 {1706 if (!VBAMovieActive())1707 {1708 return MOVIE_NOTHING;1709 }1711 if (Movie.header.minorVersion > VBM_REVISION)1712 {1713 return MOVIE_WRONG_VERSION;1714 }1716 if (Movie.header.minorVersion == VBM_REVISION)1717 {1718 return MOVIE_NOTHING;1719 }1721 Movie.header.minorVersion = VBM_REVISION;1723 if (Movie.header.length_frames == 0) // this could happen1724 {1725 truncate_movie(0);1726 return MOVIE_SUCCESS;1727 }1729 // fix movies recorded from snapshots1730 if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT)1731 {1732 uint8 *firstFramePtr = Movie.inputBuffer;1733 for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i)1734 {1735 if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i))1736 {1737 Push16(initialInputs[i], firstFramePtr);1738 // note: this is correct since Push16 advances the dest pointer by sizeof u161739 }1740 }1741 }1743 // convert old resets to new ones1744 const u8 OLD_RESET = u8(BUTTON_MASK_OLD_RESET >> 8);1745 const u8 NEW_RESET = u8(BUTTON_MASK_NEW_RESET >> 8);1746 for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i)1747 {1748 if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i))1749 {1750 uint8 *startPtr = Movie.inputBuffer + sizeof(u16) * i + 1;1751 uint8 *endPtr = Movie.inputBuffer + Movie.bytesPerFrame * (Movie.header.length_frames - 1);1752 for (; startPtr < endPtr; startPtr += Movie.bytesPerFrame)1753 {1754 if (startPtr[Movie.bytesPerFrame] & OLD_RESET)1755 {1756 startPtr[0] |= NEW_RESET;1757 }1758 }1759 }1760 }1762 flush_movie_header();1763 flush_movie_frames();1764 return MOVIE_SUCCESS;1765 }1767 bool VBAMovieTuncateAtCurrentFrame()1768 {1769 if (!VBAMovieActive())1770 return false;1772 truncate_movie(Movie.currentFrame);1773 change_state(MOVIE_STATE_END);1774 systemScreenMessage("Movie truncated");1776 return true;1777 }1779 bool VBAMovieFixHeader()1780 {1781 if (!VBAMovieActive())1782 return false;1784 flush_movie_header();1785 systemScreenMessage("Movie header fixed");1786 return true;1787 }