annotate src/common/movie.cpp @ 52:ee70875e0d3e

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