diff src/common/movie.cpp @ 19:5e8e5083da94

brought in common and gba, fixed problems with outdated Makefile.am files in both of these packages
author Robert McIntyre <rlm@mit.edu>
date Sun, 04 Mar 2012 14:33:52 -0600
parents f9f4f1b99eed
children 44974c3e093b
line wrap: on
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/common/movie.cpp	Sun Mar 04 14:33:52 2012 -0600
     1.3 @@ -0,0 +1,1772 @@
     1.4 +#include <cstdio>
     1.5 +#include <cctype>
     1.6 +#include <cstdlib>
     1.7 +#include <cstring>
     1.8 +#include <cassert>
     1.9 +#include <algorithm>
    1.10 +
    1.11 +using namespace std;
    1.12 +
    1.13 +#ifdef HAVE_STRINGS_H
    1.14 +#   include <strings.h>
    1.15 +#endif
    1.16 +
    1.17 +#if defined(__unix) || defined(__linux) || defined(__sun) || defined(__DJGPP)
    1.18 +#   include <unistd.h>
    1.19 +#   include <sys/types.h>
    1.20 +#   include <sys/stat.h>
    1.21 +#   include <climits>
    1.22 +#   define stricmp strcasecmp
    1.23 +// FIXME: this is wrong, but we don't want buffer overflow
    1.24 +#   if defined _MAX_PATH
    1.25 +#       undef _MAX_PATH
    1.26 +//#       define _MAX_PATH 128
    1.27 +#       define _MAX_PATH 260
    1.28 +#   endif
    1.29 +#endif
    1.30 +
    1.31 +#ifdef WIN32
    1.32 +#   include <io.h>
    1.33 +#   ifndef W_OK
    1.34 +#       define W_OK 2
    1.35 +#   endif
    1.36 +#   define ftruncate chsize
    1.37 +#endif
    1.38 +
    1.39 +#include "movie.h"
    1.40 +#include "System.h"
    1.41 +#include "../gba/GBA.h"
    1.42 +#include "../gba/GBAGlobals.h"
    1.43 +#include "../gba/RTC.h"
    1.44 +#include "../gb/GB.h"
    1.45 +#include "../gb/gbGlobals.h"
    1.46 +#include "inputGlobal.h"
    1.47 +#include "unzip.h"
    1.48 +#include "Util.h"
    1.49 +
    1.50 +#include "vbalua.h"
    1.51 +
    1.52 +#if (defined(WIN32) && !defined(SDL))
    1.53 +#   include "../win32/stdafx.h"
    1.54 +#   include "../win32/MainWnd.h"
    1.55 +#   include "../win32/VBA.h"
    1.56 +#   include "../win32/WinMiscUtil.h"
    1.57 +#endif
    1.58 +
    1.59 +extern int emulating; // from system.cpp
    1.60 +extern u16 currentButtons[4];     // from System.cpp
    1.61 +extern u16 lastKeys;
    1.62 +
    1.63 +SMovie Movie;
    1.64 +bool   loadingMovie = false;
    1.65 +
    1.66 +// probably bad idea to have so many global variables, but I hate to recompile almost everything after editing VBA.h
    1.67 +bool autoConvertMovieWhenPlaying = false;
    1.68 +
    1.69 +static u16 initialInputs[4] = { 0 };
    1.70 +
    1.71 +static bool resetSignaled	  = false;
    1.72 +static bool resetSignaledLast = false;
    1.73 +
    1.74 +static int prevEmulatorType, prevBorder, prevWinBorder, prevBorderAuto;
    1.75 +
    1.76 +// little-endian integer pop/push functions:
    1.77 +static inline uint32 Pop32(const uint8 * &ptr)
    1.78 +{
    1.79 +	uint32 v = (ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24));
    1.80 +	ptr += 4;
    1.81 +	return v;
    1.82 +}
    1.83 +
    1.84 +static inline uint16 Pop16(const uint8 * &ptr) /* const version */
    1.85 +{
    1.86 +	uint16 v = (ptr[0] | (ptr[1] << 8));
    1.87 +	ptr += 2;
    1.88 +	return v;
    1.89 +}
    1.90 +
    1.91 +static inline uint16 Pop16(uint8 * &ptr) /* non-const version */
    1.92 +{
    1.93 +	uint16 v = (ptr[0] | (ptr[1] << 8));
    1.94 +	ptr += 2;
    1.95 +	return v;
    1.96 +}
    1.97 +
    1.98 +static inline uint8 Pop8(const uint8 * &ptr)
    1.99 +{
   1.100 +	return *(ptr)++;
   1.101 +}
   1.102 +
   1.103 +static inline void Push32(uint32 v, uint8 * &ptr)
   1.104 +{
   1.105 +	ptr[0] = (uint8)(v & 0xff);
   1.106 +	ptr[1] = (uint8)((v >> 8) & 0xff);
   1.107 +	ptr[2] = (uint8)((v >> 16) & 0xff);
   1.108 +	ptr[3] = (uint8)((v >> 24) & 0xff);
   1.109 +	ptr	  += 4;
   1.110 +}
   1.111 +
   1.112 +static inline void Push16(uint16 v, uint8 * &ptr)
   1.113 +{
   1.114 +	ptr[0] = (uint8)(v & 0xff);
   1.115 +	ptr[1] = (uint8)((v >> 8) & 0xff);
   1.116 +	ptr	  += 2;
   1.117 +}
   1.118 +
   1.119 +static inline void Push8(uint8 v, uint8 * &ptr)
   1.120 +{
   1.121 +	*ptr++ = v;
   1.122 +}
   1.123 +
   1.124 +// little-endian integer read/write functions:
   1.125 +static inline uint16 Read16(const uint8 *ptr)
   1.126 +{
   1.127 +	return ptr[0] | (ptr[1] << 8);
   1.128 +}
   1.129 +
   1.130 +static inline void Write16(uint16 v, uint8 *ptr)
   1.131 +{
   1.132 +	ptr[0] = uint8(v & 0xff);
   1.133 +	ptr[1] = uint8((v >> 8) & 0xff);
   1.134 +}
   1.135 +
   1.136 +static long file_length(FILE *fp)
   1.137 +{
   1.138 +	long cur_pos = ftell(fp);
   1.139 +	fseek(fp, 0, SEEK_END);
   1.140 +	long length = ftell(fp);
   1.141 +	fseek(fp, cur_pos, SEEK_SET);
   1.142 +	return length;
   1.143 +}
   1.144 +
   1.145 +static int bytes_per_frame(SMovie &mov)
   1.146 +{
   1.147 +	int num_controllers = 0;
   1.148 +
   1.149 +	for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i)
   1.150 +		if (mov.header.controllerFlags & MOVIE_CONTROLLER(i))
   1.151 +			++num_controllers;
   1.152 +
   1.153 +	return CONTROLLER_DATA_SIZE * num_controllers;
   1.154 +}
   1.155 +
   1.156 +static void reserve_buffer_space(uint32 space_needed)
   1.157 +{
   1.158 +	if (space_needed > Movie.inputBufferSize)
   1.159 +	{
   1.160 +		uint32 ptr_offset	= Movie.inputBufferPtr - Movie.inputBuffer;
   1.161 +		uint32 alloc_chunks = (space_needed - 1) / BUFFER_GROWTH_SIZE + 1;
   1.162 +		uint32 old_size     = Movie.inputBufferSize;
   1.163 +		Movie.inputBufferSize = BUFFER_GROWTH_SIZE * alloc_chunks;
   1.164 +		Movie.inputBuffer	  = (uint8 *)realloc(Movie.inputBuffer, Movie.inputBufferSize);
   1.165 +		// FIXME: this only fixes the random input problem during dma-frame-skip, but not the skip
   1.166 +		memset(Movie.inputBuffer + old_size, 0, Movie.inputBufferSize - old_size);
   1.167 +		Movie.inputBufferPtr  = Movie.inputBuffer + ptr_offset;
   1.168 +	}
   1.169 +}
   1.170 +
   1.171 +static int read_movie_header(FILE *file, SMovie &movie)
   1.172 +{
   1.173 +	assert(file != NULL);
   1.174 +	assert(VBM_HEADER_SIZE == sizeof(SMovieFileHeader)); // sanity check on the header type definition
   1.175 +
   1.176 +	uint8 headerData [VBM_HEADER_SIZE];
   1.177 +
   1.178 +	if (fread(headerData, 1, VBM_HEADER_SIZE, file) != VBM_HEADER_SIZE)
   1.179 +		return MOVIE_WRONG_FORMAT;  // if we failed to read in all VBM_HEADER_SIZE bytes of the header
   1.180 +
   1.181 +	const uint8 *	  ptr	 = headerData;
   1.182 +	SMovieFileHeader &header = movie.header;
   1.183 +
   1.184 +	header.magic = Pop32(ptr);
   1.185 +	if (header.magic != VBM_MAGIC)
   1.186 +		return MOVIE_WRONG_FORMAT;
   1.187 +
   1.188 +	header.version = Pop32(ptr);
   1.189 +	if (header.version != VBM_VERSION)
   1.190 +		return MOVIE_WRONG_VERSION;
   1.191 +
   1.192 +	header.uid = Pop32(ptr);
   1.193 +	header.length_frames  = Pop32(ptr) + 1;    // HACK: add 1 to the length for compatibility
   1.194 +	header.rerecord_count = Pop32(ptr);
   1.195 +
   1.196 +	header.startFlags	   = Pop8(ptr);
   1.197 +	header.controllerFlags = Pop8(ptr);
   1.198 +	header.typeFlags	   = Pop8(ptr);
   1.199 +	header.optionFlags	   = Pop8(ptr);
   1.200 +
   1.201 +	header.saveType		  = Pop32(ptr);
   1.202 +	header.flashSize	  = Pop32(ptr);
   1.203 +	header.gbEmulatorType = Pop32(ptr);
   1.204 +
   1.205 +	for (int i = 0; i < 12; i++)
   1.206 +		header.romTitle[i] = Pop8(ptr);
   1.207 +
   1.208 +	header.minorVersion = Pop8(ptr);
   1.209 +
   1.210 +	header.romCRC = Pop8(ptr);
   1.211 +	header.romOrBiosChecksum = Pop16(ptr);
   1.212 +	header.romGameCode		 = Pop32(ptr);
   1.213 +
   1.214 +	header.offset_to_savestate		 = Pop32(ptr);
   1.215 +	header.offset_to_controller_data = Pop32(ptr);
   1.216 +
   1.217 +	return MOVIE_SUCCESS;
   1.218 +}
   1.219 +
   1.220 +static void write_movie_header(FILE *file, const SMovie &movie)
   1.221 +{
   1.222 +	assert(ftell(file) == 0); // we assume file points to beginning of movie file
   1.223 +
   1.224 +	uint8  headerData [VBM_HEADER_SIZE];
   1.225 +	uint8 *ptr = headerData;
   1.226 +	const SMovieFileHeader &header = movie.header;
   1.227 +
   1.228 +	Push32(header.magic, ptr);
   1.229 +	Push32(header.version, ptr);
   1.230 +
   1.231 +	Push32(header.uid, ptr);
   1.232 +	Push32(header.length_frames - 1, ptr);     // HACK: reduce the length by 1 for compatibility with certain faulty old tools
   1.233 +	                                           // like TME
   1.234 +	Push32(header.rerecord_count, ptr);
   1.235 +
   1.236 +	Push8(header.startFlags, ptr);
   1.237 +	Push8(header.controllerFlags, ptr);
   1.238 +	Push8(header.typeFlags, ptr);
   1.239 +	Push8(header.optionFlags, ptr);
   1.240 +
   1.241 +	Push32(header.saveType, ptr);
   1.242 +	Push32(header.flashSize, ptr);
   1.243 +	Push32(header.gbEmulatorType, ptr);
   1.244 +
   1.245 +	for (int i = 0; i < 12; ++i)
   1.246 +		Push8(header.romTitle[i], ptr);
   1.247 +
   1.248 +	Push8(header.minorVersion, ptr);
   1.249 +
   1.250 +	Push8(header.romCRC, ptr);
   1.251 +	Push16(header.romOrBiosChecksum, ptr);
   1.252 +	Push32(header.romGameCode, ptr);
   1.253 +
   1.254 +	Push32(header.offset_to_savestate, ptr);
   1.255 +	Push32(header.offset_to_controller_data, ptr);
   1.256 +
   1.257 +	fwrite(headerData, 1, VBM_HEADER_SIZE, file);
   1.258 +}
   1.259 +
   1.260 +static void flush_movie_header()
   1.261 +{
   1.262 +	assert(Movie.file != 0 && "logical error!");
   1.263 +	if (!Movie.file)
   1.264 +		return;
   1.265 +
   1.266 +	long originalPos = ftell(Movie.file);
   1.267 +
   1.268 +	// (over-)write the header
   1.269 +	fseek(Movie.file, 0, SEEK_SET);
   1.270 +	write_movie_header(Movie.file, Movie);
   1.271 +
   1.272 +	fflush(Movie.file);
   1.273 +
   1.274 +	fseek(Movie.file, originalPos, SEEK_SET);
   1.275 +}
   1.276 +
   1.277 +static void flush_movie_frames()
   1.278 +{
   1.279 +	assert(Movie.file && "logical error!");
   1.280 +	if (!Movie.file)
   1.281 +		return;
   1.282 +
   1.283 +	long originalPos = ftell(Movie.file);
   1.284 +
   1.285 +	// overwrite the controller data
   1.286 +	fseek(Movie.file, Movie.header.offset_to_controller_data, SEEK_SET);
   1.287 +	fwrite(Movie.inputBuffer, 1, Movie.bytesPerFrame * Movie.header.length_frames, Movie.file);
   1.288 +
   1.289 +	fflush(Movie.file);
   1.290 +
   1.291 +	fseek(Movie.file, originalPos, SEEK_SET);
   1.292 +}
   1.293 +
   1.294 +static void truncate_movie(long length)
   1.295 +{
   1.296 +	// truncate movie to length
   1.297 +	// NOTE: it's certain that the savestate block is never after the
   1.298 +	//       controller data block, because the VBM format decrees it.
   1.299 +
   1.300 +	assert(Movie.file && length >= 0);
   1.301 +	if (!Movie.file || length < 0)
   1.302 +		return;
   1.303 +
   1.304 +	assert(Movie.header.offset_to_savestate <= Movie.header.offset_to_controller_data);
   1.305 +	if (Movie.header.offset_to_savestate > Movie.header.offset_to_controller_data)
   1.306 +		return;
   1.307 +
   1.308 +	Movie.header.length_frames = length;
   1.309 +	flush_movie_header();
   1.310 +	const long truncLen = long(Movie.header.offset_to_controller_data + Movie.bytesPerFrame * length);
   1.311 +	if (file_length(Movie.file) != truncLen)
   1.312 +	{
   1.313 +		ftruncate(fileno(Movie.file), truncLen);
   1.314 +	}
   1.315 +}
   1.316 +
   1.317 +static void remember_input_state()
   1.318 +{
   1.319 +	for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i)
   1.320 +	{
   1.321 +		if (systemCartridgeType == 0)
   1.322 +		{
   1.323 +			initialInputs[i] = u16(~P1 & 0x03FF);
   1.324 +		}
   1.325 +		else
   1.326 +		{
   1.327 +			extern int32 gbJoymask[4];
   1.328 +			for (int i = 0; i < 4; ++i)
   1.329 +				initialInputs[i] = u16(gbJoymask[i] & 0xFFFF);
   1.330 +		}
   1.331 +	}
   1.332 +}
   1.333 +
   1.334 +static void change_state(MovieState new_state)
   1.335 +{
   1.336 +#if (defined(WIN32) && !defined(SDL))
   1.337 +	theApp.frameSearching	   = false;
   1.338 +	theApp.frameSearchSkipping = false;
   1.339 +#endif
   1.340 +
   1.341 +	if (new_state == MOVIE_STATE_NONE)
   1.342 +	{
   1.343 +		Movie.pauseFrame = -1;
   1.344 +
   1.345 +		if (Movie.state == MOVIE_STATE_NONE)
   1.346 +			return;
   1.347 +
   1.348 +		truncate_movie(Movie.header.length_frames);
   1.349 +
   1.350 +		fclose(Movie.file);
   1.351 +		Movie.file		   = NULL;
   1.352 +		Movie.currentFrame = 0;
   1.353 +#if (defined(WIN32) && !defined(SDL))
   1.354 +		// undo changes to border settings
   1.355 +		{
   1.356 +			gbBorderOn = prevBorder;
   1.357 +			theApp.winGbBorderOn = prevWinBorder;
   1.358 +			gbBorderAutomatic	 = prevBorderAuto;
   1.359 +			systemGbBorderOn();
   1.360 +		}
   1.361 +#endif
   1.362 +		gbEmulatorType = prevEmulatorType;
   1.363 +
   1.364 +		extern int32 gbDMASpeedVersion;
   1.365 +		gbDMASpeedVersion = 1;
   1.366 +
   1.367 +		extern int32 gbEchoRAMFixOn;
   1.368 +		gbEchoRAMFixOn = 1;
   1.369 +
   1.370 +		gbNullInputHackTempEnabled = gbNullInputHackEnabled;
   1.371 +
   1.372 +		if (Movie.inputBuffer)
   1.373 +		{
   1.374 +			free(Movie.inputBuffer);
   1.375 +			Movie.inputBuffer = NULL;
   1.376 +		}
   1.377 +	}
   1.378 +	else if (new_state == MOVIE_STATE_PLAY)
   1.379 +	{
   1.380 +		assert(Movie.file);
   1.381 +
   1.382 +		// this would cause problems if not dealt with
   1.383 +		if (Movie.currentFrame >= Movie.header.length_frames)
   1.384 +		{
   1.385 +			new_state = MOVIE_STATE_END;
   1.386 +			Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * Movie.header.length_frames;
   1.387 +		}
   1.388 +	}
   1.389 +	else if (new_state == MOVIE_STATE_RECORD)
   1.390 +	{
   1.391 +		assert(Movie.file);
   1.392 +
   1.393 +		// this would cause problems if not dealt with
   1.394 +		if (Movie.currentFrame > Movie.header.length_frames)
   1.395 +		{
   1.396 +			new_state = MOVIE_STATE_END;
   1.397 +			Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * Movie.header.length_frames;
   1.398 +		}
   1.399 +
   1.400 +		fseek(Movie.file, Movie.header.offset_to_controller_data + Movie.bytesPerFrame * Movie.currentFrame, SEEK_SET);
   1.401 +	}
   1.402 +
   1.403 +	if (new_state == MOVIE_STATE_END && Movie.state != MOVIE_STATE_END)
   1.404 +	{
   1.405 +#if defined(SDL)		
   1.406 +		systemClearJoypads();
   1.407 +#endif
   1.408 +		systemScreenMessage("Movie end");
   1.409 +	}
   1.410 +
   1.411 +	Movie.state = new_state;
   1.412 +
   1.413 +	// checking for movie end
   1.414 +	bool willPause = false;
   1.415 +
   1.416 +	// if the movie's been set to pause at a certain frame
   1.417 +	if (Movie.state != MOVIE_STATE_NONE && Movie.pauseFrame >= 0 && Movie.currentFrame == (uint32)Movie.pauseFrame)
   1.418 +	{
   1.419 +		Movie.pauseFrame = -1;
   1.420 +		willPause		 = true;
   1.421 +	}
   1.422 +
   1.423 +	if (Movie.state == MOVIE_STATE_END)
   1.424 +	{
   1.425 +		if (Movie.currentFrame == Movie.header.length_frames)
   1.426 +		{
   1.427 +#if (defined(WIN32) && !defined(SDL))
   1.428 +			if (theApp.movieOnEndPause)
   1.429 +			{
   1.430 +				willPause = true;
   1.431 +			}
   1.432 +#else
   1.433 +			// SDL FIXME
   1.434 +#endif
   1.435 +
   1.436 +#if (defined(WIN32) && !defined(SDL))
   1.437 +			switch (theApp.movieOnEndBehavior)
   1.438 +			{
   1.439 +			case 1:
   1.440 +				// the old behavior
   1.441 +				//VBAMovieRestart();
   1.442 +				break;
   1.443 +			case 2:
   1.444 +#else
   1.445 +			// SDL FIXME
   1.446 +#endif
   1.447 +				if (Movie.RecordedThisSession)
   1.448 +				{
   1.449 +					// if user has been recording this movie since the last time it started playing,
   1.450 +					// they probably don't want the movie to end now during playback,
   1.451 +					// so switch back to recording when it reaches the end
   1.452 +					VBAMovieSwitchToRecording();
   1.453 +					systemScreenMessage("Recording resumed");
   1.454 +					willPause = true;
   1.455 +				}
   1.456 +#if (defined(WIN32) && !defined(SDL))
   1.457 +				break;
   1.458 +			case 3:
   1.459 +				// keep open
   1.460 +				break;
   1.461 +			case 0:
   1.462 +				// fall through
   1.463 +			default:
   1.464 +				// close movie
   1.465 +				//VBAMovieStop(false);
   1.466 +				break;
   1.467 +			}
   1.468 +#else
   1.469 +				// SDL FIXME
   1.470 +#endif
   1.471 +		}
   1.472 +#if 1
   1.473 +		else if (Movie.currentFrame > Movie.header.length_frames)
   1.474 +		{
   1.475 +#if (defined(WIN32) && !defined(SDL))
   1.476 +			switch (theApp.movieOnEndBehavior)
   1.477 +			{
   1.478 +			case 1:
   1.479 +				// FIXME: this should be delayed till the current frame ends
   1.480 +				VBAMovieRestart();
   1.481 +				break;
   1.482 +			case 2:
   1.483 +				// nothing
   1.484 +				break;
   1.485 +			case 3:
   1.486 +				// keep open
   1.487 +				break;
   1.488 +			case 0:
   1.489 +				// fall through
   1.490 +			default:
   1.491 +				// close movie
   1.492 +				VBAMovieStop(false);
   1.493 +				break;
   1.494 +			}
   1.495 +#else
   1.496 +			// SDLFIXME
   1.497 +#endif
   1.498 +		}
   1.499 +#endif
   1.500 +	} // end if (Movie.state == MOVIE_STATE_END)
   1.501 +
   1.502 +	if (willPause)
   1.503 +	{
   1.504 +		systemSetPause(true);
   1.505 +	}
   1.506 +}
   1.507 +
   1.508 +void VBAMovieInit()
   1.509 +{
   1.510 +	memset(&Movie, 0, sizeof(Movie));
   1.511 +	Movie.state		 = MOVIE_STATE_NONE;
   1.512 +	Movie.pauseFrame = -1;
   1.513 +
   1.514 +	resetSignaled	  = false;
   1.515 +	resetSignaledLast = false;
   1.516 +}
   1.517 +
   1.518 +void VBAMovieGetRomInfo(const SMovie &movieInfo, char romTitle [12], uint32 &romGameCode, uint16 &checksum, uint8 &crc)
   1.519 +{
   1.520 +	if (systemCartridgeType == 0) // GBA
   1.521 +	{
   1.522 +		extern u8 *bios, *rom;
   1.523 +		memcpy(romTitle, &rom[0xa0], 12); // GBA TITLE
   1.524 +		memcpy(&romGameCode, &rom[0xac], 4); // GBA ROM GAME CODE
   1.525 +		if ((movieInfo.header.optionFlags & MOVIE_SETTING_USEBIOSFILE) != 0)
   1.526 +			checksum = utilCalcBIOSChecksum(bios, 4);  // GBA BIOS CHECKSUM
   1.527 +		else
   1.528 +			checksum = 0;
   1.529 +		crc = rom[0xbd]; // GBA ROM CRC
   1.530 +	}
   1.531 +	else // non-GBA
   1.532 +	{
   1.533 +		extern u8 *gbRom;
   1.534 +		memcpy(romTitle, &gbRom[0x134], 12); // GB TITLE (note this can be 15 but is truncated to 12)
   1.535 +		romGameCode = (uint32)gbRom[0x146]; // GB ROM UNIT CODE
   1.536 +
   1.537 +		checksum = (gbRom[0x14e] << 8) | gbRom[0x14f]; // GB ROM CHECKSUM, read from big-endian
   1.538 +		crc		 = gbRom[0x14d]; // GB ROM CRC
   1.539 +	}
   1.540 +}
   1.541 +
   1.542 +#ifdef SDL
   1.543 +static void GetBatterySaveName(char *buffer)
   1.544 +{
   1.545 +	extern char batteryDir[2048], filename[2048];     // from SDL.cpp
   1.546 +	extern char *sdlGetFilename(char *name);     // from SDL.cpp
   1.547 +	if (batteryDir[0])
   1.548 +		sprintf(buffer, "%s/%s.sav", batteryDir, sdlGetFilename(filename));
   1.549 +	else
   1.550 +		sprintf(buffer, "%s.sav", filename);
   1.551 +}
   1.552 +
   1.553 +#endif
   1.554 +
   1.555 +static void SetPlayEmuSettings()
   1.556 +{
   1.557 +	prevEmulatorType = gbEmulatorType;
   1.558 +	gbEmulatorType	 = Movie.header.gbEmulatorType;
   1.559 +
   1.560 +#if (defined(WIN32) && !defined(SDL))
   1.561 +//    theApp.removeIntros   = false;
   1.562 +	theApp.skipBiosFile = (Movie.header.optionFlags & MOVIE_SETTING_SKIPBIOSFILE) != 0;
   1.563 +	theApp.useBiosFile	= (Movie.header.optionFlags & MOVIE_SETTING_USEBIOSFILE) != 0;
   1.564 +#else
   1.565 +	extern int	 saveType, sdlRtcEnable, sdlFlashSize;   // from SDL.cpp
   1.566 +	extern bool8 useBios, skipBios, removeIntros;     // from SDL.cpp
   1.567 +	useBios		 = (Movie.header.optionFlags & MOVIE_SETTING_USEBIOSFILE) != 0;
   1.568 +	skipBios	 = (Movie.header.optionFlags & MOVIE_SETTING_SKIPBIOSFILE) != 0;
   1.569 +	removeIntros = false /*(Movie.header.optionFlags & MOVIE_SETTING_REMOVEINTROS) != 0*/;
   1.570 +#endif
   1.571 +
   1.572 +	extern void SetPrefetchHack(bool);
   1.573 +	if (systemCartridgeType == 0)    // lag disablement applies only to GBA
   1.574 +		SetPrefetchHack((Movie.header.optionFlags & MOVIE_SETTING_LAGHACK) != 0);
   1.575 +
   1.576 +	gbNullInputHackTempEnabled = ((Movie.header.optionFlags & MOVIE_SETTING_GBINPUTHACK) != 0);
   1.577 +
   1.578 +	// some GB/GBC games depend on the sound rate, so just use the highest one
   1.579 +	systemSoundSetQuality(1);
   1.580 +	useOldFrameTiming = false;
   1.581 +
   1.582 +	extern int32 gbDMASpeedVersion;
   1.583 +	if ((Movie.header.optionFlags & MOVIE_SETTING_GBCFF55FIX) != 0)
   1.584 +		gbDMASpeedVersion = 1;
   1.585 +	else
   1.586 +		gbDMASpeedVersion = 0;     // old CGB HDMA5 timing was used
   1.587 +
   1.588 +	extern int32 gbEchoRAMFixOn;
   1.589 +	if ((Movie.header.optionFlags & MOVIE_SETTING_GBECHORAMFIX) != 0)
   1.590 +		gbEchoRAMFixOn = 1;
   1.591 +	else
   1.592 +		gbEchoRAMFixOn = 0;
   1.593 +
   1.594 +#if (defined(WIN32) && !defined(SDL))
   1.595 +	rtcEnable((Movie.header.optionFlags & MOVIE_SETTING_RTCENABLE) != 0);
   1.596 +	theApp.winSaveType	= Movie.header.saveType;
   1.597 +	theApp.winFlashSize = Movie.header.flashSize;
   1.598 +
   1.599 +	prevBorder	   = gbBorderOn;
   1.600 +	prevWinBorder  = theApp.winGbBorderOn;
   1.601 +	prevBorderAuto = gbBorderAutomatic;
   1.602 +	if ((gbEmulatorType == 2 || gbEmulatorType == 5)
   1.603 +	    && !theApp.hideMovieBorder) // games played in SGB mode can have a border
   1.604 +	{
   1.605 +		gbBorderOn = true;
   1.606 +		theApp.winGbBorderOn = true;
   1.607 +		gbBorderAutomatic	 = false;
   1.608 +	}
   1.609 +	else
   1.610 +	{
   1.611 +		gbBorderOn = false;
   1.612 +		theApp.winGbBorderOn = false;
   1.613 +		gbBorderAutomatic	 = false;
   1.614 +		if (theApp.hideMovieBorder)
   1.615 +		{
   1.616 +			theApp.hideMovieBorder = false;
   1.617 +			prevBorder = false;     // it might be expected behaviour that it stays hidden after the movie
   1.618 +		}
   1.619 +	}
   1.620 +	systemGbBorderOn();
   1.621 +#else
   1.622 +	sdlRtcEnable = (Movie.header.optionFlags & MOVIE_SETTING_RTCENABLE) != 0;
   1.623 +	saveType	 = Movie.header.saveType;
   1.624 +	sdlFlashSize = Movie.header.flashSize;
   1.625 +#endif
   1.626 +}
   1.627 +
   1.628 +static void HardResetAndSRAMClear()
   1.629 +{
   1.630 +#if (defined(WIN32) && !defined(SDL))
   1.631 +	winEraseBatteryFile(); // delete the damn SRAM file and keep it from being resurrected from RAM
   1.632 +	MainWnd *temp = ((MainWnd *)theApp.m_pMainWnd);
   1.633 +	if (!temp->winFileRun(true)) // restart running the game
   1.634 +	{
   1.635 +		temp->winFileClose();
   1.636 +	}
   1.637 +#else
   1.638 +	char fname [1024];
   1.639 +	GetBatterySaveName(fname);
   1.640 +	remove(fname);     // delete the damn SRAM file
   1.641 +
   1.642 +	// Henceforth, emuCleanUp means "clear out SRAM"
   1.643 +	//theEmulator.emuCleanUp();     // keep it from being resurrected from RAM <--This is wrong, it'll deallocate all variables --Felipe
   1.644 +
   1.645 +	/// FIXME the correct SDL code to call for a full restart isn't in a function yet
   1.646 +	theEmulator.emuReset(false);
   1.647 +#endif
   1.648 +}
   1.649 +
   1.650 +int VBAMovieOpen(const char *filename, bool8 read_only)
   1.651 +{
   1.652 +	loadingMovie = true;
   1.653 +	uint8 movieReadOnly = read_only ? 1 : 0;
   1.654 +
   1.655 +	FILE * file;
   1.656 +	STREAM stream;
   1.657 +	int	   result;
   1.658 +	int	   fn;
   1.659 +
   1.660 +	char movie_filename[_MAX_PATH];
   1.661 +#ifdef WIN32
   1.662 +	_fullpath(movie_filename, filename, _MAX_PATH);
   1.663 +#else
   1.664 +	// SDL FIXME: convert to fullpath
   1.665 +	strncpy(movie_filename, filename, _MAX_PATH);
   1.666 +	movie_filename[_MAX_PATH - 1] = '\0';
   1.667 +#endif
   1.668 +
   1.669 +	if (movie_filename[0] == '\0')
   1.670 +	{ loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }
   1.671 +
   1.672 +	if (!emulating)
   1.673 +	{ loadingMovie = false; return MOVIE_UNKNOWN_ERROR; }
   1.674 +
   1.675 +//	bool alreadyOpen = (Movie.file != NULL && _stricmp(movie_filename, Movie.filename) == 0);
   1.676 +
   1.677 +//	if (alreadyOpen)
   1.678 +	change_state(MOVIE_STATE_NONE);     // have to stop current movie before trying to re-open it
   1.679 +
   1.680 +	if (!(file = fopen(movie_filename, "rb+")))
   1.681 +		if (!(file = fopen(movie_filename, "rb")))
   1.682 +		{ loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }
   1.683 +	//else
   1.684 +	//	movieReadOnly = 2; // we have to open the movie twice, no need to do this both times
   1.685 +
   1.686 +//	if (!alreadyOpen)
   1.687 +//		change_state(MOVIE_STATE_NONE); // stop current movie when we're able to open the other one
   1.688 +//
   1.689 +//	if (!(file = fopen(movie_filename, "rb+")))
   1.690 +//		if(!(file = fopen(movie_filename, "rb")))
   1.691 +//			{loadingMovie = false; return MOVIE_FILE_NOT_FOUND;}
   1.692 +//		else
   1.693 +//			movieReadOnly = 2;
   1.694 +
   1.695 +	// clear out the current movie
   1.696 +	VBAMovieInit();
   1.697 +
   1.698 +	// read header
   1.699 +	if ((result = read_movie_header(file, Movie)) != MOVIE_SUCCESS)
   1.700 +	{
   1.701 +		fclose(file);
   1.702 +		{ loadingMovie = false; return result; }
   1.703 +	}
   1.704 +
   1.705 +	// set emulator settings that make the movie more likely to stay synchronized
   1.706 +	SetPlayEmuSettings();
   1.707 +
   1.708 +//	extern bool systemLoadBIOS();
   1.709 +//	if (!systemLoadBIOS())
   1.710 +//	{ loadingMovie = false; return MOVIE_UNKNOWN_ERROR; }
   1.711 +
   1.712 +	// read the metadata / author info from file
   1.713 +	fread(Movie.authorInfo, 1, MOVIE_METADATA_SIZE, file);
   1.714 +	fn = dup(fileno(file)); // XXX: why does this fail?? it returns -1 but errno == 0
   1.715 +	fclose(file);
   1.716 +
   1.717 +	// apparently this lseek is necessary
   1.718 +	lseek(fn, Movie.header.offset_to_savestate, SEEK_SET);
   1.719 +	if (!(stream = utilGzReopen(fn, "rb")))
   1.720 +		if (!(stream = utilGzOpen(movie_filename, "rb")))
   1.721 +		{ loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }
   1.722 +		else
   1.723 +			fn = dup(fileno(file));
   1.724 +	// in case the above dup failed but opening the file normally doesn't fail
   1.725 +
   1.726 +	if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT)
   1.727 +	{
   1.728 +		// load the snapshot
   1.729 +		result = theEmulator.emuReadStateFromStream(stream) ? MOVIE_SUCCESS : MOVIE_WRONG_FORMAT;
   1.730 +
   1.731 +		// FIXME: Kludge for conversion
   1.732 +		remember_input_state();
   1.733 +	}
   1.734 +	else if (Movie.header.startFlags & MOVIE_START_FROM_SRAM)
   1.735 +	{
   1.736 +		// 'soft' reset:
   1.737 +		theEmulator.emuReset(false);
   1.738 +
   1.739 +		// load the SRAM
   1.740 +		result = theEmulator.emuReadBatteryFromStream(stream) ? MOVIE_SUCCESS : MOVIE_WRONG_FORMAT;
   1.741 +	}
   1.742 +	else
   1.743 +	{
   1.744 +		HardResetAndSRAMClear();
   1.745 +	}
   1.746 +
   1.747 +	utilGzClose(stream);
   1.748 +
   1.749 +	if (result != MOVIE_SUCCESS)
   1.750 +	{ loadingMovie = false; return result; }
   1.751 +
   1.752 +//	if (!(file = fopen(movie_filename, /*read_only ? "rb" :*/ "rb+"))) // want to be able to switch out of read-only later
   1.753 +//	{
   1.754 +//		if(!Movie.readOnly || !(file = fopen(movie_filename, "rb"))) // try read-only if failed
   1.755 +//			return MOVIE_FILE_NOT_FOUND;
   1.756 +//	}
   1.757 +	if (!(file = fopen(movie_filename, "rb+")))
   1.758 +		if (!(file = fopen(movie_filename, "rb")))
   1.759 +		{ loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }
   1.760 +		else
   1.761 +			movieReadOnly = 2;
   1.762 +
   1.763 +	// recalculate length of movie from the file size
   1.764 +	Movie.bytesPerFrame = bytes_per_frame(Movie);
   1.765 +	fseek(file, 0, SEEK_END);
   1.766 +	long fileSize = ftell(file);
   1.767 +	Movie.header.length_frames = (fileSize - Movie.header.offset_to_controller_data) / Movie.bytesPerFrame;
   1.768 +
   1.769 +	if (fseek(file, Movie.header.offset_to_controller_data, SEEK_SET))
   1.770 +	{ fclose(file); loadingMovie = false; return MOVIE_WRONG_FORMAT; }
   1.771 +
   1.772 +	strcpy(Movie.filename, movie_filename);
   1.773 +	Movie.file = file;
   1.774 +	Movie.inputBufferPtr	  = Movie.inputBuffer;
   1.775 +	Movie.currentFrame		  = 0;
   1.776 +	Movie.readOnly			  = movieReadOnly;
   1.777 +	Movie.RecordedThisSession = false;
   1.778 +
   1.779 +	// read controller data
   1.780 +	uint32 to_read = Movie.bytesPerFrame * Movie.header.length_frames;
   1.781 +	reserve_buffer_space(to_read);
   1.782 +	fread(Movie.inputBuffer, 1, to_read, file);
   1.783 +
   1.784 +	change_state(MOVIE_STATE_PLAY);
   1.785 +
   1.786 +	char messageString[64] = "Movie ";
   1.787 +	bool converted		   = false;
   1.788 +	if (autoConvertMovieWhenPlaying)
   1.789 +	{
   1.790 +		int result = VBAMovieConvertCurrent();
   1.791 +		if (result == MOVIE_SUCCESS)
   1.792 +			strcat(messageString, "converted and ");
   1.793 +		else if (result == MOVIE_WRONG_VERSION)
   1.794 +			strcat(messageString, "higher revision ");
   1.795 +	}
   1.796 +
   1.797 +	if (Movie.state == MOVIE_STATE_PLAY)
   1.798 +		strcat(messageString, "replaying ");
   1.799 +	else
   1.800 +		strcat(messageString, "finished ");
   1.801 +	if (Movie.readOnly)
   1.802 +		strcat(messageString, "(read)");
   1.803 +	else
   1.804 +		strcat(messageString, "(edit)");
   1.805 +	systemScreenMessage(messageString);
   1.806 +
   1.807 +	VBAUpdateButtonPressDisplay();
   1.808 +	VBAUpdateFrameCountDisplay();
   1.809 +	systemRefreshScreen();
   1.810 +
   1.811 +	{ loadingMovie = false; return MOVIE_SUCCESS; }
   1.812 +}
   1.813 +
   1.814 +static void SetRecordEmuSettings()
   1.815 +{
   1.816 +	Movie.header.optionFlags = 0;
   1.817 +#if (defined(WIN32) && !defined(SDL))
   1.818 +	if (theApp.useBiosFile)
   1.819 +		Movie.header.optionFlags |= MOVIE_SETTING_USEBIOSFILE;
   1.820 +	if (theApp.skipBiosFile)
   1.821 +		Movie.header.optionFlags |= MOVIE_SETTING_SKIPBIOSFILE;
   1.822 +	if (rtcIsEnabled())
   1.823 +		Movie.header.optionFlags |= MOVIE_SETTING_RTCENABLE;
   1.824 +	Movie.header.saveType  = theApp.winSaveType;
   1.825 +	Movie.header.flashSize = theApp.winFlashSize;
   1.826 +#else
   1.827 +	extern int	 saveType, sdlRtcEnable, sdlFlashSize;   // from SDL.cpp
   1.828 +	extern bool8 useBios, skipBios;     // from SDL.cpp
   1.829 +	if (useBios)
   1.830 +		Movie.header.optionFlags |= MOVIE_SETTING_USEBIOSFILE;
   1.831 +	if (skipBios)
   1.832 +		Movie.header.optionFlags |= MOVIE_SETTING_SKIPBIOSFILE;
   1.833 +	if (sdlRtcEnable)
   1.834 +		Movie.header.optionFlags |= MOVIE_SETTING_RTCENABLE;
   1.835 +	Movie.header.saveType  = saveType;
   1.836 +	Movie.header.flashSize = sdlFlashSize;
   1.837 +#endif
   1.838 +	prevEmulatorType = Movie.header.gbEmulatorType = gbEmulatorType;
   1.839 +
   1.840 +	if (!memLagTempEnabled)
   1.841 +		Movie.header.optionFlags |= MOVIE_SETTING_LAGHACK;
   1.842 +
   1.843 +	if (gbNullInputHackTempEnabled)
   1.844 +		Movie.header.optionFlags |= MOVIE_SETTING_GBINPUTHACK;
   1.845 +
   1.846 +	Movie.header.optionFlags |= MOVIE_SETTING_GBCFF55FIX;
   1.847 +	extern int32 gbDMASpeedVersion;
   1.848 +	gbDMASpeedVersion = 1;
   1.849 +
   1.850 +	Movie.header.optionFlags |= MOVIE_SETTING_GBECHORAMFIX;
   1.851 +	extern int32 gbEchoRAMFixOn;
   1.852 +	gbEchoRAMFixOn = 1;
   1.853 +
   1.854 +	// some GB/GBC games depend on the sound rate, so just use the highest one
   1.855 +	systemSoundSetQuality(1);
   1.856 +
   1.857 +	useOldFrameTiming = false;
   1.858 +
   1.859 +#if (defined(WIN32) && !defined(SDL))
   1.860 +//    theApp.removeIntros   = false;
   1.861 +
   1.862 +	prevBorder	   = gbBorderOn;
   1.863 +	prevWinBorder  = theApp.winGbBorderOn;
   1.864 +	prevBorderAuto = gbBorderAutomatic;
   1.865 +	if (gbEmulatorType == 2 || gbEmulatorType == 5)     // only games played in SGB mode will have a border
   1.866 +	{
   1.867 +		gbBorderOn = true;
   1.868 +		theApp.winGbBorderOn = true;
   1.869 +		gbBorderAutomatic	 = false;
   1.870 +	}
   1.871 +	else
   1.872 +	{
   1.873 +		gbBorderOn = false;
   1.874 +		theApp.winGbBorderOn = false;
   1.875 +		gbBorderAutomatic	 = false;
   1.876 +	}
   1.877 +	systemGbBorderOn();
   1.878 +#else
   1.879 +	/// SDLFIXME
   1.880 +#endif
   1.881 +}
   1.882 +
   1.883 +uint16 VBAMovieGetCurrentInputOf(int controllerNum, bool normalOnly)
   1.884 +{
   1.885 +	if (controllerNum < 0 || controllerNum >= MOVIE_NUM_OF_POSSIBLE_CONTROLLERS)
   1.886 +		return 0;
   1.887 +
   1.888 +	return normalOnly ? (currentButtons[controllerNum] & BUTTON_REGULAR_MASK) : currentButtons[controllerNum];
   1.889 +}
   1.890 +
   1.891 +int VBAMovieCreate(const char *filename, const char *authorInfo, uint8 startFlags, uint8 controllerFlags, uint8 typeFlags)
   1.892 +{
   1.893 +	// make sure at least one controller is enabled
   1.894 +	if ((controllerFlags & MOVIE_CONTROLLERS_ANY_MASK) == 0)
   1.895 +		return MOVIE_WRONG_FORMAT;
   1.896 +
   1.897 +	if (!emulating)
   1.898 +		return MOVIE_UNKNOWN_ERROR;
   1.899 +
   1.900 +	loadingMovie = true;
   1.901 +
   1.902 +	FILE * file;
   1.903 +	STREAM stream;
   1.904 +	int	   fn;
   1.905 +
   1.906 +	char movie_filename [_MAX_PATH];
   1.907 +#ifdef WIN32
   1.908 +	_fullpath(movie_filename, filename, _MAX_PATH);
   1.909 +#else
   1.910 +	// FIXME: convert to fullpath
   1.911 +	strncpy(movie_filename, filename, _MAX_PATH);
   1.912 +	movie_filename[_MAX_PATH - 1] = '\0';
   1.913 +#endif
   1.914 +
   1.915 +	bool alreadyOpen = (Movie.file != NULL && stricmp(movie_filename, Movie.filename) == 0);
   1.916 +
   1.917 +	if (alreadyOpen)
   1.918 +		change_state(MOVIE_STATE_NONE);  // have to stop current movie before trying to re-open it
   1.919 +
   1.920 +	if (movie_filename[0] == '\0')
   1.921 +	{ loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }
   1.922 +
   1.923 +	if (!(file = fopen(movie_filename, "wb")))
   1.924 +	{ loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }
   1.925 +
   1.926 +	if (!alreadyOpen)
   1.927 +		change_state(MOVIE_STATE_NONE);  // stop current movie when we're able to open the other one
   1.928 +
   1.929 +	// clear out the current movie
   1.930 +	VBAMovieInit();
   1.931 +
   1.932 +	// fill in the movie's header
   1.933 +	Movie.header.uid   = (uint32)time(NULL);
   1.934 +	Movie.header.magic = VBM_MAGIC;
   1.935 +	Movie.header.version		 = VBM_VERSION;
   1.936 +	Movie.header.rerecord_count	 = 0;
   1.937 +	Movie.header.length_frames	 = 0;
   1.938 +	Movie.header.startFlags		 = startFlags;
   1.939 +	Movie.header.controllerFlags = controllerFlags;
   1.940 +	Movie.header.typeFlags		 = typeFlags;
   1.941 +	Movie.header.minorVersion	 = VBM_REVISION;
   1.942 +
   1.943 +	// set emulator settings that make the movie more likely to stay synchronized when it's later played back
   1.944 +	SetRecordEmuSettings();
   1.945 +
   1.946 +	// set ROM and BIOS checksums and stuff
   1.947 +	VBAMovieGetRomInfo(Movie, Movie.header.romTitle, Movie.header.romGameCode, Movie.header.romOrBiosChecksum, Movie.header.romCRC);
   1.948 +
   1.949 +	// write the header to file
   1.950 +	write_movie_header(file, Movie);
   1.951 +
   1.952 +	// copy over the metadata / author info
   1.953 +	VBAMovieSetMetadata(authorInfo);
   1.954 +
   1.955 +	// write the metadata / author info to file
   1.956 +	fwrite(Movie.authorInfo, 1, sizeof(char) * MOVIE_METADATA_SIZE, file);
   1.957 +
   1.958 +	// write snapshot or SRAM if applicable
   1.959 +	if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT
   1.960 +	    || Movie.header.startFlags & MOVIE_START_FROM_SRAM)
   1.961 +	{
   1.962 +		Movie.header.offset_to_savestate = (uint32)ftell(file);
   1.963 +
   1.964 +		// close the file and reopen it as a stream:
   1.965 +
   1.966 +		fn = dup(fileno(file));
   1.967 +		fclose(file);
   1.968 +
   1.969 +		if (!(stream = utilGzReopen(fn, "ab"))) // append mode to start at end, no seek necessary
   1.970 +		{ loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }
   1.971 +
   1.972 +		// write the save data:
   1.973 +		if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT)
   1.974 +		{
   1.975 +			// save snapshot
   1.976 +			if (!theEmulator.emuWriteStateToStream(stream))
   1.977 +			{
   1.978 +				utilGzClose(stream);
   1.979 +				{ loadingMovie = false; return MOVIE_UNKNOWN_ERROR; }
   1.980 +			}
   1.981 +		}
   1.982 +		else if (Movie.header.startFlags & MOVIE_START_FROM_SRAM)
   1.983 +		{
   1.984 +			// save SRAM
   1.985 +			if (!theEmulator.emuWriteBatteryToStream(stream))
   1.986 +			{
   1.987 +				utilGzClose(stream);
   1.988 +				{ loadingMovie = false; return MOVIE_UNKNOWN_ERROR; }
   1.989 +			}
   1.990 +
   1.991 +			// 'soft' reset:
   1.992 +			theEmulator.emuReset(false);
   1.993 +		}
   1.994 +
   1.995 +		utilGzClose(stream);
   1.996 +
   1.997 +		// reopen the file and seek back to the end
   1.998 +
   1.999 +		if (!(file = fopen(movie_filename, "rb+")))
  1.1000 +		{ loadingMovie = false; return MOVIE_FILE_NOT_FOUND; }
  1.1001 +
  1.1002 +		fseek(file, 0, SEEK_END);
  1.1003 +	}
  1.1004 +	else // no snapshot or SRAM
  1.1005 +	{
  1.1006 +		HardResetAndSRAMClear();
  1.1007 +	}
  1.1008 +
  1.1009 +	Movie.header.offset_to_controller_data = (uint32)ftell(file);
  1.1010 +
  1.1011 +	strcpy(Movie.filename, movie_filename);
  1.1012 +	Movie.file = file;
  1.1013 +	Movie.bytesPerFrame		  = bytes_per_frame(Movie);
  1.1014 +	Movie.inputBufferPtr	  = Movie.inputBuffer;
  1.1015 +	Movie.currentFrame		  = 0;
  1.1016 +	Movie.readOnly			  = false;
  1.1017 +	Movie.RecordedThisSession = true;
  1.1018 +
  1.1019 +	change_state(MOVIE_STATE_RECORD);
  1.1020 +
  1.1021 +	systemScreenMessage("Recording movie...");
  1.1022 +	{ loadingMovie = false; return MOVIE_SUCCESS; }
  1.1023 +}
  1.1024 +
  1.1025 +void VBAUpdateButtonPressDisplay()
  1.1026 +{
  1.1027 +	uint32 keys = currentButtons[0] & BUTTON_REGULAR_RECORDING_MASK;
  1.1028 +
  1.1029 +	const static char KeyMap[]	 =  { 'A', 'B', 's', 'S', '>', '<', '^', 'v', 'R', 'L', '!', '?', '{', '}', 'v', '^' };
  1.1030 +	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  { = } _
  1.1031 +	                                                                                         // ? !
  1.1032 +	char buffer[256];
  1.1033 +	sprintf(buffer, "                    ");
  1.1034 +
  1.1035 +#ifndef WIN32
  1.1036 +	// don't bother color-coding autofire and such
  1.1037 +	int i;
  1.1038 +	for (i = 0; i < 15; i++)
  1.1039 +	{
  1.1040 +		int j	 = KeyOrder[i];
  1.1041 +		int mask = (1 << (j));
  1.1042 +		buffer[strlen("    ") + i] = ((keys & mask) != 0) ? KeyMap[j] : ' ';
  1.1043 +	}
  1.1044 +
  1.1045 +	systemScreenMessage(buffer, 2, -1);
  1.1046 +#else
  1.1047 +	const bool eraseAll		= !theApp.inputDisplay;
  1.1048 +	uint32	   autoHeldKeys = eraseAll ? 0 : theApp.autoHold & BUTTON_REGULAR_RECORDING_MASK;
  1.1049 +	uint32	   autoFireKeys = eraseAll ? 0 : (theApp.autoFire | theApp.autoFire2) & BUTTON_REGULAR_RECORDING_MASK;
  1.1050 +	uint32	   pressedKeys	= eraseAll ? 0 : keys;
  1.1051 +
  1.1052 +	char colorList[64];
  1.1053 +	memset(colorList, 1, strlen(buffer));
  1.1054 +
  1.1055 +	if (!eraseAll)
  1.1056 +	{
  1.1057 +		for (int i = 0; i < 15; i++)
  1.1058 +		{
  1.1059 +			const int  j		 = KeyOrder[i];
  1.1060 +			const int  mask		 = (1 << (j));
  1.1061 +			bool	   pressed	 = (pressedKeys  & mask) != 0;
  1.1062 +			const bool autoHeld	 = (autoHeldKeys & mask) != 0;
  1.1063 +			const bool autoFired = (autoFireKeys & mask) != 0;
  1.1064 +			const bool erased	 = (lastKeys & mask) != 0 && (!pressed && !autoHeld && !autoFired);
  1.1065 +			extern int textMethod;
  1.1066 +			if (textMethod != 2 && (autoHeld || (autoFired && !pressed) || erased))
  1.1067 +			{
  1.1068 +				int colorNum = 1;     // default is white
  1.1069 +				if (autoHeld)
  1.1070 +					colorNum += (pressed ? 2 : 1);     // yellow if pressed, red if not
  1.1071 +				else if (autoFired)
  1.1072 +					colorNum += 5;     // blue if autofired and not currently pressed
  1.1073 +				else if (erased)
  1.1074 +					colorNum += 8;     // black on black
  1.1075 +
  1.1076 +				colorList[strlen("    ") + i] = colorNum;
  1.1077 +				pressed = true;
  1.1078 +			}
  1.1079 +			buffer[strlen("    ") + i] = pressed ? KeyMap[j] : ' ';
  1.1080 +		}
  1.1081 +	}
  1.1082 +
  1.1083 +	lastKeys  = currentButtons[0];
  1.1084 +	lastKeys |= theApp.autoHold & BUTTON_REGULAR_RECORDING_MASK;
  1.1085 +	lastKeys |= (theApp.autoFire | theApp.autoFire2) & BUTTON_REGULAR_RECORDING_MASK;
  1.1086 +
  1.1087 +	systemScreenMessage(buffer, 2, -1, colorList);
  1.1088 +#endif
  1.1089 +}
  1.1090 +
  1.1091 +void VBAUpdateFrameCountDisplay()
  1.1092 +{
  1.1093 +	const int MAGICAL_NUMBER = 64;  // FIXME: this won't do any better, but only to remind you of sz issues
  1.1094 +	char	  frameDisplayString[MAGICAL_NUMBER];
  1.1095 +	char	  lagFrameDisplayString[MAGICAL_NUMBER];
  1.1096 +	char      extraCountDisplayString[MAGICAL_NUMBER];
  1.1097 +
  1.1098 +#if (defined(WIN32) && !defined(SDL))
  1.1099 +	if (theApp.frameCounter)
  1.1100 +#else
  1.1101 +	/// SDL FIXME
  1.1102 +#endif
  1.1103 +	{
  1.1104 +		switch (Movie.state)
  1.1105 +		{
  1.1106 +		case MOVIE_STATE_PLAY:
  1.1107 +		case MOVIE_STATE_END:
  1.1108 +		{
  1.1109 +			sprintf(frameDisplayString, "%d / %d", Movie.currentFrame, Movie.header.length_frames);
  1.1110 +			if (!Movie.readOnly)
  1.1111 +				strcat(frameDisplayString, " (edit)");
  1.1112 +			break;
  1.1113 +		}
  1.1114 +		case MOVIE_STATE_RECORD:
  1.1115 +		{
  1.1116 +			sprintf(frameDisplayString, "%d (record)", Movie.currentFrame);
  1.1117 +			break;
  1.1118 +		}
  1.1119 +		default:
  1.1120 +		{
  1.1121 +			sprintf(frameDisplayString, "%d (no movie)", systemCounters.frameCount);
  1.1122 +			break;
  1.1123 +		}
  1.1124 +		}
  1.1125 +
  1.1126 +#if (defined(WIN32) && !defined(SDL))
  1.1127 +		if (theApp.lagCounter)
  1.1128 +#else
  1.1129 +		/// SDL FIXME
  1.1130 +#endif
  1.1131 +		{
  1.1132 +//			sprintf(lagFrameDisplayString, " %c %d", systemCounters.laggedLast ? '*' : '|', systemCounters.lagCount);
  1.1133 +			sprintf(lagFrameDisplayString, " | %d%s", systemCounters.lagCount, systemCounters.laggedLast ? " *" : "");
  1.1134 +			strcat(frameDisplayString, lagFrameDisplayString);
  1.1135 +		}
  1.1136 +
  1.1137 +#if (defined(WIN32) && !defined(SDL))
  1.1138 +		if (theApp.extraCounter)
  1.1139 +#else
  1.1140 +		/// SDL FIXME
  1.1141 +#endif
  1.1142 +		{
  1.1143 +			sprintf(extraCountDisplayString, " | %d", systemCounters.frameCount - systemCounters.extraCount);
  1.1144 +			strcat(frameDisplayString, extraCountDisplayString);
  1.1145 +		}
  1.1146 +	}
  1.1147 +#if (defined(WIN32) && !defined(SDL))
  1.1148 +	else
  1.1149 +	{
  1.1150 +		frameDisplayString[0] = '\0';
  1.1151 +	}
  1.1152 +#else
  1.1153 +	/// SDL FIXME
  1.1154 +#endif
  1.1155 +	systemScreenMessage(frameDisplayString, 1, -1);
  1.1156 +}
  1.1157 +
  1.1158 +// this function should only be called once every frame
  1.1159 +void VBAMovieUpdateState()
  1.1160 +{
  1.1161 +	++Movie.currentFrame;
  1.1162 +
  1.1163 +	if (Movie.state == MOVIE_STATE_PLAY)
  1.1164 +	{
  1.1165 +		Movie.inputBufferPtr += Movie.bytesPerFrame;
  1.1166 +		if (Movie.currentFrame >= Movie.header.length_frames)
  1.1167 +		{
  1.1168 +			// the movie ends anyway; what to do next depends on the settings
  1.1169 +			change_state(MOVIE_STATE_END);
  1.1170 +		}
  1.1171 +	}
  1.1172 +	else if (Movie.state == MOVIE_STATE_RECORD)
  1.1173 +	{
  1.1174 +		// use first fseek?
  1.1175 +		fwrite(Movie.inputBufferPtr, 1, Movie.bytesPerFrame, Movie.file);
  1.1176 +		Movie.header.length_frames = Movie.currentFrame;
  1.1177 +		Movie.inputBufferPtr	 += Movie.bytesPerFrame;
  1.1178 +		Movie.RecordedThisSession = true;
  1.1179 +		flush_movie_header();
  1.1180 +	}
  1.1181 +	else if (Movie.state == MOVIE_STATE_END)
  1.1182 +	{
  1.1183 +		change_state(MOVIE_STATE_END);
  1.1184 +	}
  1.1185 +}
  1.1186 +
  1.1187 +void VBAMovieRead(int i, bool /*sensor*/)
  1.1188 +{
  1.1189 +	if (Movie.state != MOVIE_STATE_PLAY)
  1.1190 +		return;
  1.1191 +
  1.1192 +	if (i < 0 || i >= MOVIE_NUM_OF_POSSIBLE_CONTROLLERS)
  1.1193 +		return;      // not a controller we're recognizing
  1.1194 +
  1.1195 +	if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i))
  1.1196 +	{
  1.1197 +		currentButtons[i] = Read16(Movie.inputBufferPtr + CONTROLLER_DATA_SIZE * i);
  1.1198 +	}
  1.1199 +	else
  1.1200 +	{
  1.1201 +		currentButtons[i] = 0;        // pretend the controller is disconnected
  1.1202 +	}
  1.1203 +
  1.1204 +	if ((currentButtons[i] & BUTTON_MASK_NEW_RESET) != 0)
  1.1205 +		resetSignaled = true;
  1.1206 +}
  1.1207 +
  1.1208 +void VBAMovieWrite(int i, bool /*sensor*/)
  1.1209 +{
  1.1210 +	if (Movie.state != MOVIE_STATE_RECORD)
  1.1211 +		return;
  1.1212 +
  1.1213 +	if (i < 0 || i >= MOVIE_NUM_OF_POSSIBLE_CONTROLLERS)
  1.1214 +		return;      // not a controller we're recognizing
  1.1215 +
  1.1216 +	reserve_buffer_space((uint32)((Movie.inputBufferPtr - Movie.inputBuffer) + Movie.bytesPerFrame));
  1.1217 +
  1.1218 +	if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i))
  1.1219 +	{
  1.1220 +		// get the current controller data
  1.1221 +		uint16 buttonData = currentButtons[i];
  1.1222 +
  1.1223 +		// mask away the irrelevent bits
  1.1224 +		buttonData &= BUTTON_REGULAR_MASK | BUTTON_MOTION_MASK;
  1.1225 +
  1.1226 +		// soft-reset "button" for 1 frame if the game is reset while recording
  1.1227 +		if (resetSignaled)
  1.1228 +		{
  1.1229 +			buttonData |= BUTTON_MASK_NEW_RESET;
  1.1230 +		}
  1.1231 +
  1.1232 +		// backward compatibility kludge
  1.1233 +		if (resetSignaledLast)
  1.1234 +		{
  1.1235 +			buttonData |= BUTTON_MASK_OLD_RESET;
  1.1236 +		}
  1.1237 +
  1.1238 +		Write16(buttonData, Movie.inputBufferPtr + CONTROLLER_DATA_SIZE * i);
  1.1239 +
  1.1240 +		// and for display
  1.1241 +		currentButtons[i] = buttonData;
  1.1242 +	}
  1.1243 +	else
  1.1244 +	{
  1.1245 +		// pretend the controller is disconnected (otherwise input it gives could cause desync since we're not writing it to the
  1.1246 +		// movie)
  1.1247 +		currentButtons[i] = 0;
  1.1248 +	}
  1.1249 +}
  1.1250 +
  1.1251 +void VBAMovieStop(bool8 suppress_message)
  1.1252 +{
  1.1253 +	if (Movie.state != MOVIE_STATE_NONE)
  1.1254 +	{
  1.1255 +		change_state(MOVIE_STATE_NONE);
  1.1256 +		if (!suppress_message)
  1.1257 +			systemScreenMessage("Movie stop");
  1.1258 +	}
  1.1259 +}
  1.1260 +
  1.1261 +int VBAMovieGetInfo(const char *filename, SMovie *info)
  1.1262 +{
  1.1263 +	assert(info != NULL);
  1.1264 +	if (info == NULL)
  1.1265 +		return -1;
  1.1266 +
  1.1267 +	FILE *	file;
  1.1268 +	int		result;
  1.1269 +	SMovie &local_movie = *info;
  1.1270 +
  1.1271 +	memset(info, 0, sizeof(*info));
  1.1272 +	if (filename[0] == '\0')
  1.1273 +		return MOVIE_FILE_NOT_FOUND;
  1.1274 +	if (!(file = fopen(filename, "rb")))
  1.1275 +		return MOVIE_FILE_NOT_FOUND;
  1.1276 +
  1.1277 +	// read header
  1.1278 +	if ((result = (read_movie_header(file, local_movie))) != MOVIE_SUCCESS)
  1.1279 +	{
  1.1280 +		fclose(file);
  1.1281 +		return result;
  1.1282 +	}
  1.1283 +
  1.1284 +	// read the metadata / author info from file
  1.1285 +	fread(local_movie.authorInfo, 1, sizeof(char) * MOVIE_METADATA_SIZE, file);
  1.1286 +
  1.1287 +	strncpy(local_movie.filename, filename, _MAX_PATH);
  1.1288 +	local_movie.filename[_MAX_PATH - 1] = '\0';
  1.1289 +
  1.1290 +	if (Movie.file != NULL && stricmp(local_movie.filename, Movie.filename) == 0) // alreadyOpen
  1.1291 +	{
  1.1292 +		local_movie.bytesPerFrame		 = Movie.bytesPerFrame;
  1.1293 +		local_movie.header.length_frames = Movie.header.length_frames;
  1.1294 +	}
  1.1295 +	else
  1.1296 +	{
  1.1297 +		// recalculate length of movie from the file size
  1.1298 +		local_movie.bytesPerFrame = bytes_per_frame(local_movie);
  1.1299 +		fseek(file, 0, SEEK_END);
  1.1300 +		int fileSize = ftell(file);
  1.1301 +		local_movie.header.length_frames =
  1.1302 +		    (fileSize - local_movie.header.offset_to_controller_data) / local_movie.bytesPerFrame;
  1.1303 +	}
  1.1304 +
  1.1305 +	fclose(file);
  1.1306 +
  1.1307 +	if (access(filename, W_OK))
  1.1308 +		info->readOnly = true;
  1.1309 +
  1.1310 +	return MOVIE_SUCCESS;
  1.1311 +}
  1.1312 +
  1.1313 +bool8 VBAMovieActive()
  1.1314 +{
  1.1315 +	return (Movie.state != MOVIE_STATE_NONE);
  1.1316 +}
  1.1317 +
  1.1318 +bool8 VBAMovieLoading()
  1.1319 +{
  1.1320 +	return loadingMovie;
  1.1321 +}
  1.1322 +
  1.1323 +bool8 VBAMoviePlaying()
  1.1324 +{
  1.1325 +	return (Movie.state == MOVIE_STATE_PLAY);
  1.1326 +}
  1.1327 +
  1.1328 +bool8 VBAMovieRecording()
  1.1329 +{
  1.1330 +	return (Movie.state == MOVIE_STATE_RECORD);
  1.1331 +}
  1.1332 +
  1.1333 +bool8 VBAMovieReadOnly()
  1.1334 +{
  1.1335 +	if (!VBAMovieActive())
  1.1336 +		return false;
  1.1337 +
  1.1338 +	return Movie.readOnly;
  1.1339 +}
  1.1340 +
  1.1341 +void VBAMovieToggleReadOnly()
  1.1342 +{
  1.1343 +	if (!VBAMovieActive())
  1.1344 +		return;
  1.1345 +
  1.1346 +	if (Movie.readOnly != 2)
  1.1347 +	{
  1.1348 +		Movie.readOnly = !Movie.readOnly;
  1.1349 +
  1.1350 +		systemScreenMessage(Movie.readOnly ? "Movie now read-only" : "Movie now editable");
  1.1351 +	}
  1.1352 +	else
  1.1353 +	{
  1.1354 +		systemScreenMessage("Can't toggle read-only movie");
  1.1355 +	}
  1.1356 +}
  1.1357 +
  1.1358 +uint32 VBAMovieGetVersion()
  1.1359 +{
  1.1360 +	if (!VBAMovieActive())
  1.1361 +		return 0;
  1.1362 +
  1.1363 +	return Movie.header.version;
  1.1364 +}
  1.1365 +
  1.1366 +uint32 VBAMovieGetMinorVersion()
  1.1367 +{
  1.1368 +	if (!VBAMovieActive())
  1.1369 +		return 0;
  1.1370 +
  1.1371 +	return Movie.header.minorVersion;
  1.1372 +}
  1.1373 +
  1.1374 +uint32 VBAMovieGetId()
  1.1375 +{
  1.1376 +	if (!VBAMovieActive())
  1.1377 +		return 0;
  1.1378 +
  1.1379 +	return Movie.header.uid;
  1.1380 +}
  1.1381 +
  1.1382 +uint32 VBAMovieGetLength()
  1.1383 +{
  1.1384 +	if (!VBAMovieActive())
  1.1385 +		return 0;
  1.1386 +
  1.1387 +	return Movie.header.length_frames;
  1.1388 +}
  1.1389 +
  1.1390 +uint32 VBAMovieGetFrameCounter()
  1.1391 +{
  1.1392 +	if (!VBAMovieActive())
  1.1393 +		return 0;
  1.1394 +
  1.1395 +	return Movie.currentFrame;
  1.1396 +}
  1.1397 +
  1.1398 +uint32 VBAMovieGetRerecordCount()
  1.1399 +{
  1.1400 +	if (!VBAMovieActive())
  1.1401 +		return 0;
  1.1402 +
  1.1403 +	return Movie.header.rerecord_count;
  1.1404 +}
  1.1405 +
  1.1406 +uint32 VBAMovieSetRerecordCount(uint32 newRerecordCount)
  1.1407 +{
  1.1408 +	uint32 oldRerecordCount = 0;
  1.1409 +	if (!VBAMovieActive())
  1.1410 +		return 0;
  1.1411 +
  1.1412 +	oldRerecordCount = Movie.header.rerecord_count;
  1.1413 +	Movie.header.rerecord_count = newRerecordCount;
  1.1414 +	return oldRerecordCount;
  1.1415 +}
  1.1416 +
  1.1417 +std::string VBAMovieGetAuthorInfo()
  1.1418 +{
  1.1419 +	if (!VBAMovieActive())
  1.1420 +		return "";
  1.1421 +
  1.1422 +	return Movie.authorInfo;
  1.1423 +}
  1.1424 +
  1.1425 +std::string VBAMovieGetFilename()
  1.1426 +{
  1.1427 +	if (!VBAMovieActive())
  1.1428 +		return "";
  1.1429 +
  1.1430 +	return Movie.filename;
  1.1431 +}
  1.1432 +
  1.1433 +void VBAMovieFreeze(uint8 * *buf, uint32 *size)
  1.1434 +{
  1.1435 +	// sanity check
  1.1436 +	if (!VBAMovieActive())
  1.1437 +	{
  1.1438 +		return;
  1.1439 +	}
  1.1440 +
  1.1441 +	*buf  = NULL;
  1.1442 +	*size = 0;
  1.1443 +
  1.1444 +	// compute size needed for the buffer
  1.1445 +	// room for header.uid, currentFrame, and header.length_frames
  1.1446 +	uint32 size_needed = sizeof(Movie.header.uid) + sizeof(Movie.currentFrame) + sizeof(Movie.header.length_frames);
  1.1447 +	size_needed += (uint32)(Movie.bytesPerFrame * Movie.header.length_frames);
  1.1448 +	*buf		 = new uint8[size_needed];
  1.1449 +	*size		 = size_needed;
  1.1450 +
  1.1451 +	uint8 *ptr = *buf;
  1.1452 +	if (!ptr)
  1.1453 +	{
  1.1454 +		return;
  1.1455 +	}
  1.1456 +
  1.1457 +	Push32(Movie.header.uid, ptr);
  1.1458 +	Push32(Movie.currentFrame, ptr);
  1.1459 +	Push32(Movie.header.length_frames - 1, ptr);   // HACK: shorten the length by 1 for backward compatibility
  1.1460 +
  1.1461 +	memcpy(ptr, Movie.inputBuffer, Movie.bytesPerFrame * Movie.header.length_frames);
  1.1462 +}
  1.1463 +
  1.1464 +int VBAMovieUnfreeze(const uint8 *buf, uint32 size)
  1.1465 +{
  1.1466 +	// sanity check
  1.1467 +	if (!VBAMovieActive())
  1.1468 +	{
  1.1469 +		return MOVIE_NOT_FROM_A_MOVIE;
  1.1470 +	}
  1.1471 +
  1.1472 +	const uint8 *ptr = buf;
  1.1473 +	if (size < sizeof(Movie.header.uid) + sizeof(Movie.currentFrame) + sizeof(Movie.header.length_frames))
  1.1474 +	{
  1.1475 +		return MOVIE_WRONG_FORMAT;
  1.1476 +	}
  1.1477 +
  1.1478 +	uint32 movie_id		 = Pop32(ptr);
  1.1479 +	uint32 current_frame = Pop32(ptr);
  1.1480 +	uint32 end_frame	 = Pop32(ptr) + 1;     // HACK: restore the length for backward compatibility
  1.1481 +	uint32 space_needed	 = Movie.bytesPerFrame * end_frame;
  1.1482 +
  1.1483 +	if (movie_id != Movie.header.uid)
  1.1484 +		return MOVIE_NOT_FROM_THIS_MOVIE;
  1.1485 +
  1.1486 +	if (space_needed > size)
  1.1487 +		return MOVIE_WRONG_FORMAT;
  1.1488 +
  1.1489 +	if (Movie.readOnly)
  1.1490 +	{
  1.1491 +		// here, we are going to keep the input data from the movie file
  1.1492 +		// and simply rewind to the currentFrame pointer
  1.1493 +		// this will cause a desync if the savestate is not in sync // <-- NOT ANYMORE
  1.1494 +		// with the on-disk recording data, but it's easily solved
  1.1495 +		// by loading another savestate or playing the movie from the beginning
  1.1496 +
  1.1497 +		// don't allow loading a state inconsistent with the current movie
  1.1498 +		uint32 length_history = min(current_frame, Movie.header.length_frames);
  1.1499 +		if (end_frame < length_history)
  1.1500 +			return MOVIE_SNAPSHOT_INCONSISTENT;
  1.1501 +
  1.1502 +		uint32 space_shared = Movie.bytesPerFrame * length_history;
  1.1503 +		if (memcmp(Movie.inputBuffer, ptr, space_shared))
  1.1504 +			return MOVIE_SNAPSHOT_INCONSISTENT;
  1.1505 +
  1.1506 +		Movie.currentFrame	 = current_frame;
  1.1507 +		Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * min(current_frame, Movie.header.length_frames);
  1.1508 +	}
  1.1509 +	else
  1.1510 +	{
  1.1511 +		// here, we are going to take the input data from the savestate
  1.1512 +		// and make it the input data for the current movie, then continue
  1.1513 +		// writing new input data at the currentFrame pointer
  1.1514 +		Movie.currentFrame		   = current_frame;
  1.1515 +		Movie.header.length_frames = end_frame;
  1.1516 +		if (!VBALuaRerecordCountSkip())
  1.1517 +			++Movie.header.rerecord_count;
  1.1518 +
  1.1519 +		Movie.RecordedThisSession = true;
  1.1520 +
  1.1521 +		// do this before calling reserve_buffer_space()
  1.1522 +		Movie.inputBufferPtr = Movie.inputBuffer + Movie.bytesPerFrame * min(current_frame, Movie.header.length_frames);
  1.1523 +		reserve_buffer_space(space_needed);
  1.1524 +		memcpy(Movie.inputBuffer, ptr, space_needed);
  1.1525 +
  1.1526 +		// for consistency, no auto movie conversion here since we don't auto convert the corresponding savestate
  1.1527 +		flush_movie_header();
  1.1528 +		flush_movie_frames();
  1.1529 +	}
  1.1530 +
  1.1531 +	change_state(MOVIE_STATE_PLAY);	// check for movie end
  1.1532 +
  1.1533 +	// necessary!
  1.1534 +	resetSignaled	  = false;
  1.1535 +	resetSignaledLast = false;
  1.1536 +
  1.1537 +	// necessary to check if there's a reset signal at the previous frame
  1.1538 +	if (current_frame > 0)
  1.1539 +	{
  1.1540 +		const u8 NEW_RESET = u8(BUTTON_MASK_NEW_RESET >> 8);
  1.1541 +		for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i)
  1.1542 +		{
  1.1543 +			if ((Movie.header.controllerFlags & MOVIE_CONTROLLER(i)) && (*(Movie.inputBufferPtr+1- Movie.bytesPerFrame) & NEW_RESET))
  1.1544 +			{
  1.1545 +				resetSignaledLast = true;
  1.1546 +				break;
  1.1547 +			}
  1.1548 +		}
  1.1549 +	}
  1.1550 +
  1.1551 +	return MOVIE_SUCCESS;
  1.1552 +}
  1.1553 +
  1.1554 +bool VBAMovieEnded()
  1.1555 +{
  1.1556 +	return (Movie.state == MOVIE_STATE_END);
  1.1557 +//	return (Movie.state != MOVIE_STATE_NONE && Movie.currentFrame >= Movie.header.length_frames);
  1.1558 +}
  1.1559 +
  1.1560 +bool VBAMovieAllowsRerecording()
  1.1561 +{
  1.1562 +	bool allows = (Movie.state != MOVIE_STATE_NONE) && (Movie.currentFrame <= Movie.header.length_frames);
  1.1563 +	return /*!VBAMovieReadOnly() &&*/ allows;
  1.1564 +}
  1.1565 +
  1.1566 +bool VBAMovieSwitchToPlaying()
  1.1567 +{
  1.1568 +	if (!VBAMovieActive())
  1.1569 +		return false;
  1.1570 +
  1.1571 +	if (!Movie.readOnly)
  1.1572 +	{
  1.1573 +		VBAMovieToggleReadOnly();
  1.1574 +	}
  1.1575 +
  1.1576 +	change_state(MOVIE_STATE_PLAY);
  1.1577 +	if (Movie.state == MOVIE_STATE_PLAY)
  1.1578 +		systemScreenMessage("Movie replay (continue)");
  1.1579 +	else
  1.1580 +		systemScreenMessage("Movie end");
  1.1581 +
  1.1582 +	return true;
  1.1583 +}
  1.1584 +
  1.1585 +bool VBAMovieSwitchToRecording()
  1.1586 +{
  1.1587 +	if (!VBAMovieAllowsRerecording())
  1.1588 +		return false;
  1.1589 +
  1.1590 +	if (Movie.readOnly)
  1.1591 +	{
  1.1592 +		VBAMovieToggleReadOnly();
  1.1593 +	}
  1.1594 +
  1.1595 +	if (!VBALuaRerecordCountSkip())
  1.1596 +		++Movie.header.rerecord_count;
  1.1597 +
  1.1598 +	change_state(MOVIE_STATE_RECORD);
  1.1599 +	systemScreenMessage("Movie re-record");
  1.1600 +
  1.1601 +	//truncate_movie(Movie.currentFrame);
  1.1602 +
  1.1603 +	return true;
  1.1604 +}
  1.1605 +
  1.1606 +uint32 VBAMovieGetState()
  1.1607 +{
  1.1608 +	// ?
  1.1609 +	if (!VBAMovieActive())
  1.1610 +		return MOVIE_STATE_NONE;
  1.1611 +
  1.1612 +	return Movie.state;
  1.1613 +}
  1.1614 +
  1.1615 +void VBAMovieSignalReset()
  1.1616 +{
  1.1617 +	if (VBAMovieActive())
  1.1618 +		resetSignaled = true;
  1.1619 +}
  1.1620 +
  1.1621 +void VBAMovieResetIfRequested()
  1.1622 +{
  1.1623 +	if (resetSignaled)
  1.1624 +	{
  1.1625 +		theEmulator.emuReset(false);
  1.1626 +		resetSignaled	  = false;
  1.1627 +		resetSignaledLast = true;
  1.1628 +	}
  1.1629 +	else
  1.1630 +	{
  1.1631 +		resetSignaledLast = false;
  1.1632 +	}
  1.1633 +}
  1.1634 +
  1.1635 +void VBAMovieSetMetadata(const char *info)
  1.1636 +{
  1.1637 +	if (!memcmp(Movie.authorInfo, info, MOVIE_METADATA_SIZE))
  1.1638 +		return;
  1.1639 +
  1.1640 +	memcpy(Movie.authorInfo, info, MOVIE_METADATA_SIZE); // strncpy would omit post-0 bytes
  1.1641 +	Movie.authorInfo[MOVIE_METADATA_SIZE - 1] = '\0';
  1.1642 +
  1.1643 +	if (Movie.file)
  1.1644 +	{
  1.1645 +		// (over-)write the header
  1.1646 +		fseek(Movie.file, 0, SEEK_SET);
  1.1647 +		write_movie_header(Movie.file, Movie);
  1.1648 +
  1.1649 +		// write the metadata / author info to file
  1.1650 +		fwrite(Movie.authorInfo, 1, sizeof(char) * MOVIE_METADATA_SIZE, Movie.file);
  1.1651 +
  1.1652 +		fflush(Movie.file);
  1.1653 +	}
  1.1654 +}
  1.1655 +
  1.1656 +void VBAMovieRestart()
  1.1657 +{
  1.1658 +	if (VBAMovieActive())
  1.1659 +	{
  1.1660 +		systemSoundClearBuffer();
  1.1661 +
  1.1662 +		bool8 modified = Movie.RecordedThisSession;
  1.1663 +
  1.1664 +		VBAMovieStop(true);
  1.1665 +
  1.1666 +		char movieName [_MAX_PATH];
  1.1667 +		strncpy(movieName, Movie.filename, _MAX_PATH);
  1.1668 +		movieName[_MAX_PATH - 1] = '\0';
  1.1669 +		VBAMovieOpen(movieName, Movie.readOnly); // can't just pass in Movie.filename, since VBAMovieOpen clears out Movie's
  1.1670 +		                                         // variables
  1.1671 +
  1.1672 +		Movie.RecordedThisSession = modified;
  1.1673 +
  1.1674 +		systemScreenMessage("Movie replay (restart)");
  1.1675 +	}
  1.1676 +}
  1.1677 +
  1.1678 +int VBAMovieGetPauseAt()
  1.1679 +{
  1.1680 +	return Movie.pauseFrame;
  1.1681 +}
  1.1682 +
  1.1683 +void VBAMovieSetPauseAt(int at)
  1.1684 +{
  1.1685 +	Movie.pauseFrame = at;
  1.1686 +}
  1.1687 +
  1.1688 +///////////////////////
  1.1689 +// movie tools
  1.1690 +
  1.1691 +// FIXME: is it safe to convert/flush a movie while recording it (considering fseek() problem)?
  1.1692 +int VBAMovieConvertCurrent()
  1.1693 +{
  1.1694 +	if (!VBAMovieActive())
  1.1695 +	{
  1.1696 +		return MOVIE_NOTHING;
  1.1697 +	}
  1.1698 +
  1.1699 +	if (Movie.header.minorVersion > VBM_REVISION)
  1.1700 +	{
  1.1701 +		return MOVIE_WRONG_VERSION;
  1.1702 +	}
  1.1703 +
  1.1704 +	if (Movie.header.minorVersion == VBM_REVISION)
  1.1705 +	{
  1.1706 +		return MOVIE_NOTHING;
  1.1707 +	}
  1.1708 +
  1.1709 +	Movie.header.minorVersion = VBM_REVISION;
  1.1710 +
  1.1711 +	if (Movie.header.length_frames == 0) // this could happen
  1.1712 +	{
  1.1713 +		truncate_movie(0);
  1.1714 +		return MOVIE_SUCCESS;
  1.1715 +	}
  1.1716 +
  1.1717 +	// fix movies recorded from snapshots
  1.1718 +	if (Movie.header.startFlags & MOVIE_START_FROM_SNAPSHOT)
  1.1719 +	{
  1.1720 +		uint8 *firstFramePtr = Movie.inputBuffer;
  1.1721 +		for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i)
  1.1722 +		{
  1.1723 +			if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i))
  1.1724 +			{
  1.1725 +				Push16(initialInputs[i], firstFramePtr);
  1.1726 +				// note: this is correct since Push16 advances the dest pointer by sizeof u16
  1.1727 +			}
  1.1728 +		}
  1.1729 +	}
  1.1730 +
  1.1731 +	// convert old resets to new ones
  1.1732 +	const u8 OLD_RESET = u8(BUTTON_MASK_OLD_RESET >> 8);
  1.1733 +	const u8 NEW_RESET = u8(BUTTON_MASK_NEW_RESET >> 8);
  1.1734 +	for (int i = 0; i < MOVIE_NUM_OF_POSSIBLE_CONTROLLERS; ++i)
  1.1735 +	{
  1.1736 +		if (Movie.header.controllerFlags & MOVIE_CONTROLLER(i))
  1.1737 +		{
  1.1738 +			uint8 *startPtr = Movie.inputBuffer + sizeof(u16) * i + 1;
  1.1739 +			uint8 *endPtr	= Movie.inputBuffer + Movie.bytesPerFrame * (Movie.header.length_frames - 1);
  1.1740 +			for (; startPtr < endPtr; startPtr += Movie.bytesPerFrame)
  1.1741 +			{
  1.1742 +				if (startPtr[Movie.bytesPerFrame] & OLD_RESET)
  1.1743 +				{
  1.1744 +					startPtr[0] |= NEW_RESET;
  1.1745 +				}
  1.1746 +			}
  1.1747 +		}
  1.1748 +	}
  1.1749 +
  1.1750 +	flush_movie_header();
  1.1751 +	flush_movie_frames();
  1.1752 +	return MOVIE_SUCCESS;
  1.1753 +}
  1.1754 +
  1.1755 +bool VBAMovieTuncateAtCurrentFrame()
  1.1756 +{
  1.1757 +	if (!VBAMovieActive())
  1.1758 +		return false;
  1.1759 +
  1.1760 +	truncate_movie(Movie.currentFrame);
  1.1761 +	change_state(MOVIE_STATE_END);
  1.1762 +	systemScreenMessage("Movie truncated");
  1.1763 +
  1.1764 +	return true;
  1.1765 +}
  1.1766 +
  1.1767 +bool VBAMovieFixHeader()
  1.1768 +{
  1.1769 +	if (!VBAMovieActive())
  1.1770 +		return false;
  1.1771 +
  1.1772 +	flush_movie_header();
  1.1773 +	systemScreenMessage("Movie header fixed");
  1.1774 +	return true;
  1.1775 +}