Mercurial > vba-clojure
view src/common/movie.cpp @ 551:b69a3dba8045
condensed gameboy program into a contigous sequence of bytes.
author | Robert McIntyre <rlm@mit.edu> |
---|---|
date | Thu, 30 Aug 2012 11:19:52 -0500 |
parents | 3e36553d0cbf |
children |
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 SDL.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, 0, 0, 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 }375 }376 else if (new_state == MOVIE_STATE_PLAY)377 {378 assert(Movie.file);380 // this would cause problems if not dealt with381 if (Movie.currentFrame >= Movie.header.length_frames)382 {383 new_state = MOVIE_STATE_END;384 Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * Movie.header.length_frames;385 }386 }387 else if (new_state == MOVIE_STATE_RECORD)388 {389 assert(Movie.file);391 // this would cause problems if not dealt with392 if (Movie.currentFrame > Movie.header.length_frames)393 {394 new_state = MOVIE_STATE_END;395 Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * Movie.header.length_frames;396 }398 fseek(Movie.file, Movie.header.offset_to_controller_data + Movie.bytesPerFrame * Movie.currentFrame, SEEK_SET);399 }401 if (new_state == MOVIE_STATE_END && Movie.state != MOVIE_STATE_END)402 {403 #if defined(SDL)404 systemClearJoypads();405 #endif406 systemScreenMessage("Movie end");407 }409 Movie.state = new_state;411 // checking for movie end412 bool willPause = false;414 // if the movie's been set to pause at a certain frame415 if (Movie.state != MOVIE_STATE_NONE && Movie.pauseFrame >= 0 && Movie.currentFrame == (uint32)Movie.pauseFrame)416 {417 Movie.pauseFrame = -1;418 willPause = true;419 }421 if (Movie.state == MOVIE_STATE_END)422 {423 if (Movie.currentFrame == Movie.header.length_frames)424 {425 #if (defined(WIN32) && !defined(SDL))426 if (theApp.movieOnEndPause)427 {428 willPause = true;429 }430 #else431 // SDL FIXME432 #endif434 #if (defined(WIN32) && !defined(SDL))435 switch (theApp.movieOnEndBehavior)436 {437 case 1:438 // the old behavior439 //VBAMovieRestart();440 break;441 case 2:442 #else443 // SDL FIXME444 #endif445 if (Movie.RecordedThisSession)446 {447 // if user has been recording this movie since the last time it started playing,448 // they probably don't want the movie to end now during playback,449 // so switch back to recording when it reaches the end450 VBAMovieSwitchToRecording();451 systemScreenMessage("Recording resumed");452 willPause = true;453 }454 #if (defined(WIN32) && !defined(SDL))455 break;456 case 3:457 // keep open458 break;459 case 0:460 // fall through461 default:462 // close movie463 //VBAMovieStop(false);464 break;465 }466 #else467 // SDL FIXME468 #endif469 }470 #if 1471 else if (Movie.currentFrame > Movie.header.length_frames)472 {473 #if (defined(WIN32) && !defined(SDL))474 switch (theApp.movieOnEndBehavior)475 {476 case 1:477 // FIXME: this should be delayed till the current frame ends478 VBAMovieRestart();479 break;480 case 2:481 // nothing482 break;483 case 3:484 // keep open485 break;486 case 0:487 // fall through488 default:489 // close movie490 VBAMovieStop(false);491 break;492 }493 #else494 // SDLFIXME495 #endif496 }497 #endif498 } // end if (Movie.state == MOVIE_STATE_END)500 if (willPause)501 {502 systemSetPause(true);503 }504 }506 void VBAMovieInit()507 {508 memset(&Movie, 0, sizeof(Movie));509 Movie.state = MOVIE_STATE_NONE;510 Movie.pauseFrame = -1;512 resetSignaled = false;513 resetSignaledLast = false;515 // RLM: should probably add inputBuffer initialization here.516 reserve_buffer_space(90001);517 }519 void VBAMovieGetRomInfo(const SMovie &movieInfo, char romTitle [12], uint32 &romGameCode, uint16 &checksum, uint8 &crc)520 {521 if (systemCartridgeType == 0) // GBA522 {523 extern u8 *bios, *rom;524 memcpy(romTitle, &rom[0xa0], 12); // GBA TITLE525 memcpy(&romGameCode, &rom[0xac], 4); // GBA ROM GAME CODE526 if ((movieInfo.header.optionFlags & MOVIE_SETTING_USEBIOSFILE) != 0)527 checksum = utilCalcBIOSChecksum(bios, 4); // GBA BIOS CHECKSUM528 else529 checksum = 0;530 crc = rom[0xbd]; // GBA ROM CRC531 }532 else // non-GBA533 {534 extern u8 *gbRom;535 memcpy(romTitle, &gbRom[0x134], 12); // GB TITLE (note this can be 15 but is truncated to 12)536 romGameCode = (uint32)gbRom[0x146]; // GB ROM UNIT CODE538 checksum = (gbRom[0x14e] << 8) | gbRom[0x14f]; // GB ROM CHECKSUM, read from big-endian539 crc = gbRom[0x14d]; // GB ROM CRC540 }541 }543 #ifdef SDL544 static void GetBatterySaveName(char *buffer)545 {546 extern char batteryDir[2048], filename[2048]; // from SDL.cpp547 extern char *sdlGetFilename(char *name); // from SDL.cpp548 if (batteryDir[0])549 sprintf(buffer, "%s/%s.sav", batteryDir, sdlGetFilename(filename));550 else551 sprintf(buffer, "%s.sav", filename);552 }554 #endif556 static void SetPlayEmuSettings()557 {558 prevEmulatorType = gbEmulatorType;559 gbEmulatorType = Movie.header.gbEmulatorType;561 #if (defined(WIN32) && !defined(SDL))562 // theApp.removeIntros = false;563 theApp.skipBiosFile = (Movie.header.optionFlags & MOVIE_SETTING_SKIPBIOSFILE) != 0;564 theApp.useBiosFile = (Movie.header.optionFlags & MOVIE_SETTING_USEBIOSFILE) != 0;565 #else566 extern int saveType, sdlRtcEnable, sdlFlashSize; // from SDL.cpp567 extern bool8 useBios, skipBios, removeIntros; // from SDL.cpp568 useBios = (Movie.header.optionFlags & MOVIE_SETTING_USEBIOSFILE) != 0;569 skipBios = (Movie.header.optionFlags & MOVIE_SETTING_SKIPBIOSFILE) != 0;570 removeIntros = false /*(Movie.header.optionFlags & MOVIE_SETTING_REMOVEINTROS) != 0*/;571 #endif573 extern void SetPrefetchHack(bool);574 if (systemCartridgeType == 0) // lag disablement applies only to GBA575 SetPrefetchHack((Movie.header.optionFlags & MOVIE_SETTING_LAGHACK) != 0);577 gbNullInputHackTempEnabled = ((Movie.header.optionFlags & MOVIE_SETTING_GBINPUTHACK) != 0);579 // some GB/GBC games depend on the sound rate, so just use the highest one580 systemSoundSetQuality(1);581 useOldFrameTiming = false;583 extern int32 gbDMASpeedVersion;584 if ((Movie.header.optionFlags & MOVIE_SETTING_GBCFF55FIX) != 0)585 gbDMASpeedVersion = 1;586 else587 gbDMASpeedVersion = 0; // old CGB HDMA5 timing was used589 extern int32 gbEchoRAMFixOn;590 if ((Movie.header.optionFlags & MOVIE_SETTING_GBECHORAMFIX) != 0)591 gbEchoRAMFixOn = 1;592 else593 gbEchoRAMFixOn = 0;595 #if (defined(WIN32) && !defined(SDL))596 rtcEnable((Movie.header.optionFlags & MOVIE_SETTING_RTCENABLE) != 0);597 theApp.winSaveType = Movie.header.saveType;598 theApp.winFlashSize = Movie.header.flashSize;600 prevBorder = gbBorderOn;601 prevWinBorder = theApp.winGbBorderOn;602 prevBorderAuto = gbBorderAutomatic;603 if ((gbEmulatorType == 2 || gbEmulatorType == 5)604 && !theApp.hideMovieBorder) // games played in SGB mode can have a border605 {606 gbBorderOn = true;607 theApp.winGbBorderOn = true;608 gbBorderAutomatic = false;609 }610 else611 {612 gbBorderOn = false;613 theApp.winGbBorderOn = false;614 gbBorderAutomatic = false;615 if (theApp.hideMovieBorder)616 {617 theApp.hideMovieBorder = false;618 prevBorder = false; // it might be expected behaviour that it stays hidden after the movie619 }620 }621 systemGbBorderOn();622 #else623 sdlRtcEnable = (Movie.header.optionFlags & MOVIE_SETTING_RTCENABLE) != 0;624 saveType = Movie.header.saveType;625 sdlFlashSize = Movie.header.flashSize;626 #endif627 }629 static void HardResetAndSRAMClear()630 {631 #if (defined(WIN32) && !defined(SDL))632 winEraseBatteryFile(); // delete the damn SRAM file and keep it from being resurrected from RAM633 MainWnd *temp = ((MainWnd *)theApp.m_pMainWnd);634 if (!temp->winFileRun(true)) // restart running the game635 {636 temp->winFileClose();637 }638 #else639 char fname [1024];640 GetBatterySaveName(fname);641 remove(fname); // delete the damn SRAM file643 // Henceforth, emuCleanUp means "clear out SRAM"644 //theEmulator.emuCleanUp(); // keep it from being resurrected from RAM <--This is wrong, it'll deallocate all variables --Felipe646 /// FIXME the correct SDL code to call for a full restart isn't in a function yet647 theEmulator.emuReset(false);648 #endif649 }651 int VBAMovieOpen(const char *filename, bool8 read_only)652 {653 loadingMovie = true;654 uint8 movieReadOnly = read_only ? 1 : 0;656 FILE * file;657 STREAM stream;658 int result;659 int fn;661 char movie_filename[_MAX_PATH];662 #ifdef WIN32663 _fullpath(movie_filename, filename, _MAX_PATH);664 #else665 // SDL FIXME: convert to fullpath666 strncpy(movie_filename, filename, _MAX_PATH);667 movie_filename[_MAX_PATH - 1] = '\0';668 #endif670 if (movie_filename[0] == '\0')671 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }673 if (!emulating)674 { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; }676 // bool alreadyOpen = (Movie.file != NULL && _stricmp(movie_filename, Movie.filename) == 0);678 // if (alreadyOpen)679 change_state(MOVIE_STATE_NONE); // have to stop current movie before trying to re-open it681 if (!(file = fopen(movie_filename, "rb+")))682 if (!(file = fopen(movie_filename, "rb")))683 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }684 //else685 // movieReadOnly = 2; // we have to open the movie twice, no need to do this both times687 // if (!alreadyOpen)688 // change_state(MOVIE_STATE_NONE); // stop current movie when we're able to open the other one689 //690 // if (!(file = fopen(movie_filename, "rb+")))691 // if(!(file = fopen(movie_filename, "rb")))692 // {loadingMovie = false; return MOVIE_FILE_NOT_FOUND;}693 // else694 // movieReadOnly = 2;696 // clear out the current movie697 VBAMovieInit();699 // read header700 if ((result = read_movie_header(file, Movie)) != MOVIE_SUCCESS)701 {702 fclose(file);703 { loadingMovie = false; return result; }704 }706 // set emulator settings that make the movie more likely to stay synchronized707 SetPlayEmuSettings();709 // extern bool systemLoadBIOS();710 // if (!systemLoadBIOS())711 // { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; }713 // read the metadata / author info from file714 fread(Movie.authorInfo, 1, MOVIE_METADATA_SIZE, file);715 fn = dup(fileno(file)); // XXX: why does this fail?? it returns -1 but errno == 0716 fclose(file);718 // apparently this lseek is necessary719 lseek(fn, Movie.header.offset_to_savestate, SEEK_SET);720 if (!(stream = utilGzReopen(fn, "rb")))721 if (!(stream = utilGzOpen(movie_filename, "rb")))722 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }723 else724 fn = dup(fileno(file));725 // in case the above dup failed but opening the file normally doesn't fail727 if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT)728 {729 // load the snapshot730 result = theEmulator.emuReadStateFromStream(stream) ? MOVIE_SUCCESS : MOVIE_WRONG_FORMAT;732 // FIXME: Kludge for conversion733 remember_input_state();734 }735 else if (Movie.header.startFlags & MOVIE_START_FROM_SRAM)736 {737 // 'soft' reset:738 theEmulator.emuReset(false);740 // load the SRAM741 result = theEmulator.emuReadBatteryFromStream(stream) ? MOVIE_SUCCESS : MOVIE_WRONG_FORMAT;742 }743 else744 {745 HardResetAndSRAMClear();746 }748 utilGzClose(stream);750 if (result != MOVIE_SUCCESS)751 { loadingMovie = false; return result; }753 // if (!(file = fopen(movie_filename, /*read_only ? "rb" :*/ "rb+"))) // want to be able to switch out of read-only later754 // {755 // if(!Movie.readOnly || !(file = fopen(movie_filename, "rb"))) // try read-only if failed756 // return MOVIE_FILE_NOT_FOUND;757 // }758 if (!(file = fopen(movie_filename, "rb+")))759 if (!(file = fopen(movie_filename, "rb")))760 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }761 else762 movieReadOnly = 2;764 // recalculate length of movie from the file size765 Movie.bytesPerFrame = bytes_per_frame(Movie);766 fseek(file, 0, SEEK_END);767 long fileSize = ftell(file);768 Movie.header.length_frames = (fileSize - Movie.header.offset_to_controller_data) / Movie.bytesPerFrame;770 if (fseek(file, Movie.header.offset_to_controller_data, SEEK_SET))771 { fclose(file); loadingMovie = false; return MOVIE_WRONG_FORMAT; }773 strcpy(Movie.filename, movie_filename);774 Movie.file = file;775 Movie.inputBufferPtr = Movie.inputBuffer;776 Movie.currentFrame = 0;777 Movie.readOnly = movieReadOnly;778 Movie.RecordedThisSession = false;780 // read controller data781 uint32 to_read = Movie.bytesPerFrame * Movie.header.length_frames;782 reserve_buffer_space(to_read);783 fread(Movie.inputBuffer, 1, to_read, file);785 change_state(MOVIE_STATE_PLAY);787 char messageString[64] = "Movie ";788 bool converted = false;789 if (autoConvertMovieWhenPlaying)790 {791 int result = VBAMovieConvertCurrent();792 if (result == MOVIE_SUCCESS)793 strcat(messageString, "converted and ");794 else if (result == MOVIE_WRONG_VERSION)795 strcat(messageString, "higher revision ");796 }798 if (Movie.state == MOVIE_STATE_PLAY)799 strcat(messageString, "replaying ");800 else801 strcat(messageString, "finished ");802 if (Movie.readOnly)803 strcat(messageString, "(read)");804 else805 strcat(messageString, "(edit)");806 systemScreenMessage(messageString);808 VBAUpdateButtonPressDisplay();809 VBAUpdateFrameCountDisplay();810 systemRefreshScreen();812 { loadingMovie = false; return MOVIE_SUCCESS; }813 }815 static void SetRecordEmuSettings()816 {817 Movie.header.optionFlags = 0;818 #if (defined(WIN32) && !defined(SDL))819 if (theApp.useBiosFile)820 Movie.header.optionFlags |= MOVIE_SETTING_USEBIOSFILE;821 if (theApp.skipBiosFile)822 Movie.header.optionFlags |= MOVIE_SETTING_SKIPBIOSFILE;823 if (rtcIsEnabled())824 Movie.header.optionFlags |= MOVIE_SETTING_RTCENABLE;825 Movie.header.saveType = theApp.winSaveType;826 Movie.header.flashSize = theApp.winFlashSize;827 #else828 extern int saveType, sdlRtcEnable, sdlFlashSize; // from SDL.cpp829 extern bool8 useBios, skipBios; // from SDL.cpp830 if (useBios)831 Movie.header.optionFlags |= MOVIE_SETTING_USEBIOSFILE;832 if (skipBios)833 Movie.header.optionFlags |= MOVIE_SETTING_SKIPBIOSFILE;834 if (sdlRtcEnable)835 Movie.header.optionFlags |= MOVIE_SETTING_RTCENABLE;836 Movie.header.saveType = saveType;837 Movie.header.flashSize = sdlFlashSize;838 #endif839 prevEmulatorType = Movie.header.gbEmulatorType = gbEmulatorType;841 if (!memLagTempEnabled)842 Movie.header.optionFlags |= MOVIE_SETTING_LAGHACK;844 if (gbNullInputHackTempEnabled)845 Movie.header.optionFlags |= MOVIE_SETTING_GBINPUTHACK;847 Movie.header.optionFlags |= MOVIE_SETTING_GBCFF55FIX;848 extern int32 gbDMASpeedVersion;849 gbDMASpeedVersion = 1;851 Movie.header.optionFlags |= MOVIE_SETTING_GBECHORAMFIX;852 extern int32 gbEchoRAMFixOn;853 gbEchoRAMFixOn = 1;855 // some GB/GBC games depend on the sound rate, so just use the highest one856 systemSoundSetQuality(1);858 useOldFrameTiming = false;860 #if (defined(WIN32) && !defined(SDL))861 // theApp.removeIntros = false;863 prevBorder = gbBorderOn;864 prevWinBorder = theApp.winGbBorderOn;865 prevBorderAuto = gbBorderAutomatic;866 if (gbEmulatorType == 2 || gbEmulatorType == 5) // only games played in SGB mode will have a border867 {868 gbBorderOn = true;869 theApp.winGbBorderOn = true;870 gbBorderAutomatic = false;871 }872 else873 {874 gbBorderOn = false;875 theApp.winGbBorderOn = false;876 gbBorderAutomatic = false;877 }878 systemGbBorderOn();879 #else880 /// SDLFIXME881 #endif882 }884 uint16 VBAMovieGetCurrentInputOf(int controllerNum, bool normalOnly)885 {886 if (controllerNum < 0 || controllerNum >= MOVIE_NUM_OF_POSSIBLE_CONTROLLERS)887 return 0;889 return normalOnly ? (currentButtons[controllerNum] & BUTTON_REGULAR_MASK) : currentButtons[controllerNum];890 }892 int VBAMovieCreate(const char *filename, const char *authorInfo, uint8 startFlags, uint8 controllerFlags, uint8 typeFlags)893 {894 // make sure at least one controller is enabled895 if ((controllerFlags & MOVIE_CONTROLLERS_ANY_MASK) == 0)896 return MOVIE_WRONG_FORMAT;898 if (!emulating)899 return MOVIE_UNKNOWN_ERROR;901 loadingMovie = true;903 FILE * file;904 STREAM stream;905 int fn;907 char movie_filename [_MAX_PATH];908 #ifdef WIN32909 _fullpath(movie_filename, filename, _MAX_PATH);910 #else911 // FIXME: convert to fullpath912 strncpy(movie_filename, filename, _MAX_PATH);913 movie_filename[_MAX_PATH - 1] = '\0';914 #endif916 bool alreadyOpen = (Movie.file != NULL && stricmp(movie_filename, Movie.filename) == 0);918 if (alreadyOpen)919 change_state(MOVIE_STATE_NONE); // have to stop current movie before trying to re-open it921 if (movie_filename[0] == '\0')922 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }924 if (!(file = fopen(movie_filename, "wb")))925 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }927 if (!alreadyOpen)928 change_state(MOVIE_STATE_NONE); // stop current movie when we're able to open the other one930 // clear out the current movie931 //printf("RLM: movie init\n");933 VBAMovieInit();935 // fill in the movie's header936 Movie.header.uid = (uint32)time(NULL);937 Movie.header.magic = VBM_MAGIC;938 Movie.header.version = VBM_VERSION;939 Movie.header.rerecord_count = 0;940 Movie.header.length_frames = 0;941 Movie.header.startFlags = startFlags;942 Movie.header.controllerFlags = controllerFlags;943 Movie.header.typeFlags = typeFlags;944 Movie.header.minorVersion = VBM_REVISION;946 // set emulator settings that make the movie more likely to stay synchronized when it's later played back947 SetRecordEmuSettings();949 // set ROM and BIOS checksums and stuff950 VBAMovieGetRomInfo(Movie, Movie.header.romTitle, Movie.header.romGameCode, Movie.header.romOrBiosChecksum, Movie.header.romCRC);952 //printf("RLM: Writing movie header\n");953 // write the header to file954 write_movie_header(file, Movie);956 //printf("RLM: setting metadata\n");958 // copy over the metadata / author info959 VBAMovieSetMetadata("________________Robert McIntyre______________________________________________________________________________________________________________________________________________________________________________________________________________________");961 //printf("RLM: writing metadata\n");963 // write the metadata / author info to file966 fwrite(Movie.authorInfo, 1, sizeof(char) * MOVIE_METADATA_SIZE, file);968 // write snapshot or SRAM if applicable969 if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT970 || Movie.header.startFlags & MOVIE_START_FROM_SRAM)971 {972 Movie.header.offset_to_savestate = (uint32)ftell(file);974 // close the file and reopen it as a stream:976 fn = dup(fileno(file));977 fclose(file);979 if (!(stream = utilGzReopen(fn, "ab"))) // append mode to start at end, no seek necessary980 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }982 // write the save data:983 if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT)984 {985 // save snapshot986 if (!theEmulator.emuWriteStateToStream(stream))987 {988 utilGzClose(stream);989 { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; }990 }991 }992 else if (Movie.header.startFlags & MOVIE_START_FROM_SRAM)993 {994 // save SRAM995 if (!theEmulator.emuWriteBatteryToStream(stream))996 {997 utilGzClose(stream);998 { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; }999 }1001 // 'soft' reset:1002 theEmulator.emuReset(false);1003 }1005 utilGzClose(stream);1007 // reopen the file and seek back to the end1009 if (!(file = fopen(movie_filename, "rb+")))1010 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }1012 fseek(file, 0, SEEK_END);1013 }1014 else // no snapshot or SRAM1015 {1016 HardResetAndSRAMClear();1017 }1019 Movie.header.offset_to_controller_data = (uint32)ftell(file);1021 strcpy(Movie.filename, movie_filename);1022 Movie.file = file;1023 Movie.bytesPerFrame = bytes_per_frame(Movie);1024 Movie.inputBufferPtr = Movie.inputBuffer;1025 Movie.currentFrame = 0;1026 Movie.readOnly = false;1027 Movie.RecordedThisSession = true;1029 change_state(MOVIE_STATE_RECORD);1031 systemScreenMessage("Recording movie...");1032 { loadingMovie = false; return MOVIE_SUCCESS; }1033 }1035 void VBAUpdateButtonPressDisplay()1036 {1037 uint32 keys = currentButtons[0] & BUTTON_REGULAR_RECORDING_MASK;1039 const static char KeyMap[] = { 'A', 'B', 's', 'S', '>', '<', '^', 'v', 'R', 'L', '!', '?', '{', '}', 'v', '^' };1040 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 { = } _1041 // ? !1042 char buffer[256];1043 sprintf(buffer, " ");1045 #ifndef WIN321046 // don't bother color-coding autofire and such1047 int i;1048 for (i = 0; i < 15; i++)1049 {1050 int j = KeyOrder[i];1051 int mask = (1 << (j));1052 buffer[strlen(" ") + i] = ((keys & mask) != 0) ? KeyMap[j] : ' ';1053 }1055 systemScreenMessage(buffer, 2, -1);1056 #else1057 const bool eraseAll = !theApp.inputDisplay;1058 uint32 autoHeldKeys = eraseAll ? 0 : theApp.autoHold & BUTTON_REGULAR_RECORDING_MASK;1059 uint32 autoFireKeys = eraseAll ? 0 : (theApp.autoFire | theApp.autoFire2) & BUTTON_REGULAR_RECORDING_MASK;1060 uint32 pressedKeys = eraseAll ? 0 : keys;1062 char colorList[64];1063 memset(colorList, 1, strlen(buffer));1065 if (!eraseAll)1066 {1067 for (int i = 0; i < 15; i++)1068 {1069 const int j = KeyOrder[i];1070 const int mask = (1 << (j));1071 bool pressed = (pressedKeys & mask) != 0;1072 const bool autoHeld = (autoHeldKeys & mask) != 0;1073 const bool autoFired = (autoFireKeys & mask) != 0;1074 const bool erased = (lastKeys & mask) != 0 && (!pressed && !autoHeld && !autoFired);1075 extern int textMethod;1076 if (textMethod != 2 && (autoHeld || (autoFired && !pressed) || erased))1077 {1078 int colorNum = 1; // default is white1079 if (autoHeld)1080 colorNum += (pressed ? 2 : 1); // yellow if pressed, red if not1081 else if (autoFired)1082 colorNum += 5; // blue if autofired and not currently pressed1083 else if (erased)1084 colorNum += 8; // black on black1086 colorList[strlen(" ") + i] = colorNum;1087 pressed = true;1088 }1089 buffer[strlen(" ") + i] = pressed ? KeyMap[j] : ' ';1090 }1091 }1093 lastKeys = currentButtons[0];1094 lastKeys |= theApp.autoHold & BUTTON_REGULAR_RECORDING_MASK;1095 lastKeys |= (theApp.autoFire | theApp.autoFire2) & BUTTON_REGULAR_RECORDING_MASK;1097 systemScreenMessage(buffer, 2, -1, colorList);1098 #endif1099 }1101 void VBAUpdateFrameCountDisplay()1102 {1103 const int MAGICAL_NUMBER = 64; // FIXME: this won't do any better, but only to remind you of sz issues1104 char frameDisplayString[MAGICAL_NUMBER];1105 char lagFrameDisplayString[MAGICAL_NUMBER];1106 char extraCountDisplayString[MAGICAL_NUMBER];1108 #if (defined(WIN32) && !defined(SDL))1109 if (theApp.frameCounter)1110 #else1111 /// SDL FIXME1112 #endif1113 {1114 switch (Movie.state)1115 {1116 case MOVIE_STATE_PLAY:1117 case MOVIE_STATE_END:1118 {1119 sprintf(frameDisplayString, "%d / %d", Movie.currentFrame, Movie.header.length_frames);1120 if (!Movie.readOnly)1121 strcat(frameDisplayString, " (edit)");1122 break;1123 }1124 case MOVIE_STATE_RECORD:1125 {1126 sprintf(frameDisplayString, "%d (record)", Movie.currentFrame);1127 break;1128 }1129 default:1130 {1131 sprintf(frameDisplayString, "%d (no movie)", systemCounters.frameCount);1132 break;1133 }1134 }1136 #if (defined(WIN32) && !defined(SDL))1137 if (theApp.lagCounter)1138 #else1139 /// SDL FIXME1140 #endif1141 {1142 // sprintf(lagFrameDisplayString, " %c %d", systemCounters.laggedLast ? '*' : '|', systemCounters.lagCount);1143 sprintf(lagFrameDisplayString, " | %d%s", systemCounters.lagCount, systemCounters.laggedLast ? " *" : "");1144 strcat(frameDisplayString, lagFrameDisplayString);1145 }1147 #if (defined(WIN32) && !defined(SDL))1148 if (theApp.extraCounter)1149 #else1150 /// SDL FIXME1151 #endif1152 {1153 sprintf(extraCountDisplayString, " | %d", systemCounters.frameCount - systemCounters.extraCount);1154 strcat(frameDisplayString, extraCountDisplayString);1155 }1156 }1157 #if (defined(WIN32) && !defined(SDL))1158 else1159 {1160 frameDisplayString[0] = '\0';1161 }1162 #else1163 /// SDL FIXME1164 #endif1165 systemScreenMessage(frameDisplayString, 1, -1);1166 }1168 // this function should only be called once every frame1169 void VBAMovieUpdateState()1170 {1171 ++Movie.currentFrame;1172 //printf("RLM: inside updateState\n");1173 if (Movie.state == MOVIE_STATE_PLAY)1174 {1175 Movie.inputBufferPtr += Movie.bytesPerFrame;1176 if (Movie.currentFrame >= Movie.header.length_frames)1177 {1178 // the movie ends anyway; what to do next depends on the settings1179 change_state(MOVIE_STATE_END);1180 }1181 }1182 else if (Movie.state == MOVIE_STATE_RECORD)1183 {1184 //printf("RLM: Movie_STATE_RECORD\n");1185 VBAMovieWrite(0,true);1186 // use first fseek?1187 //TODO: THis is the problem.1188 if (Movie.inputBuffer){1189 fwrite(Movie.inputBufferPtr, 1, Movie.bytesPerFrame, Movie.file);1190 }1191 //printf("RLM: write successful.\n");1192 Movie.header.length_frames = Movie.currentFrame;1193 Movie.inputBufferPtr += Movie.bytesPerFrame;1194 Movie.RecordedThisSession = true;1195 flush_movie_header();1196 }1197 else if (Movie.state == MOVIE_STATE_END)1198 {1199 change_state(MOVIE_STATE_END);1200 }1201 }1203 void VBAMovieRead(int i, bool /*sensor*/)1204 {1205 if (Movie.state != MOVIE_STATE_PLAY)1206 return;1208 if (i < 0 || i >= MOVIE_NUM_OF_POSSIBLE_CONTROLLERS)1209 return; // not a controller we're recognizing1211 if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i))1212 {1213 currentButtons[i] = Read16(Movie.inputBufferPtr + CONTROLLER_DATA_SIZE * i);1214 }1215 else1216 {1217 currentButtons[i] = 0; // pretend the controller is disconnected1218 }1220 printf("RLM: button %d\n",currentButtons[i]);1221 if ((currentButtons[i] & BUTTON_MASK_NEW_RESET) != 0){1222 printf("RLM: reset signaled\n");1223 resetSignaled = true;1224 }1225 }1227 void VBAMovieWrite(int i, bool /*sensor*/)1228 {1229 if (Movie.state != MOVIE_STATE_RECORD)1230 return;1232 if (i < 0 || i >= MOVIE_NUM_OF_POSSIBLE_CONTROLLERS)1233 return; // not a controller we're recognizing1235 reserve_buffer_space((uint32)((Movie.inputBufferPtr - Movie.inputBuffer) + Movie.bytesPerFrame));1237 if (Movie.header.controllerFlags)1238 {1239 // get the current controller data1240 uint16 buttonData = currentButtons[i];1241 //printf("RLM: currentButtons %i\n", currentButtons[0]);1243 // mask away the irrelevent bits1244 buttonData &= BUTTON_REGULAR_MASK | BUTTON_MOTION_MASK;1246 // soft-reset "button" for 1 frame if the game is reset while recording1247 if (resetSignaled)1248 {1249 buttonData |= BUTTON_MASK_NEW_RESET;1250 }1252 // backward compatibility kludge1253 if (resetSignaledLast)1254 {1255 buttonData |= BUTTON_MASK_OLD_RESET;1256 }1258 Write16(buttonData, Movie.inputBufferPtr + CONTROLLER_DATA_SIZE * i);1260 // and for display1261 currentButtons[i] = buttonData;1262 }1263 else1264 {1265 // pretend the controller is disconnected (otherwise input it gives could cause desync since we're not writing it to the1266 // movie)1267 currentButtons[i] = 0;1268 }1269 }1271 void VBAMovieStop(bool8 suppress_message)1272 {1273 if (Movie.state != MOVIE_STATE_NONE)1274 {1275 change_state(MOVIE_STATE_NONE);1276 if (!suppress_message)1277 systemScreenMessage("Movie stop");1278 }1279 }1281 int VBAMovieGetInfo(const char *filename, SMovie *info)1282 {1283 assert(info != NULL);1284 if (info == NULL)1285 return -1;1287 FILE * file;1288 int result;1289 SMovie &local_movie = *info;1291 memset(info, 0, sizeof(*info));1292 if (filename[0] == '\0')1293 return MOVIE_FILE_NOT_FOUND;1294 if (!(file = fopen(filename, "rb")))1295 return MOVIE_FILE_NOT_FOUND;1297 // read header1298 if ((result = (read_movie_header(file, local_movie))) != MOVIE_SUCCESS)1299 {1300 fclose(file);1301 return result;1302 }1304 // read the metadata / author info from file1305 fread(local_movie.authorInfo, 1, sizeof(char) * MOVIE_METADATA_SIZE, file);1307 strncpy(local_movie.filename, filename, _MAX_PATH);1308 local_movie.filename[_MAX_PATH - 1] = '\0';1310 if (Movie.file != NULL && stricmp(local_movie.filename, Movie.filename) == 0) // alreadyOpen1311 {1312 local_movie.bytesPerFrame = Movie.bytesPerFrame;1313 local_movie.header.length_frames = Movie.header.length_frames;1314 }1315 else1316 {1317 // recalculate length of movie from the file size1318 local_movie.bytesPerFrame = bytes_per_frame(local_movie);1319 fseek(file, 0, SEEK_END);1320 int fileSize = ftell(file);1321 local_movie.header.length_frames =1322 (fileSize - local_movie.header.offset_to_controller_data) / local_movie.bytesPerFrame;1323 }1325 fclose(file);1327 if (access(filename, W_OK))1328 info->readOnly = true;1330 return MOVIE_SUCCESS;1331 }1333 bool8 VBAMovieActive()1334 {1335 return (Movie.state != MOVIE_STATE_NONE);1336 }1338 bool8 VBAMovieLoading()1339 {1340 return loadingMovie;1341 }1343 bool8 VBAMoviePlaying()1344 {1345 return (Movie.state == MOVIE_STATE_PLAY);1346 }1348 bool8 VBAMovieRecording()1349 {1350 return (Movie.state == MOVIE_STATE_RECORD);1351 }1353 bool8 VBAMovieReadOnly()1354 {1355 if (!VBAMovieActive())1356 return false;1358 return Movie.readOnly;1359 }1361 void VBAMovieToggleReadOnly()1362 {1363 if (!VBAMovieActive())1364 return;1366 if (Movie.readOnly != 2)1367 {1368 Movie.readOnly = !Movie.readOnly;1370 systemScreenMessage(Movie.readOnly ? "Movie now read-only" : "Movie now editable");1371 }1372 else1373 {1374 systemScreenMessage("Can't toggle read-only movie");1375 }1376 }1378 uint32 VBAMovieGetVersion()1379 {1380 if (!VBAMovieActive())1381 return 0;1383 return Movie.header.version;1384 }1386 uint32 VBAMovieGetMinorVersion()1387 {1388 if (!VBAMovieActive())1389 return 0;1391 return Movie.header.minorVersion;1392 }1394 uint32 VBAMovieGetId()1395 {1396 if (!VBAMovieActive())1397 return 0;1399 return Movie.header.uid;1400 }1402 uint32 VBAMovieGetLength()1403 {1404 if (!VBAMovieActive())1405 return 0;1407 return Movie.header.length_frames;1408 }1410 uint32 VBAMovieGetFrameCounter()1411 {1412 if (!VBAMovieActive())1413 return 0;1415 return Movie.currentFrame;1416 }1418 uint32 VBAMovieGetRerecordCount()1419 {1420 if (!VBAMovieActive())1421 return 0;1423 return Movie.header.rerecord_count;1424 }1426 uint32 VBAMovieSetRerecordCount(uint32 newRerecordCount)1427 {1428 uint32 oldRerecordCount = 0;1429 if (!VBAMovieActive())1430 return 0;1432 oldRerecordCount = Movie.header.rerecord_count;1433 Movie.header.rerecord_count = newRerecordCount;1434 return oldRerecordCount;1435 }1437 std::string VBAMovieGetAuthorInfo()1438 {1439 if (!VBAMovieActive())1440 return "";1442 return Movie.authorInfo;1443 }1445 std::string VBAMovieGetFilename()1446 {1447 if (!VBAMovieActive())1448 return "";1450 return Movie.filename;1451 }1453 void VBAMovieFreeze(uint8 * *buf, uint32 *size)1454 {1455 // sanity check1456 if (!VBAMovieActive())1457 {1458 return;1459 }1461 *buf = NULL;1462 *size = 0;1464 // compute size needed for the buffer1465 // room for header.uid, currentFrame, and header.length_frames1466 uint32 size_needed = sizeof(Movie.header.uid) + sizeof(Movie.currentFrame) + sizeof(Movie.header.length_frames);1467 size_needed += (uint32)(Movie.bytesPerFrame * Movie.header.length_frames);1468 *buf = new uint8[size_needed];1469 *size = size_needed;1471 uint8 *ptr = *buf;1472 if (!ptr)1473 {1474 return;1475 }1477 Push32(Movie.header.uid, ptr);1478 Push32(Movie.currentFrame, ptr);1479 Push32(Movie.header.length_frames - 1, ptr); // HACK: shorten the length by 1 for backward compatibility1481 memcpy(ptr, Movie.inputBuffer, Movie.bytesPerFrame * Movie.header.length_frames);1482 }1484 int VBAMovieUnfreeze(const uint8 *buf, uint32 size)1485 {1486 // sanity check1487 if (!VBAMovieActive())1488 {1489 return MOVIE_NOT_FROM_A_MOVIE;1490 }1492 const uint8 *ptr = buf;1493 if (size < sizeof(Movie.header.uid) + sizeof(Movie.currentFrame) + sizeof(Movie.header.length_frames))1494 {1495 return MOVIE_WRONG_FORMAT;1496 }1498 uint32 movie_id = Pop32(ptr);1499 uint32 current_frame = Pop32(ptr);1500 uint32 end_frame = Pop32(ptr) + 1; // HACK: restore the length for backward compatibility1501 uint32 space_needed = Movie.bytesPerFrame * end_frame;1503 if (movie_id != Movie.header.uid)1504 return MOVIE_NOT_FROM_THIS_MOVIE;1506 if (space_needed > size)1507 return MOVIE_WRONG_FORMAT;1509 if (Movie.readOnly)1510 {1511 // here, we are going to keep the input data from the movie file1512 // and simply rewind to the currentFrame pointer1513 // this will cause a desync if the savestate is not in sync // <-- NOT ANYMORE1514 // with the on-disk recording data, but it's easily solved1515 // by loading another savestate or playing the movie from the beginning1517 // don't allow loading a state inconsistent with the current movie1518 uint32 length_history = min(current_frame, Movie.header.length_frames);1519 if (end_frame < length_history)1520 return MOVIE_SNAPSHOT_INCONSISTENT;1522 uint32 space_shared = Movie.bytesPerFrame * length_history;1523 if (memcmp(Movie.inputBuffer, ptr, space_shared))1524 return MOVIE_SNAPSHOT_INCONSISTENT;1526 Movie.currentFrame = current_frame;1527 Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * min(current_frame, Movie.header.length_frames);1528 }1529 else1530 {1531 // here, we are going to take the input data from the savestate1532 // and make it the input data for the current movie, then continue1533 // writing new input data at the currentFrame pointer1534 Movie.currentFrame = current_frame;1535 Movie.header.length_frames = end_frame;1536 if (!VBALuaRerecordCountSkip())1537 ++Movie.header.rerecord_count;1539 Movie.RecordedThisSession = true;1541 // do this before calling reserve_buffer_space()1542 Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * min(current_frame, Movie.header.length_frames);1543 reserve_buffer_space(space_needed);1544 memcpy(Movie.inputBuffer, ptr, space_needed);1546 // for consistency, no auto movie conversion here since we don't auto convert the corresponding savestate1547 flush_movie_header();1548 flush_movie_frames();1549 }1551 change_state(MOVIE_STATE_PLAY); // check for movie end1553 // necessary!1554 resetSignaled = false;1555 resetSignaledLast = false;1557 // necessary to check if there's a reset signal at the previous frame1558 if (current_frame > 0)1559 {1560 const u8 NEW_RESET = u8(BUTTON_MASK_NEW_RESET >> 8);1561 for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i)1562 {1563 if ((Movie.header.controllerFlags & MOVIE_CONTROLLER(i)) && (*(Movie.inputBufferPtr+1- Movie.bytesPerFrame) & NEW_RESET))1564 {1565 resetSignaledLast = true;1566 break;1567 }1568 }1569 }1571 return MOVIE_SUCCESS;1572 }1574 bool VBAMovieEnded()1575 {1576 return (Movie.state == MOVIE_STATE_END);1577 // return (Movie.state != MOVIE_STATE_NONE && Movie.currentFrame >= Movie.header.length_frames);1578 }1580 bool VBAMovieAllowsRerecording()1581 {1582 bool allows = (Movie.state != MOVIE_STATE_NONE) && (Movie.currentFrame <= Movie.header.length_frames);1583 return /*!VBAMovieReadOnly() &&*/ allows;1584 }1586 bool VBAMovieSwitchToPlaying()1587 {1588 if (!VBAMovieActive())1589 return false;1591 if (!Movie.readOnly)1592 {1593 VBAMovieToggleReadOnly();1594 }1596 change_state(MOVIE_STATE_PLAY);1597 if (Movie.state == MOVIE_STATE_PLAY)1598 systemScreenMessage("Movie replay (continue)");1599 else1600 systemScreenMessage("Movie end");1602 return true;1603 }1605 bool VBAMovieSwitchToRecording()1606 {1607 if (!VBAMovieAllowsRerecording())1608 return false;1610 if (Movie.readOnly)1611 {1612 VBAMovieToggleReadOnly();1613 }1615 if (!VBALuaRerecordCountSkip())1616 ++Movie.header.rerecord_count;1618 change_state(MOVIE_STATE_RECORD);1619 systemScreenMessage("Movie re-record");1621 //truncate_movie(Movie.currentFrame);1623 return true;1624 }1626 uint32 VBAMovieGetState()1627 {1628 // ?1629 if (!VBAMovieActive())1630 return MOVIE_STATE_NONE;1632 return Movie.state;1633 }1635 void VBAMovieSignalReset()1636 {1637 if (VBAMovieActive())1638 resetSignaled = true;1639 }1641 void VBAMovieResetIfRequested()1642 {1643 if (resetSignaled)1644 {1645 theEmulator.emuReset(false);1646 resetSignaled = false;1647 resetSignaledLast = true;1648 }1649 else1650 {1651 resetSignaledLast = false;1652 }1653 }1655 void VBAMovieSetMetadata(const char *info)1656 {1657 if (!memcmp(Movie.authorInfo, info, MOVIE_METADATA_SIZE))1658 return;1660 memcpy(Movie.authorInfo, info, MOVIE_METADATA_SIZE); // strncpy would omit post-0 bytes1661 Movie.authorInfo[MOVIE_METADATA_SIZE - 1] = '\0';1663 if (Movie.file)1664 {1665 // (over-)write the header1666 fseek(Movie.file, 0, SEEK_SET);1668 write_movie_header(Movie.file, Movie);1670 // write the metadata / author info to file1671 fwrite(Movie.authorInfo, 1, sizeof(char) * MOVIE_METADATA_SIZE, Movie.file);1673 fflush(Movie.file);1674 }1675 //printf("RLM: setMetadata called\n");1677 }1679 void VBAMovieRestart()1680 {1681 if (VBAMovieActive())1682 {1683 systemSoundClearBuffer();1685 bool8 modified = Movie.RecordedThisSession;1687 VBAMovieStop(true);1689 char movieName [_MAX_PATH];1690 strncpy(movieName, Movie.filename, _MAX_PATH);1691 movieName[_MAX_PATH - 1] = '\0';1692 VBAMovieOpen(movieName, Movie.readOnly); // can't just pass in Movie.filename, since VBAMovieOpen clears out Movie's1693 // variables1695 Movie.RecordedThisSession = modified;1697 systemScreenMessage("Movie replay (restart)");1698 }1699 }1701 int VBAMovieGetPauseAt()1702 {1703 return Movie.pauseFrame;1704 }1706 void VBAMovieSetPauseAt(int at)1707 {1708 Movie.pauseFrame = at;1709 }1711 ///////////////////////1712 // movie tools1714 // FIXME: is it safe to convert/flush a movie while recording it (considering fseek() problem)?1715 int VBAMovieConvertCurrent()1716 {1717 if (!VBAMovieActive())1718 {1719 return MOVIE_NOTHING;1720 }1722 if (Movie.header.minorVersion > VBM_REVISION)1723 {1724 return MOVIE_WRONG_VERSION;1725 }1727 if (Movie.header.minorVersion == VBM_REVISION)1728 {1729 return MOVIE_NOTHING;1730 }1732 Movie.header.minorVersion = VBM_REVISION;1734 if (Movie.header.length_frames == 0) // this could happen1735 {1736 truncate_movie(0);1737 return MOVIE_SUCCESS;1738 }1740 // fix movies recorded from snapshots1741 if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT)1742 {1743 uint8 *firstFramePtr = Movie.inputBuffer;1744 for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i)1745 {1746 if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i))1747 {1748 Push16(initialInputs[i], firstFramePtr);1749 // note: this is correct since Push16 advances the dest pointer by sizeof u161750 }1751 }1752 }1754 // convert old resets to new ones1755 const u8 OLD_RESET = u8(BUTTON_MASK_OLD_RESET >> 8);1756 const u8 NEW_RESET = u8(BUTTON_MASK_NEW_RESET >> 8);1757 for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i)1758 {1759 if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i))1760 {1761 uint8 *startPtr = Movie.inputBuffer + sizeof(u16) * i + 1;1762 uint8 *endPtr = Movie.inputBuffer + Movie.bytesPerFrame * (Movie.header.length_frames - 1);1763 for (; startPtr < endPtr; startPtr += Movie.bytesPerFrame)1764 {1765 if (startPtr[Movie.bytesPerFrame] & OLD_RESET)1766 {1767 startPtr[0] |= NEW_RESET;1768 }1769 }1770 }1771 }1773 flush_movie_header();1774 flush_movie_frames();1775 return MOVIE_SUCCESS;1776 }1778 bool VBAMovieTuncateAtCurrentFrame()1779 {1780 if (!VBAMovieActive())1781 return false;1783 truncate_movie(Movie.currentFrame);1784 change_state(MOVIE_STATE_END);1785 systemScreenMessage("Movie truncated");1787 return true;1788 }1790 bool VBAMovieFixHeader()1791 {1792 if (!VBAMovieActive())1793 return false;1795 flush_movie_header();1796 systemScreenMessage("Movie header fixed");1797 return true;1798 }