annotate 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
rev   line source
rlm@1 1 #include <cstdio>
rlm@1 2 #include <cctype>
rlm@1 3 #include <cstdlib>
rlm@1 4 #include <cstring>
rlm@1 5 #include <cassert>
rlm@1 6 #include <algorithm>
rlm@1 7
rlm@1 8 using namespace std;
rlm@1 9
rlm@1 10 #ifdef HAVE_STRINGS_H
rlm@1 11 # include <strings.h>
rlm@1 12 #endif
rlm@1 13
rlm@1 14 #if defined(__unix) || defined(__linux) || defined(__sun) || defined(__DJGPP)
rlm@1 15 # include <unistd.h>
rlm@1 16 # include <sys/types.h>
rlm@1 17 # include <sys/stat.h>
rlm@1 18 # include <climits>
rlm@1 19 # define stricmp strcasecmp
rlm@1 20 // FIXME: this is wrong, but we don't want buffer overflow
rlm@1 21 # if defined _MAX_PATH
rlm@1 22 # undef _MAX_PATH
rlm@1 23 //# define _MAX_PATH 128
rlm@1 24 # define _MAX_PATH 260
rlm@1 25 # endif
rlm@1 26 #endif
rlm@1 27
rlm@1 28 #ifdef WIN32
rlm@1 29 # include <io.h>
rlm@1 30 # ifndef W_OK
rlm@1 31 # define W_OK 2
rlm@1 32 # endif
rlm@1 33 # define ftruncate chsize
rlm@1 34 #endif
rlm@1 35
rlm@1 36 #include "movie.h"
rlm@1 37 #include "System.h"
rlm@1 38 #include "../gba/GBA.h"
rlm@1 39 #include "../gba/GBAGlobals.h"
rlm@1 40 #include "../gba/RTC.h"
rlm@1 41 #include "../gb/GB.h"
rlm@1 42 #include "../gb/gbGlobals.h"
rlm@1 43 #include "inputGlobal.h"
rlm@1 44 #include "unzip.h"
rlm@1 45 #include "Util.h"
rlm@1 46
rlm@1 47 #include "vbalua.h"
rlm@1 48
rlm@1 49 #if (defined(WIN32) && !defined(SDL))
rlm@1 50 # include "../win32/stdafx.h"
rlm@1 51 # include "../win32/MainWnd.h"
rlm@1 52 # include "../win32/VBA.h"
rlm@1 53 # include "../win32/WinMiscUtil.h"
rlm@1 54 #endif
rlm@1 55
rlm@1 56 extern int emulating; // from system.cpp
rlm@1 57 extern u16 currentButtons[4]; // from System.cpp
rlm@1 58 extern u16 lastKeys;
rlm@1 59
rlm@1 60 SMovie Movie;
rlm@1 61 bool loadingMovie = false;
rlm@1 62
rlm@1 63 // probably bad idea to have so many global variables, but I hate to recompile almost everything after editing VBA.h
rlm@1 64 bool autoConvertMovieWhenPlaying = false;
rlm@1 65
rlm@1 66 static u16 initialInputs[4] = { 0 };
rlm@1 67
rlm@1 68 static bool resetSignaled = false;
rlm@1 69 static bool resetSignaledLast = false;
rlm@1 70
rlm@1 71 static int prevEmulatorType, prevBorder, prevWinBorder, prevBorderAuto;
rlm@1 72
rlm@1 73 // little-endian integer pop/push functions:
rlm@1 74 static inline uint32 Pop32(const uint8 * &ptr)
rlm@1 75 {
rlm@33 76 uint32 v = (ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24));
rlm@33 77 ptr += 4;
rlm@33 78 return v;
rlm@1 79 }
rlm@1 80
rlm@1 81 static inline uint16 Pop16(const uint8 * &ptr) /* const version */
rlm@1 82 {
rlm@33 83 uint16 v = (ptr[0] | (ptr[1] << 8));
rlm@33 84 ptr += 2;
rlm@33 85 return v;
rlm@1 86 }
rlm@1 87
rlm@1 88 static inline uint16 Pop16(uint8 * &ptr) /* non-const version */
rlm@1 89 {
rlm@33 90 uint16 v = (ptr[0] | (ptr[1] << 8));
rlm@33 91 ptr += 2;
rlm@33 92 return v;
rlm@1 93 }
rlm@1 94
rlm@1 95 static inline uint8 Pop8(const uint8 * &ptr)
rlm@1 96 {
rlm@33 97 return *(ptr)++;
rlm@1 98 }
rlm@1 99
rlm@1 100 static inline void Push32(uint32 v, uint8 * &ptr)
rlm@1 101 {
rlm@33 102 ptr[0] = (uint8)(v & 0xff);
rlm@33 103 ptr[1] = (uint8)((v >> 8) & 0xff);
rlm@33 104 ptr[2] = (uint8)((v >> 16) & 0xff);
rlm@33 105 ptr[3] = (uint8)((v >> 24) & 0xff);
rlm@33 106 ptr += 4;
rlm@1 107 }
rlm@1 108
rlm@1 109 static inline void Push16(uint16 v, uint8 * &ptr)
rlm@1 110 {
rlm@33 111 ptr[0] = (uint8)(v & 0xff);
rlm@33 112 ptr[1] = (uint8)((v >> 8) & 0xff);
rlm@33 113 ptr += 2;
rlm@1 114 }
rlm@1 115
rlm@1 116 static inline void Push8(uint8 v, uint8 * &ptr)
rlm@1 117 {
rlm@33 118 *ptr++ = v;
rlm@1 119 }
rlm@1 120
rlm@1 121 // little-endian integer read/write functions:
rlm@1 122 static inline uint16 Read16(const uint8 *ptr)
rlm@1 123 {
rlm@33 124 return ptr[0] | (ptr[1] << 8);
rlm@1 125 }
rlm@1 126
rlm@1 127 static inline void Write16(uint16 v, uint8 *ptr)
rlm@1 128 {
rlm@33 129 ptr[0] = uint8(v & 0xff);
rlm@33 130 ptr[1] = uint8((v >> 8) & 0xff);
rlm@1 131 }
rlm@1 132
rlm@1 133 static long file_length(FILE *fp)
rlm@1 134 {
rlm@33 135 long cur_pos = ftell(fp);
rlm@33 136 fseek(fp, 0, SEEK_END);
rlm@33 137 long length = ftell(fp);
rlm@33 138 fseek(fp, cur_pos, SEEK_SET);
rlm@33 139 return length;
rlm@1 140 }
rlm@1 141
rlm@1 142 static int bytes_per_frame(SMovie &mov)
rlm@1 143 {
rlm@33 144 int num_controllers = 0;
rlm@1 145
rlm@33 146 for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i)
rlm@33 147 if (mov.header.controllerFlags & MOVIE_CONTROLLER(i))
rlm@33 148 ++num_controllers;
rlm@1 149
rlm@33 150 return CONTROLLER_DATA_SIZE * num_controllers;
rlm@1 151 }
rlm@1 152
rlm@1 153 static void reserve_buffer_space(uint32 space_needed)
rlm@1 154 {
rlm@33 155 if (space_needed > Movie.inputBufferSize)
rlm@33 156 {
rlm@33 157 uint32 ptr_offset = Movie.inputBufferPtr - Movie.inputBuffer;
rlm@33 158 uint32 alloc_chunks = (space_needed - 1) / BUFFER_GROWTH_SIZE + 1;
rlm@33 159 uint32 old_size = Movie.inputBufferSize;
rlm@33 160 Movie.inputBufferSize = BUFFER_GROWTH_SIZE * alloc_chunks;
rlm@33 161 Movie.inputBuffer = (uint8 *)realloc(Movie.inputBuffer, Movie.inputBufferSize);
rlm@33 162 // FIXME: this only fixes the random input problem during dma-frame-skip, but not the skip
rlm@33 163 memset(Movie.inputBuffer + old_size, 0, Movie.inputBufferSize - old_size);
rlm@33 164 Movie.inputBufferPtr = Movie.inputBuffer + ptr_offset;
rlm@33 165 }
rlm@1 166 }
rlm@1 167
rlm@1 168 static int read_movie_header(FILE *file, SMovie &movie)
rlm@1 169 {
rlm@33 170 assert(file != NULL);
rlm@33 171 assert(VBM_HEADER_SIZE == sizeof(SMovieFileHeader)); // sanity check on the header type definition
rlm@1 172
rlm@33 173 uint8 headerData [VBM_HEADER_SIZE];
rlm@1 174
rlm@33 175 if (fread(headerData, 1, VBM_HEADER_SIZE, file) != VBM_HEADER_SIZE)
rlm@33 176 return MOVIE_WRONG_FORMAT; // if we failed to read in all VBM_HEADER_SIZE bytes of the header
rlm@1 177
rlm@33 178 const uint8 * ptr = headerData;
rlm@33 179 SMovieFileHeader &header = movie.header;
rlm@1 180
rlm@33 181 header.magic = Pop32(ptr);
rlm@33 182 if (header.magic != VBM_MAGIC)
rlm@33 183 return MOVIE_WRONG_FORMAT;
rlm@1 184
rlm@33 185 header.version = Pop32(ptr);
rlm@33 186 if (header.version != VBM_VERSION)
rlm@33 187 return MOVIE_WRONG_VERSION;
rlm@1 188
rlm@33 189 header.uid = Pop32(ptr);
rlm@33 190 header.length_frames = Pop32(ptr) + 1; // HACK: add 1 to the length for compatibility
rlm@33 191 header.rerecord_count = Pop32(ptr);
rlm@1 192
rlm@33 193 header.startFlags = Pop8(ptr);
rlm@33 194 header.controllerFlags = Pop8(ptr);
rlm@33 195 header.typeFlags = Pop8(ptr);
rlm@33 196 header.optionFlags = Pop8(ptr);
rlm@1 197
rlm@33 198 header.saveType = Pop32(ptr);
rlm@33 199 header.flashSize = Pop32(ptr);
rlm@33 200 header.gbEmulatorType = Pop32(ptr);
rlm@1 201
rlm@33 202 for (int i = 0; i < 12; i++)
rlm@33 203 header.romTitle[i] = Pop8(ptr);
rlm@1 204
rlm@33 205 header.minorVersion = Pop8(ptr);
rlm@1 206
rlm@33 207 header.romCRC = Pop8(ptr);
rlm@33 208 header.romOrBiosChecksum = Pop16(ptr);
rlm@33 209 header.romGameCode = Pop32(ptr);
rlm@1 210
rlm@33 211 header.offset_to_savestate = Pop32(ptr);
rlm@33 212 header.offset_to_controller_data = Pop32(ptr);
rlm@1 213
rlm@33 214 return MOVIE_SUCCESS;
rlm@1 215 }
rlm@1 216
rlm@1 217 static void write_movie_header(FILE *file, const SMovie &movie)
rlm@1 218 {
rlm@33 219 assert(ftell(file) == 0); // we assume file points to beginning of movie file
rlm@1 220
rlm@33 221 uint8 headerData [VBM_HEADER_SIZE];
rlm@33 222 uint8 *ptr = headerData;
rlm@33 223 const SMovieFileHeader &header = movie.header;
rlm@1 224
rlm@33 225 Push32(header.magic, ptr);
rlm@33 226 Push32(header.version, ptr);
rlm@1 227
rlm@33 228 Push32(header.uid, ptr);
rlm@33 229 Push32(header.length_frames - 1, ptr); // HACK: reduce the length by 1 for compatibility with certain faulty old tools
rlm@33 230 // like TME
rlm@33 231 Push32(header.rerecord_count, ptr);
rlm@1 232
rlm@33 233 Push8(header.startFlags, ptr);
rlm@33 234 Push8(header.controllerFlags, ptr);
rlm@33 235 Push8(header.typeFlags, ptr);
rlm@33 236 Push8(header.optionFlags, ptr);
rlm@1 237
rlm@33 238 Push32(header.saveType, ptr);
rlm@33 239 Push32(header.flashSize, ptr);
rlm@33 240 Push32(header.gbEmulatorType, ptr);
rlm@1 241
rlm@33 242 for (int i = 0; i < 12; ++i)
rlm@33 243 Push8(header.romTitle[i], ptr);
rlm@1 244
rlm@33 245 Push8(header.minorVersion, ptr);
rlm@1 246
rlm@33 247 Push8(header.romCRC, ptr);
rlm@33 248 Push16(header.romOrBiosChecksum, ptr);
rlm@33 249 Push32(header.romGameCode, ptr);
rlm@1 250
rlm@33 251 Push32(header.offset_to_savestate, ptr);
rlm@33 252 Push32(header.offset_to_controller_data, ptr);
rlm@1 253
rlm@33 254 fwrite(headerData, 1, VBM_HEADER_SIZE, file);
rlm@1 255 }
rlm@1 256
rlm@1 257 static void flush_movie_header()
rlm@1 258 {
rlm@33 259 assert(Movie.file != 0 && "logical error!");
rlm@33 260 if (!Movie.file)
rlm@33 261 return;
rlm@1 262
rlm@33 263 long originalPos = ftell(Movie.file);
rlm@1 264
rlm@33 265 // (over-)write the header
rlm@33 266 fseek(Movie.file, 0, SEEK_SET);
rlm@33 267 write_movie_header(Movie.file, Movie);
rlm@1 268
rlm@33 269 fflush(Movie.file);
rlm@1 270
rlm@33 271 fseek(Movie.file, originalPos, SEEK_SET);
rlm@1 272 }
rlm@1 273
rlm@1 274 static void flush_movie_frames()
rlm@1 275 {
rlm@33 276 assert(Movie.file && "logical error!");
rlm@33 277 if (!Movie.file)
rlm@33 278 return;
rlm@1 279
rlm@33 280 long originalPos = ftell(Movie.file);
rlm@1 281
rlm@33 282 // overwrite the controller data
rlm@33 283 fseek(Movie.file, Movie.header.offset_to_controller_data, SEEK_SET);
rlm@33 284 fwrite(Movie.inputBuffer, 1, Movie.bytesPerFrame * Movie.header.length_frames, Movie.file);
rlm@1 285
rlm@33 286 fflush(Movie.file);
rlm@1 287
rlm@33 288 fseek(Movie.file, originalPos, SEEK_SET);
rlm@1 289 }
rlm@1 290
rlm@1 291 static void truncate_movie(long length)
rlm@1 292 {
rlm@33 293 // truncate movie to length
rlm@33 294 // NOTE: it's certain that the savestate block is never after the
rlm@33 295 // controller data block, because the VBM format decrees it.
rlm@1 296
rlm@33 297 assert(Movie.file && length >= 0);
rlm@33 298 if (!Movie.file || length < 0)
rlm@33 299 return;
rlm@1 300
rlm@33 301 assert(Movie.header.offset_to_savestate <= Movie.header.offset_to_controller_data);
rlm@33 302 if (Movie.header.offset_to_savestate > Movie.header.offset_to_controller_data)
rlm@33 303 return;
rlm@1 304
rlm@33 305 Movie.header.length_frames = length;
rlm@33 306 flush_movie_header();
rlm@33 307 const long truncLen = long(Movie.header.offset_to_controller_data + Movie.bytesPerFrame * length);
rlm@33 308 if (file_length(Movie.file) != truncLen)
rlm@33 309 {
rlm@33 310 ftruncate(fileno(Movie.file), truncLen);
rlm@33 311 }
rlm@1 312 }
rlm@1 313
rlm@1 314 static void remember_input_state()
rlm@1 315 {
rlm@33 316 for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i)
rlm@33 317 {
rlm@33 318 if (systemCartridgeType == 0)
rlm@1 319 {
rlm@33 320 initialInputs[i] = u16(~P1 & 0x03FF);
rlm@1 321 }
rlm@33 322 else
rlm@33 323 {
rlm@33 324 extern int32 gbJoymask[4];
rlm@33 325 for (int i = 0; i < 4; ++i)
rlm@33 326 initialInputs[i] = u16(gbJoymask[i] & 0xFFFF);
rlm@33 327 }
rlm@33 328 }
rlm@1 329 }
rlm@1 330
rlm@1 331 static void change_state(MovieState new_state)
rlm@1 332 {
rlm@1 333 #if (defined(WIN32) && !defined(SDL))
rlm@33 334 theApp.frameSearching = false;
rlm@33 335 theApp.frameSearchSkipping = false;
rlm@1 336 #endif
rlm@1 337
rlm@33 338 if (new_state == MOVIE_STATE_NONE)
rlm@33 339 {
rlm@33 340 Movie.pauseFrame = -1;
rlm@33 341
rlm@33 342 if (Movie.state == MOVIE_STATE_NONE)
rlm@33 343 return;
rlm@33 344
rlm@33 345 truncate_movie(Movie.header.length_frames);
rlm@33 346
rlm@33 347 fclose(Movie.file);
rlm@33 348 Movie.file = NULL;
rlm@33 349 Movie.currentFrame = 0;
rlm@33 350 #if (defined(WIN32) && !defined(SDL))
rlm@33 351 // undo changes to border settings
rlm@33 352 {
rlm@33 353 gbBorderOn = prevBorder;
rlm@33 354 theApp.winGbBorderOn = prevWinBorder;
rlm@33 355 gbBorderAutomatic = prevBorderAuto;
rlm@33 356 systemGbBorderOn();
rlm@33 357 }
rlm@33 358 #endif
rlm@33 359 gbEmulatorType = prevEmulatorType;
rlm@33 360
rlm@33 361 extern int32 gbDMASpeedVersion;
rlm@33 362 gbDMASpeedVersion = 1;
rlm@33 363
rlm@33 364 extern int32 gbEchoRAMFixOn;
rlm@33 365 gbEchoRAMFixOn = 1;
rlm@33 366
rlm@33 367 gbNullInputHackTempEnabled = gbNullInputHackEnabled;
rlm@33 368
rlm@33 369 if (Movie.inputBuffer)
rlm@1 370 {
rlm@33 371 free(Movie.inputBuffer);
rlm@33 372 Movie.inputBuffer = NULL;
rlm@33 373 }
rlm@33 374 }
rlm@33 375 else if (new_state == MOVIE_STATE_PLAY)
rlm@33 376 {
rlm@33 377 assert(Movie.file);
rlm@1 378
rlm@33 379 // this would cause problems if not dealt with
rlm@33 380 if (Movie.currentFrame >= Movie.header.length_frames)
rlm@33 381 {
rlm@33 382 new_state = MOVIE_STATE_END;
rlm@33 383 Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * Movie.header.length_frames;
rlm@33 384 }
rlm@33 385 }
rlm@33 386 else if (new_state == MOVIE_STATE_RECORD)
rlm@33 387 {
rlm@33 388 assert(Movie.file);
rlm@1 389
rlm@33 390 // this would cause problems if not dealt with
rlm@33 391 if (Movie.currentFrame > Movie.header.length_frames)
rlm@1 392 {
rlm@33 393 new_state = MOVIE_STATE_END;
rlm@33 394 Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * Movie.header.length_frames;
rlm@1 395 }
rlm@1 396
rlm@33 397 fseek(Movie.file, Movie.header.offset_to_controller_data + Movie.bytesPerFrame * Movie.currentFrame, SEEK_SET);
rlm@33 398 }
rlm@33 399
rlm@33 400 if (new_state == MOVIE_STATE_END && Movie.state != MOVIE_STATE_END)
rlm@33 401 {
rlm@33 402 #if defined(SDL)
rlm@33 403 systemClearJoypads();
rlm@33 404 #endif
rlm@33 405 systemScreenMessage("Movie end");
rlm@33 406 }
rlm@33 407
rlm@33 408 Movie.state = new_state;
rlm@33 409
rlm@33 410 // checking for movie end
rlm@33 411 bool willPause = false;
rlm@33 412
rlm@33 413 // if the movie's been set to pause at a certain frame
rlm@33 414 if (Movie.state != MOVIE_STATE_NONE && Movie.pauseFrame >= 0 && Movie.currentFrame == (uint32)Movie.pauseFrame)
rlm@33 415 {
rlm@33 416 Movie.pauseFrame = -1;
rlm@33 417 willPause = true;
rlm@33 418 }
rlm@33 419
rlm@33 420 if (Movie.state == MOVIE_STATE_END)
rlm@33 421 {
rlm@33 422 if (Movie.currentFrame == Movie.header.length_frames)
rlm@1 423 {
rlm@1 424 #if (defined(WIN32) && !defined(SDL))
rlm@33 425 if (theApp.movieOnEndPause)
rlm@33 426 {
rlm@33 427 willPause = true;
rlm@33 428 }
rlm@1 429 #else
rlm@33 430 // SDL FIXME
rlm@1 431 #endif
rlm@1 432
rlm@1 433 #if (defined(WIN32) && !defined(SDL))
rlm@33 434 switch (theApp.movieOnEndBehavior)
rlm@33 435 {
rlm@33 436 case 1:
rlm@33 437 // the old behavior
rlm@33 438 //VBAMovieRestart();
rlm@33 439 break;
rlm@33 440 case 2:
rlm@1 441 #else
rlm@33 442 // SDL FIXME
rlm@1 443 #endif
rlm@33 444 if (Movie.RecordedThisSession)
rlm@33 445 {
rlm@33 446 // if user has been recording this movie since the last time it started playing,
rlm@33 447 // they probably don't want the movie to end now during playback,
rlm@33 448 // so switch back to recording when it reaches the end
rlm@33 449 VBAMovieSwitchToRecording();
rlm@33 450 systemScreenMessage("Recording resumed");
rlm@33 451 willPause = true;
rlm@33 452 }
rlm@1 453 #if (defined(WIN32) && !defined(SDL))
rlm@33 454 break;
rlm@33 455 case 3:
rlm@33 456 // keep open
rlm@33 457 break;
rlm@33 458 case 0:
rlm@33 459 // fall through
rlm@33 460 default:
rlm@33 461 // close movie
rlm@33 462 //VBAMovieStop(false);
rlm@33 463 break;
rlm@33 464 }
rlm@1 465 #else
rlm@33 466 // SDL FIXME
rlm@1 467 #endif
rlm@33 468 }
rlm@1 469 #if 1
rlm@33 470 else if (Movie.currentFrame > Movie.header.length_frames)
rlm@33 471 {
rlm@1 472 #if (defined(WIN32) && !defined(SDL))
rlm@33 473 switch (theApp.movieOnEndBehavior)
rlm@33 474 {
rlm@33 475 case 1:
rlm@33 476 // FIXME: this should be delayed till the current frame ends
rlm@33 477 VBAMovieRestart();
rlm@33 478 break;
rlm@33 479 case 2:
rlm@33 480 // nothing
rlm@33 481 break;
rlm@33 482 case 3:
rlm@33 483 // keep open
rlm@33 484 break;
rlm@33 485 case 0:
rlm@33 486 // fall through
rlm@33 487 default:
rlm@33 488 // close movie
rlm@33 489 VBAMovieStop(false);
rlm@33 490 break;
rlm@33 491 }
rlm@1 492 #else
rlm@33 493 // SDLFIXME
rlm@1 494 #endif
rlm@33 495 }
rlm@1 496 #endif
rlm@33 497 } // end if (Movie.state == MOVIE_STATE_END)
rlm@1 498
rlm@33 499 if (willPause)
rlm@33 500 {
rlm@33 501 systemSetPause(true);
rlm@33 502 }
rlm@1 503 }
rlm@1 504
rlm@1 505 void VBAMovieInit()
rlm@1 506 {
rlm@33 507 memset(&Movie, 0, sizeof(Movie));
rlm@33 508 Movie.state = MOVIE_STATE_NONE;
rlm@33 509 Movie.pauseFrame = -1;
rlm@1 510
rlm@33 511 resetSignaled = false;
rlm@33 512 resetSignaledLast = false;
rlm@1 513 }
rlm@1 514
rlm@1 515 void VBAMovieGetRomInfo(const SMovie &movieInfo, char romTitle [12], uint32 &romGameCode, uint16 &checksum, uint8 &crc)
rlm@1 516 {
rlm@33 517 if (systemCartridgeType == 0) // GBA
rlm@33 518 {
rlm@33 519 extern u8 *bios, *rom;
rlm@33 520 memcpy(romTitle, &rom[0xa0], 12); // GBA TITLE
rlm@33 521 memcpy(&romGameCode, &rom[0xac], 4); // GBA ROM GAME CODE
rlm@33 522 if ((movieInfo.header.optionFlags & MOVIE_SETTING_USEBIOSFILE) != 0)
rlm@33 523 checksum = utilCalcBIOSChecksum(bios, 4); // GBA BIOS CHECKSUM
rlm@33 524 else
rlm@33 525 checksum = 0;
rlm@33 526 crc = rom[0xbd]; // GBA ROM CRC
rlm@33 527 }
rlm@33 528 else // non-GBA
rlm@33 529 {
rlm@33 530 extern u8 *gbRom;
rlm@33 531 memcpy(romTitle, &gbRom[0x134], 12); // GB TITLE (note this can be 15 but is truncated to 12)
rlm@33 532 romGameCode = (uint32)gbRom[0x146]; // GB ROM UNIT CODE
rlm@1 533
rlm@33 534 checksum = (gbRom[0x14e] << 8) | gbRom[0x14f]; // GB ROM CHECKSUM, read from big-endian
rlm@33 535 crc = gbRom[0x14d]; // GB ROM CRC
rlm@33 536 }
rlm@1 537 }
rlm@1 538
rlm@1 539 #ifdef SDL
rlm@1 540 static void GetBatterySaveName(char *buffer)
rlm@1 541 {
rlm@33 542 extern char batteryDir[2048], filename[2048]; // from SDL.cpp
rlm@33 543 extern char *sdlGetFilename(char *name); // from SDL.cpp
rlm@33 544 if (batteryDir[0])
rlm@33 545 sprintf(buffer, "%s/%s.sav", batteryDir, sdlGetFilename(filename));
rlm@33 546 else
rlm@33 547 sprintf(buffer, "%s.sav", filename);
rlm@1 548 }
rlm@1 549
rlm@1 550 #endif
rlm@1 551
rlm@1 552 static void SetPlayEmuSettings()
rlm@1 553 {
rlm@33 554 prevEmulatorType = gbEmulatorType;
rlm@33 555 gbEmulatorType = Movie.header.gbEmulatorType;
rlm@1 556
rlm@1 557 #if (defined(WIN32) && !defined(SDL))
rlm@33 558 // theApp.removeIntros = false;
rlm@33 559 theApp.skipBiosFile = (Movie.header.optionFlags & MOVIE_SETTING_SKIPBIOSFILE) != 0;
rlm@33 560 theApp.useBiosFile = (Movie.header.optionFlags & MOVIE_SETTING_USEBIOSFILE) != 0;
rlm@1 561 #else
rlm@33 562 extern int saveType, sdlRtcEnable, sdlFlashSize; // from SDL.cpp
rlm@33 563 extern bool8 useBios, skipBios, removeIntros; // from SDL.cpp
rlm@33 564 useBios = (Movie.header.optionFlags & MOVIE_SETTING_USEBIOSFILE) != 0;
rlm@33 565 skipBios = (Movie.header.optionFlags & MOVIE_SETTING_SKIPBIOSFILE) != 0;
rlm@33 566 removeIntros = false /*(Movie.header.optionFlags & MOVIE_SETTING_REMOVEINTROS) != 0*/;
rlm@1 567 #endif
rlm@1 568
rlm@33 569 extern void SetPrefetchHack(bool);
rlm@33 570 if (systemCartridgeType == 0) // lag disablement applies only to GBA
rlm@33 571 SetPrefetchHack((Movie.header.optionFlags & MOVIE_SETTING_LAGHACK) != 0);
rlm@1 572
rlm@33 573 gbNullInputHackTempEnabled = ((Movie.header.optionFlags & MOVIE_SETTING_GBINPUTHACK) != 0);
rlm@1 574
rlm@33 575 // some GB/GBC games depend on the sound rate, so just use the highest one
rlm@33 576 systemSoundSetQuality(1);
rlm@33 577 useOldFrameTiming = false;
rlm@1 578
rlm@33 579 extern int32 gbDMASpeedVersion;
rlm@33 580 if ((Movie.header.optionFlags & MOVIE_SETTING_GBCFF55FIX) != 0)
rlm@33 581 gbDMASpeedVersion = 1;
rlm@33 582 else
rlm@33 583 gbDMASpeedVersion = 0; // old CGB HDMA5 timing was used
rlm@1 584
rlm@33 585 extern int32 gbEchoRAMFixOn;
rlm@33 586 if ((Movie.header.optionFlags & MOVIE_SETTING_GBECHORAMFIX) != 0)
rlm@33 587 gbEchoRAMFixOn = 1;
rlm@33 588 else
rlm@33 589 gbEchoRAMFixOn = 0;
rlm@1 590
rlm@1 591 #if (defined(WIN32) && !defined(SDL))
rlm@33 592 rtcEnable((Movie.header.optionFlags & MOVIE_SETTING_RTCENABLE) != 0);
rlm@33 593 theApp.winSaveType = Movie.header.saveType;
rlm@33 594 theApp.winFlashSize = Movie.header.flashSize;
rlm@1 595
rlm@33 596 prevBorder = gbBorderOn;
rlm@33 597 prevWinBorder = theApp.winGbBorderOn;
rlm@33 598 prevBorderAuto = gbBorderAutomatic;
rlm@33 599 if ((gbEmulatorType == 2 || gbEmulatorType == 5)
rlm@33 600 && !theApp.hideMovieBorder) // games played in SGB mode can have a border
rlm@33 601 {
rlm@33 602 gbBorderOn = true;
rlm@33 603 theApp.winGbBorderOn = true;
rlm@33 604 gbBorderAutomatic = false;
rlm@33 605 }
rlm@33 606 else
rlm@33 607 {
rlm@33 608 gbBorderOn = false;
rlm@33 609 theApp.winGbBorderOn = false;
rlm@33 610 gbBorderAutomatic = false;
rlm@33 611 if (theApp.hideMovieBorder)
rlm@1 612 {
rlm@33 613 theApp.hideMovieBorder = false;
rlm@33 614 prevBorder = false; // it might be expected behaviour that it stays hidden after the movie
rlm@1 615 }
rlm@33 616 }
rlm@33 617 systemGbBorderOn();
rlm@1 618 #else
rlm@33 619 sdlRtcEnable = (Movie.header.optionFlags & MOVIE_SETTING_RTCENABLE) != 0;
rlm@33 620 saveType = Movie.header.saveType;
rlm@33 621 sdlFlashSize = Movie.header.flashSize;
rlm@1 622 #endif
rlm@1 623 }
rlm@1 624
rlm@1 625 static void HardResetAndSRAMClear()
rlm@1 626 {
rlm@1 627 #if (defined(WIN32) && !defined(SDL))
rlm@33 628 winEraseBatteryFile(); // delete the damn SRAM file and keep it from being resurrected from RAM
rlm@33 629 MainWnd *temp = ((MainWnd *)theApp.m_pMainWnd);
rlm@33 630 if (!temp->winFileRun(true)) // restart running the game
rlm@33 631 {
rlm@33 632 temp->winFileClose();
rlm@33 633 }
rlm@1 634 #else
rlm@33 635 char fname [1024];
rlm@33 636 GetBatterySaveName(fname);
rlm@33 637 remove(fname); // delete the damn SRAM file
rlm@1 638
rlm@33 639 // Henceforth, emuCleanUp means "clear out SRAM"
rlm@33 640 //theEmulator.emuCleanUp(); // keep it from being resurrected from RAM <--This is wrong, it'll deallocate all variables --Felipe
rlm@1 641
rlm@33 642 /// FIXME the correct SDL code to call for a full restart isn't in a function yet
rlm@33 643 theEmulator.emuReset(false);
rlm@1 644 #endif
rlm@1 645 }
rlm@1 646
rlm@1 647 int VBAMovieOpen(const char *filename, bool8 read_only)
rlm@1 648 {
rlm@33 649 loadingMovie = true;
rlm@33 650 uint8 movieReadOnly = read_only ? 1 : 0;
rlm@1 651
rlm@33 652 FILE * file;
rlm@33 653 STREAM stream;
rlm@33 654 int result;
rlm@33 655 int fn;
rlm@1 656
rlm@33 657 char movie_filename[_MAX_PATH];
rlm@1 658 #ifdef WIN32
rlm@33 659 _fullpath(movie_filename, filename, _MAX_PATH);
rlm@1 660 #else
rlm@33 661 // SDL FIXME: convert to fullpath
rlm@33 662 strncpy(movie_filename, filename, _MAX_PATH);
rlm@33 663 movie_filename[_MAX_PATH - 1] = '\0';
rlm@1 664 #endif
rlm@1 665
rlm@33 666 if (movie_filename[0] == '\0')
rlm@33 667 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }
rlm@1 668
rlm@33 669 if (!emulating)
rlm@33 670 { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; }
rlm@1 671
rlm@33 672 // bool alreadyOpen = (Movie.file != NULL && _stricmp(movie_filename, Movie.filename) == 0);
rlm@1 673
rlm@33 674 // if (alreadyOpen)
rlm@33 675 change_state(MOVIE_STATE_NONE); // have to stop current movie before trying to re-open it
rlm@1 676
rlm@33 677 if (!(file = fopen(movie_filename, "rb+")))
rlm@33 678 if (!(file = fopen(movie_filename, "rb")))
rlm@33 679 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }
rlm@33 680 //else
rlm@33 681 // movieReadOnly = 2; // we have to open the movie twice, no need to do this both times
rlm@1 682
rlm@33 683 // if (!alreadyOpen)
rlm@33 684 // change_state(MOVIE_STATE_NONE); // stop current movie when we're able to open the other one
rlm@33 685 //
rlm@33 686 // if (!(file = fopen(movie_filename, "rb+")))
rlm@33 687 // if(!(file = fopen(movie_filename, "rb")))
rlm@33 688 // {loadingMovie = false; return MOVIE_FILE_NOT_FOUND;}
rlm@33 689 // else
rlm@33 690 // movieReadOnly = 2;
rlm@1 691
rlm@33 692 // clear out the current movie
rlm@33 693 VBAMovieInit();
rlm@1 694
rlm@33 695 // read header
rlm@33 696 if ((result = read_movie_header(file, Movie)) != MOVIE_SUCCESS)
rlm@33 697 {
rlm@33 698 fclose(file);
rlm@33 699 { loadingMovie = false; return result; }
rlm@33 700 }
rlm@1 701
rlm@33 702 // set emulator settings that make the movie more likely to stay synchronized
rlm@33 703 SetPlayEmuSettings();
rlm@1 704
rlm@33 705 // extern bool systemLoadBIOS();
rlm@33 706 // if (!systemLoadBIOS())
rlm@33 707 // { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; }
rlm@1 708
rlm@33 709 // read the metadata / author info from file
rlm@33 710 fread(Movie.authorInfo, 1, MOVIE_METADATA_SIZE, file);
rlm@33 711 fn = dup(fileno(file)); // XXX: why does this fail?? it returns -1 but errno == 0
rlm@33 712 fclose(file);
rlm@1 713
rlm@33 714 // apparently this lseek is necessary
rlm@33 715 lseek(fn, Movie.header.offset_to_savestate, SEEK_SET);
rlm@33 716 if (!(stream = utilGzReopen(fn, "rb")))
rlm@33 717 if (!(stream = utilGzOpen(movie_filename, "rb")))
rlm@33 718 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }
rlm@33 719 else
rlm@33 720 fn = dup(fileno(file));
rlm@33 721 // in case the above dup failed but opening the file normally doesn't fail
rlm@1 722
rlm@33 723 if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT)
rlm@33 724 {
rlm@33 725 // load the snapshot
rlm@33 726 result = theEmulator.emuReadStateFromStream(stream) ? MOVIE_SUCCESS : MOVIE_WRONG_FORMAT;
rlm@1 727
rlm@33 728 // FIXME: Kludge for conversion
rlm@33 729 remember_input_state();
rlm@33 730 }
rlm@33 731 else if (Movie.header.startFlags & MOVIE_START_FROM_SRAM)
rlm@33 732 {
rlm@33 733 // 'soft' reset:
rlm@33 734 theEmulator.emuReset(false);
rlm@1 735
rlm@33 736 // load the SRAM
rlm@33 737 result = theEmulator.emuReadBatteryFromStream(stream) ? MOVIE_SUCCESS : MOVIE_WRONG_FORMAT;
rlm@33 738 }
rlm@33 739 else
rlm@33 740 {
rlm@33 741 HardResetAndSRAMClear();
rlm@33 742 }
rlm@1 743
rlm@33 744 utilGzClose(stream);
rlm@1 745
rlm@33 746 if (result != MOVIE_SUCCESS)
rlm@33 747 { loadingMovie = false; return result; }
rlm@1 748
rlm@33 749 // if (!(file = fopen(movie_filename, /*read_only ? "rb" :*/ "rb+"))) // want to be able to switch out of read-only later
rlm@33 750 // {
rlm@33 751 // if(!Movie.readOnly || !(file = fopen(movie_filename, "rb"))) // try read-only if failed
rlm@33 752 // return MOVIE_FILE_NOT_FOUND;
rlm@33 753 // }
rlm@33 754 if (!(file = fopen(movie_filename, "rb+")))
rlm@33 755 if (!(file = fopen(movie_filename, "rb")))
rlm@33 756 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }
rlm@33 757 else
rlm@33 758 movieReadOnly = 2;
rlm@1 759
rlm@33 760 // recalculate length of movie from the file size
rlm@33 761 Movie.bytesPerFrame = bytes_per_frame(Movie);
rlm@33 762 fseek(file, 0, SEEK_END);
rlm@33 763 long fileSize = ftell(file);
rlm@33 764 Movie.header.length_frames = (fileSize - Movie.header.offset_to_controller_data) / Movie.bytesPerFrame;
rlm@1 765
rlm@33 766 if (fseek(file, Movie.header.offset_to_controller_data, SEEK_SET))
rlm@33 767 { fclose(file); loadingMovie = false; return MOVIE_WRONG_FORMAT; }
rlm@1 768
rlm@33 769 strcpy(Movie.filename, movie_filename);
rlm@33 770 Movie.file = file;
rlm@33 771 Movie.inputBufferPtr = Movie.inputBuffer;
rlm@33 772 Movie.currentFrame = 0;
rlm@33 773 Movie.readOnly = movieReadOnly;
rlm@33 774 Movie.RecordedThisSession = false;
rlm@1 775
rlm@33 776 // read controller data
rlm@33 777 uint32 to_read = Movie.bytesPerFrame * Movie.header.length_frames;
rlm@33 778 reserve_buffer_space(to_read);
rlm@33 779 fread(Movie.inputBuffer, 1, to_read, file);
rlm@1 780
rlm@33 781 change_state(MOVIE_STATE_PLAY);
rlm@1 782
rlm@33 783 char messageString[64] = "Movie ";
rlm@33 784 bool converted = false;
rlm@33 785 if (autoConvertMovieWhenPlaying)
rlm@33 786 {
rlm@33 787 int result = VBAMovieConvertCurrent();
rlm@33 788 if (result == MOVIE_SUCCESS)
rlm@33 789 strcat(messageString, "converted and ");
rlm@33 790 else if (result == MOVIE_WRONG_VERSION)
rlm@33 791 strcat(messageString, "higher revision ");
rlm@33 792 }
rlm@1 793
rlm@33 794 if (Movie.state == MOVIE_STATE_PLAY)
rlm@33 795 strcat(messageString, "replaying ");
rlm@33 796 else
rlm@33 797 strcat(messageString, "finished ");
rlm@33 798 if (Movie.readOnly)
rlm@33 799 strcat(messageString, "(read)");
rlm@33 800 else
rlm@33 801 strcat(messageString, "(edit)");
rlm@33 802 systemScreenMessage(messageString);
rlm@1 803
rlm@33 804 VBAUpdateButtonPressDisplay();
rlm@33 805 VBAUpdateFrameCountDisplay();
rlm@33 806 systemRefreshScreen();
rlm@1 807
rlm@33 808 { loadingMovie = false; return MOVIE_SUCCESS; }
rlm@1 809 }
rlm@1 810
rlm@1 811 static void SetRecordEmuSettings()
rlm@1 812 {
rlm@33 813 Movie.header.optionFlags = 0;
rlm@1 814 #if (defined(WIN32) && !defined(SDL))
rlm@33 815 if (theApp.useBiosFile)
rlm@33 816 Movie.header.optionFlags |= MOVIE_SETTING_USEBIOSFILE;
rlm@33 817 if (theApp.skipBiosFile)
rlm@33 818 Movie.header.optionFlags |= MOVIE_SETTING_SKIPBIOSFILE;
rlm@33 819 if (rtcIsEnabled())
rlm@33 820 Movie.header.optionFlags |= MOVIE_SETTING_RTCENABLE;
rlm@33 821 Movie.header.saveType = theApp.winSaveType;
rlm@33 822 Movie.header.flashSize = theApp.winFlashSize;
rlm@1 823 #else
rlm@33 824 extern int saveType, sdlRtcEnable, sdlFlashSize; // from SDL.cpp
rlm@33 825 extern bool8 useBios, skipBios; // from SDL.cpp
rlm@33 826 if (useBios)
rlm@33 827 Movie.header.optionFlags |= MOVIE_SETTING_USEBIOSFILE;
rlm@33 828 if (skipBios)
rlm@33 829 Movie.header.optionFlags |= MOVIE_SETTING_SKIPBIOSFILE;
rlm@33 830 if (sdlRtcEnable)
rlm@33 831 Movie.header.optionFlags |= MOVIE_SETTING_RTCENABLE;
rlm@33 832 Movie.header.saveType = saveType;
rlm@33 833 Movie.header.flashSize = sdlFlashSize;
rlm@1 834 #endif
rlm@33 835 prevEmulatorType = Movie.header.gbEmulatorType = gbEmulatorType;
rlm@1 836
rlm@33 837 if (!memLagTempEnabled)
rlm@33 838 Movie.header.optionFlags |= MOVIE_SETTING_LAGHACK;
rlm@1 839
rlm@33 840 if (gbNullInputHackTempEnabled)
rlm@33 841 Movie.header.optionFlags |= MOVIE_SETTING_GBINPUTHACK;
rlm@1 842
rlm@33 843 Movie.header.optionFlags |= MOVIE_SETTING_GBCFF55FIX;
rlm@33 844 extern int32 gbDMASpeedVersion;
rlm@33 845 gbDMASpeedVersion = 1;
rlm@1 846
rlm@33 847 Movie.header.optionFlags |= MOVIE_SETTING_GBECHORAMFIX;
rlm@33 848 extern int32 gbEchoRAMFixOn;
rlm@33 849 gbEchoRAMFixOn = 1;
rlm@1 850
rlm@33 851 // some GB/GBC games depend on the sound rate, so just use the highest one
rlm@33 852 systemSoundSetQuality(1);
rlm@1 853
rlm@33 854 useOldFrameTiming = false;
rlm@1 855
rlm@1 856 #if (defined(WIN32) && !defined(SDL))
rlm@33 857 // theApp.removeIntros = false;
rlm@1 858
rlm@33 859 prevBorder = gbBorderOn;
rlm@33 860 prevWinBorder = theApp.winGbBorderOn;
rlm@33 861 prevBorderAuto = gbBorderAutomatic;
rlm@33 862 if (gbEmulatorType == 2 || gbEmulatorType == 5) // only games played in SGB mode will have a border
rlm@33 863 {
rlm@33 864 gbBorderOn = true;
rlm@33 865 theApp.winGbBorderOn = true;
rlm@33 866 gbBorderAutomatic = false;
rlm@33 867 }
rlm@33 868 else
rlm@33 869 {
rlm@33 870 gbBorderOn = false;
rlm@33 871 theApp.winGbBorderOn = false;
rlm@33 872 gbBorderAutomatic = false;
rlm@33 873 }
rlm@33 874 systemGbBorderOn();
rlm@1 875 #else
rlm@33 876 /// SDLFIXME
rlm@1 877 #endif
rlm@1 878 }
rlm@1 879
rlm@1 880 uint16 VBAMovieGetCurrentInputOf(int controllerNum, bool normalOnly)
rlm@1 881 {
rlm@33 882 if (controllerNum < 0 || controllerNum >= MOVIE_NUM_OF_POSSIBLE_CONTROLLERS)
rlm@33 883 return 0;
rlm@1 884
rlm@33 885 return normalOnly ? (currentButtons[controllerNum] & BUTTON_REGULAR_MASK) : currentButtons[controllerNum];
rlm@1 886 }
rlm@1 887
rlm@1 888 int VBAMovieCreate(const char *filename, const char *authorInfo, uint8 startFlags, uint8 controllerFlags, uint8 typeFlags)
rlm@1 889 {
rlm@33 890 // make sure at least one controller is enabled
rlm@33 891 if ((controllerFlags & MOVIE_CONTROLLERS_ANY_MASK) == 0)
rlm@33 892 return MOVIE_WRONG_FORMAT;
rlm@1 893
rlm@33 894 if (!emulating)
rlm@33 895 return MOVIE_UNKNOWN_ERROR;
rlm@1 896
rlm@33 897 loadingMovie = true;
rlm@1 898
rlm@33 899 FILE * file;
rlm@33 900 STREAM stream;
rlm@33 901 int fn;
rlm@1 902
rlm@33 903 char movie_filename [_MAX_PATH];
rlm@1 904 #ifdef WIN32
rlm@33 905 _fullpath(movie_filename, filename, _MAX_PATH);
rlm@1 906 #else
rlm@33 907 // FIXME: convert to fullpath
rlm@33 908 strncpy(movie_filename, filename, _MAX_PATH);
rlm@33 909 movie_filename[_MAX_PATH - 1] = '\0';
rlm@1 910 #endif
rlm@1 911
rlm@33 912 bool alreadyOpen = (Movie.file != NULL && stricmp(movie_filename, Movie.filename) == 0);
rlm@1 913
rlm@33 914 if (alreadyOpen)
rlm@33 915 change_state(MOVIE_STATE_NONE); // have to stop current movie before trying to re-open it
rlm@1 916
rlm@33 917 if (movie_filename[0] == '\0')
rlm@33 918 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }
rlm@33 919
rlm@33 920 if (!(file = fopen(movie_filename, "wb")))
rlm@33 921 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }
rlm@33 922
rlm@33 923 if (!alreadyOpen)
rlm@33 924 change_state(MOVIE_STATE_NONE); // stop current movie when we're able to open the other one
rlm@33 925
rlm@33 926 // clear out the current movie
rlm@33 927 printf("RLM: movie init\n");
rlm@33 928
rlm@33 929 VBAMovieInit();
rlm@33 930
rlm@33 931 // fill in the movie's header
rlm@33 932 Movie.header.uid = (uint32)time(NULL);
rlm@33 933 Movie.header.magic = VBM_MAGIC;
rlm@33 934 Movie.header.version = VBM_VERSION;
rlm@33 935 Movie.header.rerecord_count = 0;
rlm@33 936 Movie.header.length_frames = 0;
rlm@33 937 Movie.header.startFlags = startFlags;
rlm@33 938 Movie.header.controllerFlags = controllerFlags;
rlm@33 939 Movie.header.typeFlags = typeFlags;
rlm@33 940 Movie.header.minorVersion = VBM_REVISION;
rlm@33 941
rlm@33 942 // set emulator settings that make the movie more likely to stay synchronized when it's later played back
rlm@33 943 SetRecordEmuSettings();
rlm@33 944
rlm@33 945 // set ROM and BIOS checksums and stuff
rlm@33 946 VBAMovieGetRomInfo(Movie, Movie.header.romTitle, Movie.header.romGameCode, Movie.header.romOrBiosChecksum, Movie.header.romCRC);
rlm@33 947
rlm@33 948 printf("RLM: Writing movie header\n");
rlm@33 949 // write the header to file
rlm@33 950 write_movie_header(file, Movie);
rlm@33 951
rlm@33 952 printf("RLM: setting metadata\n");
rlm@33 953
rlm@33 954 // copy over the metadata / author info
rlm@33 955 VBAMovieSetMetadata("________________Robert McIntyre______________________________________________________________________________________________________________________________________________________________________________________________________________________");
rlm@33 956
rlm@33 957 printf("RLM: writing metadata\n");
rlm@33 958
rlm@33 959 // write the metadata / author info to file
rlm@33 960
rlm@33 961
rlm@33 962 fwrite(Movie.authorInfo, 1, sizeof(char) * MOVIE_METADATA_SIZE, file);
rlm@33 963
rlm@33 964 // write snapshot or SRAM if applicable
rlm@33 965 if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT
rlm@33 966 || Movie.header.startFlags & MOVIE_START_FROM_SRAM)
rlm@33 967 {
rlm@33 968 Movie.header.offset_to_savestate = (uint32)ftell(file);
rlm@33 969
rlm@33 970 // close the file and reopen it as a stream:
rlm@33 971
rlm@33 972 fn = dup(fileno(file));
rlm@33 973 fclose(file);
rlm@33 974
rlm@33 975 if (!(stream = utilGzReopen(fn, "ab"))) // append mode to start at end, no seek necessary
rlm@1 976 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }
rlm@1 977
rlm@33 978 // write the save data:
rlm@33 979 if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT)
rlm@33 980 {
rlm@33 981 // save snapshot
rlm@33 982 if (!theEmulator.emuWriteStateToStream(stream))
rlm@33 983 {
rlm@33 984 utilGzClose(stream);
rlm@33 985 { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; }
rlm@33 986 }
rlm@33 987 }
rlm@33 988 else if (Movie.header.startFlags & MOVIE_START_FROM_SRAM)
rlm@33 989 {
rlm@33 990 // save SRAM
rlm@33 991 if (!theEmulator.emuWriteBatteryToStream(stream))
rlm@33 992 {
rlm@33 993 utilGzClose(stream);
rlm@33 994 { loadingMovie = false; return MOVIE_UNKNOWN_ERROR; }
rlm@33 995 }
rlm@33 996
rlm@33 997 // 'soft' reset:
rlm@33 998 theEmulator.emuReset(false);
rlm@33 999 }
rlm@33 1000
rlm@33 1001 utilGzClose(stream);
rlm@33 1002
rlm@33 1003 // reopen the file and seek back to the end
rlm@33 1004
rlm@33 1005 if (!(file = fopen(movie_filename, "rb+")))
rlm@1 1006 { loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }
rlm@1 1007
rlm@33 1008 fseek(file, 0, SEEK_END);
rlm@33 1009 }
rlm@33 1010 else // no snapshot or SRAM
rlm@33 1011 {
rlm@33 1012 HardResetAndSRAMClear();
rlm@33 1013 }
rlm@1 1014
rlm@33 1015 Movie.header.offset_to_controller_data = (uint32)ftell(file);
rlm@1 1016
rlm@33 1017 strcpy(Movie.filename, movie_filename);
rlm@33 1018 Movie.file = file;
rlm@33 1019 Movie.bytesPerFrame = bytes_per_frame(Movie);
rlm@33 1020 Movie.inputBufferPtr = Movie.inputBuffer;
rlm@33 1021 Movie.currentFrame = 0;
rlm@33 1022 Movie.readOnly = false;
rlm@33 1023 Movie.RecordedThisSession = true;
rlm@1 1024
rlm@33 1025 change_state(MOVIE_STATE_RECORD);
rlm@1 1026
rlm@33 1027 systemScreenMessage("Recording movie...");
rlm@33 1028 { loadingMovie = false; return MOVIE_SUCCESS; }
rlm@1 1029 }
rlm@1 1030
rlm@1 1031 void VBAUpdateButtonPressDisplay()
rlm@1 1032 {
rlm@33 1033 uint32 keys = currentButtons[0] & BUTTON_REGULAR_RECORDING_MASK;
rlm@1 1034
rlm@33 1035 const static char KeyMap[] = { 'A', 'B', 's', 'S', '>', '<', '^', 'v', 'R', 'L', '!', '?', '{', '}', 'v', '^' };
rlm@33 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 { = } _
rlm@33 1037 // ? !
rlm@33 1038 char buffer[256];
rlm@33 1039 sprintf(buffer, " ");
rlm@1 1040
rlm@1 1041 #ifndef WIN32
rlm@33 1042 // don't bother color-coding autofire and such
rlm@33 1043 int i;
rlm@33 1044 for (i = 0; i < 15; i++)
rlm@33 1045 {
rlm@33 1046 int j = KeyOrder[i];
rlm@33 1047 int mask = (1 << (j));
rlm@33 1048 buffer[strlen(" ") + i] = ((keys & mask) != 0) ? KeyMap[j] : ' ';
rlm@33 1049 }
rlm@33 1050
rlm@33 1051 systemScreenMessage(buffer, 2, -1);
rlm@33 1052 #else
rlm@33 1053 const bool eraseAll = !theApp.inputDisplay;
rlm@33 1054 uint32 autoHeldKeys = eraseAll ? 0 : theApp.autoHold & BUTTON_REGULAR_RECORDING_MASK;
rlm@33 1055 uint32 autoFireKeys = eraseAll ? 0 : (theApp.autoFire | theApp.autoFire2) & BUTTON_REGULAR_RECORDING_MASK;
rlm@33 1056 uint32 pressedKeys = eraseAll ? 0 : keys;
rlm@33 1057
rlm@33 1058 char colorList[64];
rlm@33 1059 memset(colorList, 1, strlen(buffer));
rlm@33 1060
rlm@33 1061 if (!eraseAll)
rlm@33 1062 {
rlm@33 1063 for (int i = 0; i < 15; i++)
rlm@1 1064 {
rlm@33 1065 const int j = KeyOrder[i];
rlm@33 1066 const int mask = (1 << (j));
rlm@33 1067 bool pressed = (pressedKeys & mask) != 0;
rlm@33 1068 const bool autoHeld = (autoHeldKeys & mask) != 0;
rlm@33 1069 const bool autoFired = (autoFireKeys & mask) != 0;
rlm@33 1070 const bool erased = (lastKeys & mask) != 0 && (!pressed && !autoHeld && !autoFired);
rlm@33 1071 extern int textMethod;
rlm@33 1072 if (textMethod != 2 && (autoHeld || (autoFired && !pressed) || erased))
rlm@33 1073 {
rlm@33 1074 int colorNum = 1; // default is white
rlm@33 1075 if (autoHeld)
rlm@33 1076 colorNum += (pressed ? 2 : 1); // yellow if pressed, red if not
rlm@33 1077 else if (autoFired)
rlm@33 1078 colorNum += 5; // blue if autofired and not currently pressed
rlm@33 1079 else if (erased)
rlm@33 1080 colorNum += 8; // black on black
rlm@33 1081
rlm@33 1082 colorList[strlen(" ") + i] = colorNum;
rlm@33 1083 pressed = true;
rlm@33 1084 }
rlm@33 1085 buffer[strlen(" ") + i] = pressed ? KeyMap[j] : ' ';
rlm@1 1086 }
rlm@33 1087 }
rlm@1 1088
rlm@33 1089 lastKeys = currentButtons[0];
rlm@33 1090 lastKeys |= theApp.autoHold & BUTTON_REGULAR_RECORDING_MASK;
rlm@33 1091 lastKeys |= (theApp.autoFire | theApp.autoFire2) & BUTTON_REGULAR_RECORDING_MASK;
rlm@1 1092
rlm@33 1093 systemScreenMessage(buffer, 2, -1, colorList);
rlm@1 1094 #endif
rlm@1 1095 }
rlm@1 1096
rlm@1 1097 void VBAUpdateFrameCountDisplay()
rlm@1 1098 {
rlm@33 1099 const int MAGICAL_NUMBER = 64; // FIXME: this won't do any better, but only to remind you of sz issues
rlm@33 1100 char frameDisplayString[MAGICAL_NUMBER];
rlm@33 1101 char lagFrameDisplayString[MAGICAL_NUMBER];
rlm@33 1102 char extraCountDisplayString[MAGICAL_NUMBER];
rlm@1 1103
rlm@1 1104 #if (defined(WIN32) && !defined(SDL))
rlm@33 1105 if (theApp.frameCounter)
rlm@33 1106 #else
rlm@33 1107 /// SDL FIXME
rlm@33 1108 #endif
rlm@33 1109 {
rlm@33 1110 switch (Movie.state)
rlm@33 1111 {
rlm@33 1112 case MOVIE_STATE_PLAY:
rlm@33 1113 case MOVIE_STATE_END:
rlm@33 1114 {
rlm@33 1115 sprintf(frameDisplayString, "%d / %d", Movie.currentFrame, Movie.header.length_frames);
rlm@33 1116 if (!Movie.readOnly)
rlm@33 1117 strcat(frameDisplayString, " (edit)");
rlm@33 1118 break;
rlm@33 1119 }
rlm@33 1120 case MOVIE_STATE_RECORD:
rlm@33 1121 {
rlm@33 1122 sprintf(frameDisplayString, "%d (record)", Movie.currentFrame);
rlm@33 1123 break;
rlm@33 1124 }
rlm@33 1125 default:
rlm@33 1126 {
rlm@33 1127 sprintf(frameDisplayString, "%d (no movie)", systemCounters.frameCount);
rlm@33 1128 break;
rlm@33 1129 }
rlm@33 1130 }
rlm@33 1131
rlm@33 1132 #if (defined(WIN32) && !defined(SDL))
rlm@33 1133 if (theApp.lagCounter)
rlm@1 1134 #else
rlm@1 1135 /// SDL FIXME
rlm@1 1136 #endif
rlm@1 1137 {
rlm@33 1138 // sprintf(lagFrameDisplayString, " %c %d", systemCounters.laggedLast ? '*' : '|', systemCounters.lagCount);
rlm@33 1139 sprintf(lagFrameDisplayString, " | %d%s", systemCounters.lagCount, systemCounters.laggedLast ? " *" : "");
rlm@33 1140 strcat(frameDisplayString, lagFrameDisplayString);
rlm@33 1141 }
rlm@1 1142
rlm@1 1143 #if (defined(WIN32) && !defined(SDL))
rlm@33 1144 if (theApp.extraCounter)
rlm@1 1145 #else
rlm@1 1146 /// SDL FIXME
rlm@1 1147 #endif
rlm@33 1148 {
rlm@33 1149 sprintf(extraCountDisplayString, " | %d", systemCounters.frameCount - systemCounters.extraCount);
rlm@33 1150 strcat(frameDisplayString, extraCountDisplayString);
rlm@33 1151 }
rlm@33 1152 }
rlm@33 1153 #if (defined(WIN32) && !defined(SDL))
rlm@33 1154 else
rlm@33 1155 {
rlm@33 1156 frameDisplayString[0] = '\0';
rlm@33 1157 }
rlm@33 1158 #else
rlm@33 1159 /// SDL FIXME
rlm@33 1160 #endif
rlm@33 1161 systemScreenMessage(frameDisplayString, 1, -1);
rlm@1 1162 }
rlm@1 1163
rlm@1 1164 // this function should only be called once every frame
rlm@1 1165 void VBAMovieUpdateState()
rlm@1 1166 {
rlm@33 1167 ++Movie.currentFrame;
rlm@33 1168 printf("RLM: inside updateState\n");
rlm@33 1169 if (Movie.state == MOVIE_STATE_PLAY)
rlm@33 1170 {
rlm@33 1171 Movie.inputBufferPtr += Movie.bytesPerFrame;
rlm@33 1172 if (Movie.currentFrame >= Movie.header.length_frames)
rlm@1 1173 {
rlm@33 1174 // the movie ends anyway; what to do next depends on the settings
rlm@33 1175 change_state(MOVIE_STATE_END);
rlm@1 1176 }
rlm@33 1177 }
rlm@33 1178 else if (Movie.state == MOVIE_STATE_RECORD)
rlm@33 1179 {
rlm@33 1180 printf("RLM: Movie_STATE_RECORD\n");
rlm@33 1181 // use first fseek?
rlm@33 1182 //TODO: THis is the problem.
rlm@34 1183 fwrite(Movie.inputBufferPtr, 1, Movie.bytesPerFrame, Movie.file);
rlm@34 1184 printf("RLM: write successful.\n");
rlm@33 1185 Movie.header.length_frames = Movie.currentFrame;
rlm@33 1186 Movie.inputBufferPtr += Movie.bytesPerFrame;
rlm@33 1187 Movie.RecordedThisSession = true;
rlm@33 1188 flush_movie_header();
rlm@33 1189 }
rlm@33 1190 else if (Movie.state == MOVIE_STATE_END)
rlm@33 1191 {
rlm@33 1192 change_state(MOVIE_STATE_END);
rlm@33 1193 }
rlm@1 1194 }
rlm@1 1195
rlm@1 1196 void VBAMovieRead(int i, bool /*sensor*/)
rlm@1 1197 {
rlm@33 1198 if (Movie.state != MOVIE_STATE_PLAY)
rlm@33 1199 return;
rlm@1 1200
rlm@33 1201 if (i < 0 || i >= MOVIE_NUM_OF_POSSIBLE_CONTROLLERS)
rlm@33 1202 return; // not a controller we're recognizing
rlm@1 1203
rlm@33 1204 if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i))
rlm@33 1205 {
rlm@33 1206 currentButtons[i] = Read16(Movie.inputBufferPtr + CONTROLLER_DATA_SIZE * i);
rlm@33 1207 }
rlm@33 1208 else
rlm@33 1209 {
rlm@33 1210 currentButtons[i] = 0; // pretend the controller is disconnected
rlm@33 1211 }
rlm@1 1212
rlm@33 1213 if ((currentButtons[i] & BUTTON_MASK_NEW_RESET) != 0)
rlm@33 1214 resetSignaled = true;
rlm@1 1215 }
rlm@1 1216
rlm@1 1217 void VBAMovieWrite(int i, bool /*sensor*/)
rlm@1 1218 {
rlm@33 1219 if (Movie.state != MOVIE_STATE_RECORD)
rlm@33 1220 return;
rlm@1 1221
rlm@33 1222 if (i < 0 || i >= MOVIE_NUM_OF_POSSIBLE_CONTROLLERS)
rlm@33 1223 return; // not a controller we're recognizing
rlm@1 1224
rlm@33 1225 reserve_buffer_space((uint32)((Movie.inputBufferPtr - Movie.inputBuffer) + Movie.bytesPerFrame));
rlm@1 1226
rlm@33 1227 if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i))
rlm@33 1228 {
rlm@33 1229 // get the current controller data
rlm@33 1230 uint16 buttonData = currentButtons[i];
rlm@33 1231
rlm@33 1232 // mask away the irrelevent bits
rlm@33 1233 buttonData &= BUTTON_REGULAR_MASK | BUTTON_MOTION_MASK;
rlm@33 1234
rlm@33 1235 // soft-reset "button" for 1 frame if the game is reset while recording
rlm@33 1236 if (resetSignaled)
rlm@1 1237 {
rlm@33 1238 buttonData |= BUTTON_MASK_NEW_RESET;
rlm@33 1239 }
rlm@1 1240
rlm@33 1241 // backward compatibility kludge
rlm@33 1242 if (resetSignaledLast)
rlm@33 1243 {
rlm@33 1244 buttonData |= BUTTON_MASK_OLD_RESET;
rlm@33 1245 }
rlm@1 1246
rlm@33 1247 Write16(buttonData, Movie.inputBufferPtr + CONTROLLER_DATA_SIZE * i);
rlm@1 1248
rlm@33 1249 // and for display
rlm@33 1250 currentButtons[i] = buttonData;
rlm@33 1251 }
rlm@33 1252 else
rlm@33 1253 {
rlm@33 1254 // pretend the controller is disconnected (otherwise input it gives could cause desync since we're not writing it to the
rlm@33 1255 // movie)
rlm@33 1256 currentButtons[i] = 0;
rlm@33 1257 }
rlm@1 1258 }
rlm@1 1259
rlm@1 1260 void VBAMovieStop(bool8 suppress_message)
rlm@1 1261 {
rlm@33 1262 if (Movie.state != MOVIE_STATE_NONE)
rlm@33 1263 {
rlm@33 1264 change_state(MOVIE_STATE_NONE);
rlm@33 1265 if (!suppress_message)
rlm@33 1266 systemScreenMessage("Movie stop");
rlm@33 1267 }
rlm@1 1268 }
rlm@1 1269
rlm@1 1270 int VBAMovieGetInfo(const char *filename, SMovie *info)
rlm@1 1271 {
rlm@33 1272 assert(info != NULL);
rlm@33 1273 if (info == NULL)
rlm@33 1274 return -1;
rlm@1 1275
rlm@33 1276 FILE * file;
rlm@33 1277 int result;
rlm@33 1278 SMovie &local_movie = *info;
rlm@1 1279
rlm@33 1280 memset(info, 0, sizeof(*info));
rlm@33 1281 if (filename[0] == '\0')
rlm@33 1282 return MOVIE_FILE_NOT_FOUND;
rlm@33 1283 if (!(file = fopen(filename, "rb")))
rlm@33 1284 return MOVIE_FILE_NOT_FOUND;
rlm@1 1285
rlm@33 1286 // read header
rlm@33 1287 if ((result = (read_movie_header(file, local_movie))) != MOVIE_SUCCESS)
rlm@33 1288 {
rlm@33 1289 fclose(file);
rlm@33 1290 return result;
rlm@33 1291 }
rlm@1 1292
rlm@33 1293 // read the metadata / author info from file
rlm@33 1294 fread(local_movie.authorInfo, 1, sizeof(char) * MOVIE_METADATA_SIZE, file);
rlm@1 1295
rlm@33 1296 strncpy(local_movie.filename, filename, _MAX_PATH);
rlm@33 1297 local_movie.filename[_MAX_PATH - 1] = '\0';
rlm@1 1298
rlm@33 1299 if (Movie.file != NULL && stricmp(local_movie.filename, Movie.filename) == 0) // alreadyOpen
rlm@33 1300 {
rlm@33 1301 local_movie.bytesPerFrame = Movie.bytesPerFrame;
rlm@33 1302 local_movie.header.length_frames = Movie.header.length_frames;
rlm@33 1303 }
rlm@33 1304 else
rlm@33 1305 {
rlm@33 1306 // recalculate length of movie from the file size
rlm@33 1307 local_movie.bytesPerFrame = bytes_per_frame(local_movie);
rlm@33 1308 fseek(file, 0, SEEK_END);
rlm@33 1309 int fileSize = ftell(file);
rlm@33 1310 local_movie.header.length_frames =
rlm@33 1311 (fileSize - local_movie.header.offset_to_controller_data) / local_movie.bytesPerFrame;
rlm@33 1312 }
rlm@1 1313
rlm@33 1314 fclose(file);
rlm@1 1315
rlm@33 1316 if (access(filename, W_OK))
rlm@33 1317 info->readOnly = true;
rlm@1 1318
rlm@33 1319 return MOVIE_SUCCESS;
rlm@1 1320 }
rlm@1 1321
rlm@1 1322 bool8 VBAMovieActive()
rlm@1 1323 {
rlm@33 1324 return (Movie.state != MOVIE_STATE_NONE);
rlm@1 1325 }
rlm@1 1326
rlm@1 1327 bool8 VBAMovieLoading()
rlm@1 1328 {
rlm@33 1329 return loadingMovie;
rlm@1 1330 }
rlm@1 1331
rlm@1 1332 bool8 VBAMoviePlaying()
rlm@1 1333 {
rlm@33 1334 return (Movie.state == MOVIE_STATE_PLAY);
rlm@1 1335 }
rlm@1 1336
rlm@1 1337 bool8 VBAMovieRecording()
rlm@1 1338 {
rlm@33 1339 return (Movie.state == MOVIE_STATE_RECORD);
rlm@1 1340 }
rlm@1 1341
rlm@1 1342 bool8 VBAMovieReadOnly()
rlm@1 1343 {
rlm@33 1344 if (!VBAMovieActive())
rlm@33 1345 return false;
rlm@1 1346
rlm@33 1347 return Movie.readOnly;
rlm@1 1348 }
rlm@1 1349
rlm@1 1350 void VBAMovieToggleReadOnly()
rlm@1 1351 {
rlm@33 1352 if (!VBAMovieActive())
rlm@33 1353 return;
rlm@1 1354
rlm@33 1355 if (Movie.readOnly != 2)
rlm@33 1356 {
rlm@33 1357 Movie.readOnly = !Movie.readOnly;
rlm@1 1358
rlm@33 1359 systemScreenMessage(Movie.readOnly ? "Movie now read-only" : "Movie now editable");
rlm@33 1360 }
rlm@33 1361 else
rlm@33 1362 {
rlm@33 1363 systemScreenMessage("Can't toggle read-only movie");
rlm@33 1364 }
rlm@1 1365 }
rlm@1 1366
rlm@1 1367 uint32 VBAMovieGetVersion()
rlm@1 1368 {
rlm@33 1369 if (!VBAMovieActive())
rlm@33 1370 return 0;
rlm@1 1371
rlm@33 1372 return Movie.header.version;
rlm@1 1373 }
rlm@1 1374
rlm@1 1375 uint32 VBAMovieGetMinorVersion()
rlm@1 1376 {
rlm@33 1377 if (!VBAMovieActive())
rlm@33 1378 return 0;
rlm@1 1379
rlm@33 1380 return Movie.header.minorVersion;
rlm@1 1381 }
rlm@1 1382
rlm@1 1383 uint32 VBAMovieGetId()
rlm@1 1384 {
rlm@33 1385 if (!VBAMovieActive())
rlm@33 1386 return 0;
rlm@1 1387
rlm@33 1388 return Movie.header.uid;
rlm@1 1389 }
rlm@1 1390
rlm@1 1391 uint32 VBAMovieGetLength()
rlm@1 1392 {
rlm@33 1393 if (!VBAMovieActive())
rlm@33 1394 return 0;
rlm@1 1395
rlm@33 1396 return Movie.header.length_frames;
rlm@1 1397 }
rlm@1 1398
rlm@1 1399 uint32 VBAMovieGetFrameCounter()
rlm@1 1400 {
rlm@33 1401 if (!VBAMovieActive())
rlm@33 1402 return 0;
rlm@1 1403
rlm@33 1404 return Movie.currentFrame;
rlm@1 1405 }
rlm@1 1406
rlm@1 1407 uint32 VBAMovieGetRerecordCount()
rlm@1 1408 {
rlm@33 1409 if (!VBAMovieActive())
rlm@33 1410 return 0;
rlm@1 1411
rlm@33 1412 return Movie.header.rerecord_count;
rlm@1 1413 }
rlm@1 1414
rlm@1 1415 uint32 VBAMovieSetRerecordCount(uint32 newRerecordCount)
rlm@1 1416 {
rlm@33 1417 uint32 oldRerecordCount = 0;
rlm@33 1418 if (!VBAMovieActive())
rlm@33 1419 return 0;
rlm@1 1420
rlm@33 1421 oldRerecordCount = Movie.header.rerecord_count;
rlm@33 1422 Movie.header.rerecord_count = newRerecordCount;
rlm@33 1423 return oldRerecordCount;
rlm@1 1424 }
rlm@1 1425
rlm@1 1426 std::string VBAMovieGetAuthorInfo()
rlm@1 1427 {
rlm@33 1428 if (!VBAMovieActive())
rlm@33 1429 return "";
rlm@1 1430
rlm@33 1431 return Movie.authorInfo;
rlm@1 1432 }
rlm@1 1433
rlm@1 1434 std::string VBAMovieGetFilename()
rlm@1 1435 {
rlm@33 1436 if (!VBAMovieActive())
rlm@33 1437 return "";
rlm@1 1438
rlm@33 1439 return Movie.filename;
rlm@1 1440 }
rlm@1 1441
rlm@1 1442 void VBAMovieFreeze(uint8 * *buf, uint32 *size)
rlm@1 1443 {
rlm@33 1444 // sanity check
rlm@33 1445 if (!VBAMovieActive())
rlm@33 1446 {
rlm@33 1447 return;
rlm@33 1448 }
rlm@1 1449
rlm@33 1450 *buf = NULL;
rlm@33 1451 *size = 0;
rlm@1 1452
rlm@33 1453 // compute size needed for the buffer
rlm@33 1454 // room for header.uid, currentFrame, and header.length_frames
rlm@33 1455 uint32 size_needed = sizeof(Movie.header.uid) + sizeof(Movie.currentFrame) + sizeof(Movie.header.length_frames);
rlm@33 1456 size_needed += (uint32)(Movie.bytesPerFrame * Movie.header.length_frames);
rlm@33 1457 *buf = new uint8[size_needed];
rlm@33 1458 *size = size_needed;
rlm@1 1459
rlm@33 1460 uint8 *ptr = *buf;
rlm@33 1461 if (!ptr)
rlm@33 1462 {
rlm@33 1463 return;
rlm@33 1464 }
rlm@1 1465
rlm@33 1466 Push32(Movie.header.uid, ptr);
rlm@33 1467 Push32(Movie.currentFrame, ptr);
rlm@33 1468 Push32(Movie.header.length_frames - 1, ptr); // HACK: shorten the length by 1 for backward compatibility
rlm@1 1469
rlm@33 1470 memcpy(ptr, Movie.inputBuffer, Movie.bytesPerFrame * Movie.header.length_frames);
rlm@1 1471 }
rlm@1 1472
rlm@1 1473 int VBAMovieUnfreeze(const uint8 *buf, uint32 size)
rlm@1 1474 {
rlm@33 1475 // sanity check
rlm@33 1476 if (!VBAMovieActive())
rlm@33 1477 {
rlm@33 1478 return MOVIE_NOT_FROM_A_MOVIE;
rlm@33 1479 }
rlm@33 1480
rlm@33 1481 const uint8 *ptr = buf;
rlm@33 1482 if (size < sizeof(Movie.header.uid) + sizeof(Movie.currentFrame) + sizeof(Movie.header.length_frames))
rlm@33 1483 {
rlm@33 1484 return MOVIE_WRONG_FORMAT;
rlm@33 1485 }
rlm@33 1486
rlm@33 1487 uint32 movie_id = Pop32(ptr);
rlm@33 1488 uint32 current_frame = Pop32(ptr);
rlm@33 1489 uint32 end_frame = Pop32(ptr) + 1; // HACK: restore the length for backward compatibility
rlm@33 1490 uint32 space_needed = Movie.bytesPerFrame * end_frame;
rlm@33 1491
rlm@33 1492 if (movie_id != Movie.header.uid)
rlm@33 1493 return MOVIE_NOT_FROM_THIS_MOVIE;
rlm@33 1494
rlm@33 1495 if (space_needed > size)
rlm@33 1496 return MOVIE_WRONG_FORMAT;
rlm@33 1497
rlm@33 1498 if (Movie.readOnly)
rlm@33 1499 {
rlm@33 1500 // here, we are going to keep the input data from the movie file
rlm@33 1501 // and simply rewind to the currentFrame pointer
rlm@33 1502 // this will cause a desync if the savestate is not in sync // <-- NOT ANYMORE
rlm@33 1503 // with the on-disk recording data, but it's easily solved
rlm@33 1504 // by loading another savestate or playing the movie from the beginning
rlm@33 1505
rlm@33 1506 // don't allow loading a state inconsistent with the current movie
rlm@33 1507 uint32 length_history = min(current_frame, Movie.header.length_frames);
rlm@33 1508 if (end_frame < length_history)
rlm@33 1509 return MOVIE_SNAPSHOT_INCONSISTENT;
rlm@33 1510
rlm@33 1511 uint32 space_shared = Movie.bytesPerFrame * length_history;
rlm@33 1512 if (memcmp(Movie.inputBuffer, ptr, space_shared))
rlm@33 1513 return MOVIE_SNAPSHOT_INCONSISTENT;
rlm@33 1514
rlm@33 1515 Movie.currentFrame = current_frame;
rlm@33 1516 Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * min(current_frame, Movie.header.length_frames);
rlm@33 1517 }
rlm@33 1518 else
rlm@33 1519 {
rlm@33 1520 // here, we are going to take the input data from the savestate
rlm@33 1521 // and make it the input data for the current movie, then continue
rlm@33 1522 // writing new input data at the currentFrame pointer
rlm@33 1523 Movie.currentFrame = current_frame;
rlm@33 1524 Movie.header.length_frames = end_frame;
rlm@33 1525 if (!VBALuaRerecordCountSkip())
rlm@33 1526 ++Movie.header.rerecord_count;
rlm@33 1527
rlm@33 1528 Movie.RecordedThisSession = true;
rlm@33 1529
rlm@33 1530 // do this before calling reserve_buffer_space()
rlm@33 1531 Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * min(current_frame, Movie.header.length_frames);
rlm@33 1532 reserve_buffer_space(space_needed);
rlm@33 1533 memcpy(Movie.inputBuffer, ptr, space_needed);
rlm@33 1534
rlm@33 1535 // for consistency, no auto movie conversion here since we don't auto convert the corresponding savestate
rlm@33 1536 flush_movie_header();
rlm@33 1537 flush_movie_frames();
rlm@33 1538 }
rlm@33 1539
rlm@33 1540 change_state(MOVIE_STATE_PLAY); // check for movie end
rlm@33 1541
rlm@33 1542 // necessary!
rlm@33 1543 resetSignaled = false;
rlm@33 1544 resetSignaledLast = false;
rlm@33 1545
rlm@33 1546 // necessary to check if there's a reset signal at the previous frame
rlm@33 1547 if (current_frame > 0)
rlm@33 1548 {
rlm@33 1549 const u8 NEW_RESET = u8(BUTTON_MASK_NEW_RESET >> 8);
rlm@33 1550 for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i)
rlm@1 1551 {
rlm@33 1552 if ((Movie.header.controllerFlags & MOVIE_CONTROLLER(i)) && (*(Movie.inputBufferPtr+1- Movie.bytesPerFrame) & NEW_RESET))
rlm@33 1553 {
rlm@33 1554 resetSignaledLast = true;
rlm@33 1555 break;
rlm@33 1556 }
rlm@1 1557 }
rlm@33 1558 }
rlm@1 1559
rlm@33 1560 return MOVIE_SUCCESS;
rlm@1 1561 }
rlm@1 1562
rlm@1 1563 bool VBAMovieEnded()
rlm@1 1564 {
rlm@33 1565 return (Movie.state == MOVIE_STATE_END);
rlm@33 1566 // return (Movie.state != MOVIE_STATE_NONE && Movie.currentFrame >= Movie.header.length_frames);
rlm@1 1567 }
rlm@1 1568
rlm@1 1569 bool VBAMovieAllowsRerecording()
rlm@1 1570 {
rlm@33 1571 bool allows = (Movie.state != MOVIE_STATE_NONE) && (Movie.currentFrame <= Movie.header.length_frames);
rlm@33 1572 return /*!VBAMovieReadOnly() &&*/ allows;
rlm@1 1573 }
rlm@1 1574
rlm@1 1575 bool VBAMovieSwitchToPlaying()
rlm@1 1576 {
rlm@33 1577 if (!VBAMovieActive())
rlm@33 1578 return false;
rlm@1 1579
rlm@33 1580 if (!Movie.readOnly)
rlm@33 1581 {
rlm@33 1582 VBAMovieToggleReadOnly();
rlm@33 1583 }
rlm@1 1584
rlm@33 1585 change_state(MOVIE_STATE_PLAY);
rlm@33 1586 if (Movie.state == MOVIE_STATE_PLAY)
rlm@33 1587 systemScreenMessage("Movie replay (continue)");
rlm@33 1588 else
rlm@33 1589 systemScreenMessage("Movie end");
rlm@1 1590
rlm@33 1591 return true;
rlm@1 1592 }
rlm@1 1593
rlm@1 1594 bool VBAMovieSwitchToRecording()
rlm@1 1595 {
rlm@33 1596 if (!VBAMovieAllowsRerecording())
rlm@33 1597 return false;
rlm@1 1598
rlm@33 1599 if (Movie.readOnly)
rlm@33 1600 {
rlm@33 1601 VBAMovieToggleReadOnly();
rlm@33 1602 }
rlm@1 1603
rlm@33 1604 if (!VBALuaRerecordCountSkip())
rlm@33 1605 ++Movie.header.rerecord_count;
rlm@1 1606
rlm@33 1607 change_state(MOVIE_STATE_RECORD);
rlm@33 1608 systemScreenMessage("Movie re-record");
rlm@1 1609
rlm@33 1610 //truncate_movie(Movie.currentFrame);
rlm@1 1611
rlm@33 1612 return true;
rlm@1 1613 }
rlm@1 1614
rlm@1 1615 uint32 VBAMovieGetState()
rlm@1 1616 {
rlm@33 1617 // ?
rlm@33 1618 if (!VBAMovieActive())
rlm@33 1619 return MOVIE_STATE_NONE;
rlm@1 1620
rlm@33 1621 return Movie.state;
rlm@1 1622 }
rlm@1 1623
rlm@1 1624 void VBAMovieSignalReset()
rlm@1 1625 {
rlm@33 1626 if (VBAMovieActive())
rlm@33 1627 resetSignaled = true;
rlm@1 1628 }
rlm@1 1629
rlm@1 1630 void VBAMovieResetIfRequested()
rlm@1 1631 {
rlm@33 1632 if (resetSignaled)
rlm@33 1633 {
rlm@33 1634 theEmulator.emuReset(false);
rlm@33 1635 resetSignaled = false;
rlm@33 1636 resetSignaledLast = true;
rlm@33 1637 }
rlm@33 1638 else
rlm@33 1639 {
rlm@33 1640 resetSignaledLast = false;
rlm@33 1641 }
rlm@1 1642 }
rlm@1 1643
rlm@1 1644 void VBAMovieSetMetadata(const char *info)
rlm@1 1645 {
rlm@33 1646 if (!memcmp(Movie.authorInfo, info, MOVIE_METADATA_SIZE))
rlm@33 1647 return;
rlm@1 1648
rlm@33 1649 memcpy(Movie.authorInfo, info, MOVIE_METADATA_SIZE); // strncpy would omit post-0 bytes
rlm@33 1650 Movie.authorInfo[MOVIE_METADATA_SIZE - 1] = '\0';
rlm@1 1651
rlm@33 1652 if (Movie.file)
rlm@33 1653 {
rlm@33 1654 // (over-)write the header
rlm@33 1655 fseek(Movie.file, 0, SEEK_SET);
rlm@1 1656
rlm@33 1657 write_movie_header(Movie.file, Movie);
rlm@1 1658
rlm@33 1659 // write the metadata / author info to file
rlm@33 1660 fwrite(Movie.authorInfo, 1, sizeof(char) * MOVIE_METADATA_SIZE, Movie.file);
rlm@33 1661
rlm@33 1662 fflush(Movie.file);
rlm@33 1663 }
rlm@33 1664 printf("RLM: setMetadata called\n");
rlm@33 1665
rlm@1 1666 }
rlm@1 1667
rlm@1 1668 void VBAMovieRestart()
rlm@1 1669 {
rlm@33 1670 if (VBAMovieActive())
rlm@33 1671 {
rlm@33 1672 systemSoundClearBuffer();
rlm@1 1673
rlm@33 1674 bool8 modified = Movie.RecordedThisSession;
rlm@1 1675
rlm@33 1676 VBAMovieStop(true);
rlm@1 1677
rlm@33 1678 char movieName [_MAX_PATH];
rlm@33 1679 strncpy(movieName, Movie.filename, _MAX_PATH);
rlm@33 1680 movieName[_MAX_PATH - 1] = '\0';
rlm@33 1681 VBAMovieOpen(movieName, Movie.readOnly); // can't just pass in Movie.filename, since VBAMovieOpen clears out Movie's
rlm@33 1682 // variables
rlm@1 1683
rlm@33 1684 Movie.RecordedThisSession = modified;
rlm@1 1685
rlm@33 1686 systemScreenMessage("Movie replay (restart)");
rlm@33 1687 }
rlm@1 1688 }
rlm@1 1689
rlm@1 1690 int VBAMovieGetPauseAt()
rlm@1 1691 {
rlm@33 1692 return Movie.pauseFrame;
rlm@1 1693 }
rlm@1 1694
rlm@1 1695 void VBAMovieSetPauseAt(int at)
rlm@1 1696 {
rlm@33 1697 Movie.pauseFrame = at;
rlm@1 1698 }
rlm@1 1699
rlm@1 1700 ///////////////////////
rlm@1 1701 // movie tools
rlm@1 1702
rlm@1 1703 // FIXME: is it safe to convert/flush a movie while recording it (considering fseek() problem)?
rlm@1 1704 int VBAMovieConvertCurrent()
rlm@1 1705 {
rlm@33 1706 if (!VBAMovieActive())
rlm@33 1707 {
rlm@33 1708 return MOVIE_NOTHING;
rlm@33 1709 }
rlm@33 1710
rlm@33 1711 if (Movie.header.minorVersion > VBM_REVISION)
rlm@33 1712 {
rlm@33 1713 return MOVIE_WRONG_VERSION;
rlm@33 1714 }
rlm@33 1715
rlm@33 1716 if (Movie.header.minorVersion == VBM_REVISION)
rlm@33 1717 {
rlm@33 1718 return MOVIE_NOTHING;
rlm@33 1719 }
rlm@33 1720
rlm@33 1721 Movie.header.minorVersion = VBM_REVISION;
rlm@33 1722
rlm@33 1723 if (Movie.header.length_frames == 0) // this could happen
rlm@33 1724 {
rlm@33 1725 truncate_movie(0);
rlm@33 1726 return MOVIE_SUCCESS;
rlm@33 1727 }
rlm@33 1728
rlm@33 1729 // fix movies recorded from snapshots
rlm@33 1730 if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT)
rlm@33 1731 {
rlm@33 1732 uint8 *firstFramePtr = Movie.inputBuffer;
rlm@33 1733 for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i)
rlm@1 1734 {
rlm@33 1735 if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i))
rlm@33 1736 {
rlm@33 1737 Push16(initialInputs[i], firstFramePtr);
rlm@33 1738 // note: this is correct since Push16 advances the dest pointer by sizeof u16
rlm@33 1739 }
rlm@1 1740 }
rlm@33 1741 }
rlm@1 1742
rlm@33 1743 // convert old resets to new ones
rlm@33 1744 const u8 OLD_RESET = u8(BUTTON_MASK_OLD_RESET >> 8);
rlm@33 1745 const u8 NEW_RESET = u8(BUTTON_MASK_NEW_RESET >> 8);
rlm@33 1746 for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i)
rlm@33 1747 {
rlm@33 1748 if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i))
rlm@1 1749 {
rlm@33 1750 uint8 *startPtr = Movie.inputBuffer + sizeof(u16) * i + 1;
rlm@33 1751 uint8 *endPtr = Movie.inputBuffer + Movie.bytesPerFrame * (Movie.header.length_frames - 1);
rlm@33 1752 for (; startPtr < endPtr; startPtr += Movie.bytesPerFrame)
rlm@33 1753 {
rlm@33 1754 if (startPtr[Movie.bytesPerFrame] & OLD_RESET)
rlm@33 1755 {
rlm@33 1756 startPtr[0] |= NEW_RESET;
rlm@33 1757 }
rlm@33 1758 }
rlm@1 1759 }
rlm@33 1760 }
rlm@1 1761
rlm@33 1762 flush_movie_header();
rlm@33 1763 flush_movie_frames();
rlm@33 1764 return MOVIE_SUCCESS;
rlm@1 1765 }
rlm@1 1766
rlm@1 1767 bool VBAMovieTuncateAtCurrentFrame()
rlm@1 1768 {
rlm@33 1769 if (!VBAMovieActive())
rlm@33 1770 return false;
rlm@1 1771
rlm@33 1772 truncate_movie(Movie.currentFrame);
rlm@33 1773 change_state(MOVIE_STATE_END);
rlm@33 1774 systemScreenMessage("Movie truncated");
rlm@1 1775
rlm@33 1776 return true;
rlm@1 1777 }
rlm@1 1778
rlm@1 1779 bool VBAMovieFixHeader()
rlm@1 1780 {
rlm@33 1781 if (!VBAMovieActive())
rlm@33 1782 return false;
rlm@1 1783
rlm@33 1784 flush_movie_header();
rlm@33 1785 systemScreenMessage("Movie header fixed");
rlm@33 1786 return true;
rlm@1 1787 }