annotate src/common/movie.cpp @ 6:458a4f18f3cd

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