snes9x/movie.cpp

1044 lines
25 KiB
C++

/*****************************************************************************\
Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
This file is licensed under the Snes9x License.
For further information, consult the LICENSE file in the root directory.
\*****************************************************************************/
// Input recording/playback code
// (c) Copyright 2004 blip
#ifndef __WIN32__
#include <unistd.h>
#endif
#include "snes9x.h"
#include "memmap.h"
#include "controls.h"
#include "snapshot.h"
#include "movie.h"
#include "language.h"
#ifdef NETPLAY_SUPPORT
#include "netplay.h"
#endif
#ifdef __WIN32__
#include <io.h>
#ifndef W_OK
#define W_OK 2
#endif
#define ftruncate chsize
#endif
#define SMV_MAGIC 0x1a564d53 // SMV0x1a
#define SMV_VERSION 5
#define SMV_HEADER_SIZE 64
#define SMV_EXTRAROMINFO_SIZE 30
#define BUFFER_GROWTH_SIZE 4096
enum MovieState
{
MOVIE_STATE_NONE = 0,
MOVIE_STATE_PLAY,
MOVIE_STATE_RECORD
};
struct SMovie
{
enum MovieState State;
FILE *File;
char Filename[PATH_MAX + 1];
char ROMName[23];
uint32 ROMCRC32;
uint32 MovieId;
uint32 Version;
uint32 SaveStateOffset;
uint32 ControllerDataOffset;
uint8 ControllersMask;
uint8 Opts;
uint8 SyncFlags;
uint32 MaxFrame;
uint32 MaxSample;
uint32 CurrentFrame;
uint32 CurrentSample;
uint32 BytesPerSample;
uint32 RerecordCount;
bool8 ReadOnly;
uint8 PortType[2];
int8 PortIDs[2][4];
uint8 *InputBuffer;
uint8 *InputBufferPtr;
uint32 InputBufferSize;
};
static struct SMovie Movie;
static uint8 prevPortType[2];
static int8 prevPortIDs[2][4];
static bool8 prevMouseMaster, prevSuperScopeMaster, prevJustifierMaster, prevMultiPlayer5Master;
static uint8 Read8 (uint8 *&);
static uint16 Read16 (uint8 *&);
static uint32 Read32 (uint8 *&);
static void Write8 (uint8, uint8 *&);
static void Write16 (uint16, uint8 *&);
static void Write32 (uint32, uint8 *&);
static void store_previous_settings (void);
static void restore_previous_settings (void);
static void store_movie_settings (void);
static void restore_movie_settings (void);
static int bytes_per_sample (void);
static void reserve_buffer_space (uint32);
static void reset_controllers (void);
static void read_frame_controller_data (bool);
static void write_frame_controller_data (void);
static void flush_movie (void);
static void truncate_movie (void);
static int read_movie_header (FILE *, SMovie *);
static int read_movie_extrarominfo (FILE *, SMovie *);
static void write_movie_header (FILE *, SMovie *);
static void write_movie_extrarominfo (FILE *, SMovie *);
static void change_state (MovieState);
// HACK: reduce movie size by not storing changes that can only affect polled input in the movie for these types,
// because currently no port sets these types to polling
#define SKIPPED_POLLING_PORT_TYPE(x) (((x) == CTL_NONE) || ((x) == CTL_JOYPAD) || ((x) == CTL_MP5))
#ifndef max
#define max(a, b) (((a) > (b)) ? (a) : (b))
#endif
static uint8 Read8 (uint8 *&ptr)
{
uint8 v = *ptr++;
return (v);
}
static uint16 Read16 (uint8 *&ptr)
{
uint16 v = READ_WORD(ptr);
ptr += 2;
return (v);
}
static uint32 Read32 (uint8 *&ptr)
{
uint32 v = READ_DWORD(ptr);
ptr += 4;
return (v);
}
static void Write8 (uint8 v, uint8 *&ptr)
{
*ptr++ = v;
}
static void Write16 (uint16 v, uint8 *&ptr)
{
WRITE_WORD(ptr, v);
ptr += 2;
}
static void Write32 (uint32 v, uint8 *&ptr)
{
WRITE_DWORD(ptr, v);
ptr += 4;
}
static void store_previous_settings (void)
{
for (int i = 0; i < 2; i++)
{
enum controllers pt;
S9xGetController(i, &pt, &prevPortIDs[i][0], &prevPortIDs[i][1], &prevPortIDs[i][2], &prevPortIDs[i][3]);
prevPortType[i] = (uint8) pt;
}
prevMouseMaster = Settings.MouseMaster;
prevSuperScopeMaster = Settings.SuperScopeMaster;
prevJustifierMaster = Settings.JustifierMaster;
prevMultiPlayer5Master = Settings.MultiPlayer5Master;
}
static void restore_previous_settings (void)
{
Settings.MouseMaster = prevMouseMaster;
Settings.SuperScopeMaster = prevSuperScopeMaster;
Settings.JustifierMaster = prevJustifierMaster;
Settings.MultiPlayer5Master = prevMultiPlayer5Master;
S9xSetController(0, (enum controllers) prevPortType[0], prevPortIDs[0][0], prevPortIDs[0][1], prevPortIDs[0][2], prevPortIDs[0][3]);
S9xSetController(1, (enum controllers) prevPortType[1], prevPortIDs[1][0], prevPortIDs[1][1], prevPortIDs[1][2], prevPortIDs[1][3]);
}
static void store_movie_settings (void)
{
for (int i = 0; i < 2; i++)
{
enum controllers pt;
S9xGetController(i, &pt, &Movie.PortIDs[i][0], &Movie.PortIDs[i][1], &Movie.PortIDs[i][2], &Movie.PortIDs[i][3]);
Movie.PortType[i] = (uint8) pt;
}
}
static void restore_movie_settings (void)
{
Settings.MouseMaster = (Movie.PortType[0] == CTL_MOUSE || Movie.PortType[1] == CTL_MOUSE);
Settings.SuperScopeMaster = (Movie.PortType[0] == CTL_SUPERSCOPE || Movie.PortType[1] == CTL_SUPERSCOPE);
Settings.JustifierMaster = (Movie.PortType[0] == CTL_JUSTIFIER || Movie.PortType[1] == CTL_JUSTIFIER);
Settings.MultiPlayer5Master = (Movie.PortType[0] == CTL_MP5 || Movie.PortType[1] == CTL_MP5);
S9xSetController(0, (enum controllers) Movie.PortType[0], Movie.PortIDs[0][0], Movie.PortIDs[0][1], Movie.PortIDs[0][2], Movie.PortIDs[0][3]);
S9xSetController(1, (enum controllers) Movie.PortType[1], Movie.PortIDs[1][0], Movie.PortIDs[1][1], Movie.PortIDs[1][2], Movie.PortIDs[1][3]);
}
static int bytes_per_sample (void)
{
int num_controllers = 0;
for (int i = 0; i < 8; i++)
{
if (Movie.ControllersMask & (1 << i))
num_controllers++;
}
int bytes = CONTROLLER_DATA_SIZE * num_controllers;
for (int p = 0; p < 2; p++)
{
if (Movie.PortType[p] == CTL_MOUSE)
bytes += MOUSE_DATA_SIZE;
else
if (Movie.PortType[p] == CTL_SUPERSCOPE)
bytes += SCOPE_DATA_SIZE;
else
if (Movie.PortType[p] == CTL_JUSTIFIER)
bytes += JUSTIFIER_DATA_SIZE;
}
return (bytes);
}
static void reserve_buffer_space (uint32 space_needed)
{
if (space_needed > Movie.InputBufferSize)
{
uint32 ptr_offset = Movie.InputBufferPtr - Movie.InputBuffer;
uint32 alloc_chunks = space_needed / BUFFER_GROWTH_SIZE;
Movie.InputBufferSize = BUFFER_GROWTH_SIZE * (alloc_chunks + 1);
Movie.InputBuffer = (uint8 *) realloc(Movie.InputBuffer, Movie.InputBufferSize);
Movie.InputBufferPtr = Movie.InputBuffer + ptr_offset;
}
}
static void reset_controllers (void)
{
for (int i = 0; i < 8; i++)
MovieSetJoypad(i, 0);
uint8 clearedMouse[MOUSE_DATA_SIZE];
memset(clearedMouse, 0, MOUSE_DATA_SIZE);
clearedMouse[4] = 1;
uint8 clearedScope[SCOPE_DATA_SIZE];
memset(clearedScope, 0, SCOPE_DATA_SIZE);
uint8 clearedJustifier[JUSTIFIER_DATA_SIZE];
memset(clearedJustifier, 0, JUSTIFIER_DATA_SIZE);
for (int p = 0; p < 2; p++)
{
MovieSetMouse(p, clearedMouse, true);
MovieSetScope(p, clearedScope);
MovieSetJustifier(p, clearedJustifier);
}
}
static void read_frame_controller_data (bool addFrame)
{
// reset code check
if (Movie.InputBufferPtr[0] == 0xff)
{
bool reset = true;
for (int i = 1; i < (int) Movie.BytesPerSample; i++)
{
if (Movie.InputBufferPtr[i] != 0xff)
{
reset = false;
break;
}
}
if (reset)
{
Movie.InputBufferPtr += Movie.BytesPerSample;
S9xSoftReset();
return;
}
}
for (int i = 0; i < 8; i++)
{
if (Movie.ControllersMask & (1 << i))
MovieSetJoypad(i, Read16(Movie.InputBufferPtr));
else
MovieSetJoypad(i, 0); // pretend the controller is disconnected
}
for (int p = 0; p < 2; p++)
{
if (Movie.PortType[p] == CTL_MOUSE)
{
uint8 buf[MOUSE_DATA_SIZE];
memcpy(buf, Movie.InputBufferPtr, MOUSE_DATA_SIZE);
Movie.InputBufferPtr += MOUSE_DATA_SIZE;
MovieSetMouse(p, buf, !addFrame);
}
else
if (Movie.PortType[p] == CTL_SUPERSCOPE)
{
uint8 buf[SCOPE_DATA_SIZE];
memcpy(buf, Movie.InputBufferPtr, SCOPE_DATA_SIZE);
Movie.InputBufferPtr += SCOPE_DATA_SIZE;
MovieSetScope(p, buf);
}
else
if (Movie.PortType[p] == CTL_JUSTIFIER)
{
uint8 buf[JUSTIFIER_DATA_SIZE];
memcpy(buf, Movie.InputBufferPtr, JUSTIFIER_DATA_SIZE);
Movie.InputBufferPtr += JUSTIFIER_DATA_SIZE;
MovieSetJustifier(p, buf);
}
}
}
static void write_frame_controller_data (void)
{
reserve_buffer_space((uint32) (Movie.InputBufferPtr + Movie.BytesPerSample - Movie.InputBuffer));
for (int i = 0; i < 8; i++)
{
if (Movie.ControllersMask & (1 << i))
Write16(MovieGetJoypad(i), Movie.InputBufferPtr);
else
MovieSetJoypad(i, 0); // pretend the controller is disconnected
}
for (int p = 0; p < 2; p++)
{
if (Movie.PortType[p] == CTL_MOUSE)
{
uint8 buf[MOUSE_DATA_SIZE];
MovieGetMouse(p, buf);
memcpy(Movie.InputBufferPtr, buf, MOUSE_DATA_SIZE);
Movie.InputBufferPtr += MOUSE_DATA_SIZE;
}
else
if (Movie.PortType[p] == CTL_SUPERSCOPE)
{
uint8 buf[SCOPE_DATA_SIZE];
MovieGetScope(p, buf);
memcpy(Movie.InputBufferPtr, buf, SCOPE_DATA_SIZE);
Movie.InputBufferPtr += SCOPE_DATA_SIZE;
}
else
if (Movie.PortType[p] == CTL_JUSTIFIER)
{
uint8 buf[JUSTIFIER_DATA_SIZE];
MovieGetJustifier(p, buf);
memcpy(Movie.InputBufferPtr, buf, JUSTIFIER_DATA_SIZE);
Movie.InputBufferPtr += JUSTIFIER_DATA_SIZE;
}
}
}
static void flush_movie (void)
{
if (!Movie.File)
return;
fseek(Movie.File, 0, SEEK_SET);
write_movie_header(Movie.File, &Movie);
fseek(Movie.File, Movie.ControllerDataOffset, SEEK_SET);
size_t ignore;
ignore = fwrite(Movie.InputBuffer, 1, Movie.BytesPerSample * (Movie.MaxSample + 1), Movie.File);
}
static void truncate_movie (void)
{
if (!Movie.File || !Settings.MovieTruncate)
return;
if (Movie.SaveStateOffset > Movie.ControllerDataOffset)
return;
int ignore;
ignore = ftruncate(fileno(Movie.File), Movie.ControllerDataOffset + Movie.BytesPerSample * (Movie.MaxSample + 1));
}
static int read_movie_header (FILE *fd, SMovie *movie)
{
uint32 value;
uint8 buf[SMV_HEADER_SIZE], *ptr = buf;
if (fread(buf, 1, SMV_HEADER_SIZE, fd) != SMV_HEADER_SIZE)
return (WRONG_FORMAT);
value = Read32(ptr);
if (value != SMV_MAGIC)
return (WRONG_FORMAT);
value = Read32(ptr);
if(value > SMV_VERSION || value < 4)
return (WRONG_VERSION);
movie->Version = value;
movie->MovieId = Read32(ptr);
movie->RerecordCount = Read32(ptr);
movie->MaxFrame = Read32(ptr);
movie->ControllersMask = Read8(ptr);
movie->Opts = Read8(ptr);
ptr++;
movie->SyncFlags = Read8(ptr);
movie->SaveStateOffset = Read32(ptr);
movie->ControllerDataOffset = Read32(ptr);
movie->MaxSample = Read32(ptr);
movie->PortType[0] = Read8(ptr);
movie->PortType[1] = Read8(ptr);
for (int p = 0; p < 2; p++)
{
for (int i = 0; i < 4; i++)
movie->PortIDs[p][i] = Read8(ptr);
}
if (movie->MaxSample < movie->MaxFrame)
movie->MaxSample = movie->MaxFrame;
return (SUCCESS);
}
static int read_movie_extrarominfo (FILE *fd, SMovie *movie)
{
uint8 buf[SMV_EXTRAROMINFO_SIZE], *ptr = buf;
fseek(fd, movie->SaveStateOffset - SMV_EXTRAROMINFO_SIZE, SEEK_SET);
if (fread(buf, 1, SMV_EXTRAROMINFO_SIZE, fd) != SMV_EXTRAROMINFO_SIZE)
return (WRONG_FORMAT);
ptr += 3; // zero bytes
movie->ROMCRC32 = Read32(ptr);
strncpy(movie->ROMName, (char *) ptr, 23);
return (SUCCESS);
}
static void write_movie_header (FILE *fd, SMovie *movie)
{
uint8 buf[SMV_HEADER_SIZE], *ptr = buf;
memset(buf, 0, sizeof(buf));
Write32(SMV_MAGIC, ptr);
Write32(SMV_VERSION, ptr);
Write32(movie->MovieId, ptr);
Write32(movie->RerecordCount, ptr);
Write32(movie->MaxFrame, ptr);
Write8(movie->ControllersMask, ptr);
Write8(movie->Opts, ptr);
ptr++;
Write8(movie->SyncFlags, ptr);
Write32(movie->SaveStateOffset, ptr);
Write32(movie->ControllerDataOffset, ptr);
Write32(movie->MaxSample, ptr);
Write8(movie->PortType[0], ptr);
Write8(movie->PortType[1], ptr);
for (int p = 0; p < 2; p++)
{
for (int i = 0; i < 4; i++)
Write8(movie->PortIDs[p][i], ptr);
}
size_t ignore;
ignore = fwrite(buf, 1, SMV_HEADER_SIZE, fd);
}
static void write_movie_extrarominfo (FILE *fd, SMovie *movie)
{
uint8 buf[SMV_EXTRAROMINFO_SIZE], *ptr = buf;
Write8(0, ptr);
Write8(0, ptr);
Write8(0, ptr);
Write32(movie->ROMCRC32, ptr);
strncpy((char *) ptr, movie->ROMName, 23);
size_t ignore;
ignore = fwrite(buf, 1, SMV_EXTRAROMINFO_SIZE, fd);
}
static void change_state (MovieState new_state)
{
if (new_state == Movie.State)
return;
if (Movie.State == MOVIE_STATE_RECORD)
flush_movie();
if (new_state == MOVIE_STATE_NONE)
{
truncate_movie();
fclose(Movie.File);
Movie.File = NULL;
if (S9xMoviePlaying() || S9xMovieRecording())
restore_previous_settings();
}
Movie.State = new_state;
}
void S9xMovieFreeze (uint8 **buf, uint32 *size)
{
if (!S9xMovieActive())
return;
uint32 size_needed;
uint8 *ptr;
size_needed = sizeof(Movie.MovieId) + sizeof(Movie.CurrentFrame) + sizeof(Movie.MaxFrame) + sizeof(Movie.CurrentSample) + sizeof(Movie.MaxSample);
size_needed += (uint32) (Movie.BytesPerSample * (Movie.MaxSample + 1));
*size = size_needed;
*buf = new uint8[size_needed];
ptr = *buf;
if (!ptr)
return;
Write32(Movie.MovieId, ptr);
Write32(Movie.CurrentFrame, ptr);
Write32(Movie.MaxFrame, ptr);
Write32(Movie.CurrentSample, ptr);
Write32(Movie.MaxSample, ptr);
memcpy(ptr, Movie.InputBuffer, Movie.BytesPerSample * (Movie.MaxSample + 1));
}
int S9xMovieUnfreeze (uint8 *buf, uint32 size)
{
if (!S9xMovieActive())
return (FILE_NOT_FOUND);
if (size < sizeof(Movie.MovieId) + sizeof(Movie.CurrentFrame) + sizeof(Movie.MaxFrame) + sizeof(Movie.CurrentSample) + sizeof(Movie.MaxSample))
return (WRONG_FORMAT);
uint8 *ptr = buf;
uint32 movie_id = Read32(ptr);
uint32 current_frame = Read32(ptr);
uint32 max_frame = Read32(ptr);
uint32 current_sample = Read32(ptr);
uint32 max_sample = Read32(ptr);
uint32 space_needed = (Movie.BytesPerSample * (max_sample + 1));
if (current_frame > max_frame || current_sample > max_sample || space_needed > size)
return (WRONG_MOVIE_SNAPSHOT);
if (Settings.WrongMovieStateProtection)
if (movie_id != Movie.MovieId)
if (max_frame < Movie.MaxFrame || max_sample < Movie.MaxSample || memcmp(Movie.InputBuffer, ptr, space_needed))
return (WRONG_MOVIE_SNAPSHOT);
if (!Movie.ReadOnly)
{
change_state(MOVIE_STATE_RECORD);
Movie.CurrentFrame = current_frame;
Movie.MaxFrame = max_frame;
Movie.CurrentSample = current_sample;
Movie.MaxSample = max_sample;
Movie.RerecordCount++;
store_movie_settings();
reserve_buffer_space(space_needed);
memcpy(Movie.InputBuffer, ptr, space_needed);
flush_movie();
fseek(Movie.File, Movie.ControllerDataOffset + (Movie.BytesPerSample * (Movie.CurrentSample + 1)), SEEK_SET);
}
else
{
uint32 space_processed = (Movie.BytesPerSample * (current_sample + 1));
if (current_frame > Movie.MaxFrame || current_sample > Movie.MaxSample || memcmp(Movie.InputBuffer, ptr, space_processed))
return (SNAPSHOT_INCONSISTENT);
change_state(MOVIE_STATE_PLAY);
Movie.CurrentFrame = current_frame;
Movie.CurrentSample = current_sample;
}
Movie.InputBufferPtr = Movie.InputBuffer + (Movie.BytesPerSample * Movie.CurrentSample);
read_frame_controller_data(true);
return (SUCCESS);
}
int S9xMovieOpen (const char *filename, bool8 read_only)
{
FILE *fd;
STREAM stream;
int result;
int fn;
if (!(fd = fopen(filename, "rb+")))
{
if (!(fd = fopen(filename, "rb")))
return (FILE_NOT_FOUND);
else
read_only = TRUE;
}
change_state(MOVIE_STATE_NONE);
result = read_movie_header(fd, &Movie);
if (result != SUCCESS)
{
fclose(fd);
return (result);
}
read_movie_extrarominfo(fd, &Movie);
fflush(fd);
fn = fileno(fd);
store_previous_settings();
restore_movie_settings();
lseek(fn, Movie.SaveStateOffset, SEEK_SET);
// reopen stream to access as gzipped data
stream = REOPEN_STREAM(fn, "rb");
if (!stream)
return (FILE_NOT_FOUND);
if (Movie.Opts & MOVIE_OPT_FROM_RESET)
{
S9xReset();
reset_controllers();
result = (READ_STREAM(Memory.SRAM, 0x20000, stream) == 0x20000) ? SUCCESS : WRONG_FORMAT;
}
else
result = S9xUnfreezeFromStream(stream);
// do not close stream but close FILE *
// (msvcrt will try to close all open FILE *handles on exit - if we do CLOSE_STREAM here
// the underlying file will be closed by zlib, causing problems when msvcrt tries to do it)
delete stream;
fclose(fd);
if (result != SUCCESS)
return (result);
if (!(fd = fopen(filename, "rb+")))
{
if (!(fd = fopen(filename, "rb")))
return (FILE_NOT_FOUND);
else
read_only = TRUE;
}
if (fseek(fd, Movie.ControllerDataOffset, SEEK_SET))
{
fclose(fd);
return (WRONG_FORMAT);
}
Movie.File = fd;
Movie.BytesPerSample = bytes_per_sample();
Movie.InputBufferPtr = Movie.InputBuffer;
reserve_buffer_space(Movie.BytesPerSample * (Movie.MaxSample + 1));
size_t ignore;
ignore = fread(Movie.InputBufferPtr, 1, Movie.BytesPerSample * (Movie.MaxSample + 1), fd);
// read "baseline" controller data
if (Movie.MaxSample && Movie.MaxFrame)
read_frame_controller_data(true);
Movie.CurrentFrame = 0;
Movie.CurrentSample = 0;
Movie.ReadOnly = read_only;
strncpy(Movie.Filename, filename, PATH_MAX + 1);
Movie.Filename[PATH_MAX] = 0;
change_state(MOVIE_STATE_PLAY);
S9xUpdateFrameCounter(-1);
S9xMessage(S9X_INFO, S9X_MOVIE_INFO, MOVIE_INFO_REPLAY);
return (SUCCESS);
}
int S9xMovieCreate (const char *filename, uint8 controllers_mask, uint8 opts, const wchar_t *metadata, int metadata_length)
{
FILE *fd;
STREAM stream;
if (controllers_mask == 0)
return (WRONG_FORMAT);
if (!(fd = fopen(filename, "wb")))
return (FILE_NOT_FOUND);
if (metadata_length > MOVIE_MAX_METADATA)
metadata_length = MOVIE_MAX_METADATA;
change_state(MOVIE_STATE_NONE);
store_previous_settings();
store_movie_settings();
Movie.MovieId = (uint32) time(NULL);
Movie.RerecordCount = 0;
Movie.MaxFrame = 0;
Movie.MaxSample = 0;
Movie.SaveStateOffset = SMV_HEADER_SIZE + (sizeof(uint16) * metadata_length) + SMV_EXTRAROMINFO_SIZE;
Movie.ControllerDataOffset = 0;
Movie.ControllersMask = controllers_mask;
Movie.Opts = opts;
Movie.SyncFlags = MOVIE_SYNC_DATA_EXISTS | MOVIE_SYNC_HASROMINFO;
write_movie_header(fd, &Movie);
// convert wchar_t metadata string/array to a uint16 array
// XXX: UTF-8 is much better...
if (metadata_length > 0)
{
uint8 meta_buf[sizeof(uint16) * MOVIE_MAX_METADATA];
for (int i = 0; i < metadata_length; i++)
{
uint16 c = (uint16) metadata[i];
meta_buf[i * 2] = (uint8) (c & 0xff);
meta_buf[i * 2 + 1] = (uint8) ((c >> 8) & 0xff);
}
size_t ignore;
ignore = fwrite(meta_buf, sizeof(uint16), metadata_length, fd);
}
Movie.ROMCRC32 = Memory.ROMCRC32;
strncpy(Movie.ROMName, Memory.RawROMName, 23);
write_movie_extrarominfo(fd, &Movie);
fclose(fd);
stream = OPEN_STREAM(filename, "ab");
if (!stream)
return (FILE_NOT_FOUND);
if (opts & MOVIE_OPT_FROM_RESET)
{
S9xReset();
reset_controllers();
WRITE_STREAM(Memory.SRAM, 0x20000, stream);
}
else
S9xFreezeToStream(stream);
CLOSE_STREAM(stream);
if (!(fd = fopen(filename, "rb+")))
return (FILE_NOT_FOUND);
fseek(fd, 0, SEEK_END);
Movie.ControllerDataOffset = (uint32) ftell(fd);
// 16-byte align the controller input, for hex-editing friendliness if nothing else
while (Movie.ControllerDataOffset % 16)
{
fputc(0xcc, fd); // arbitrary
Movie.ControllerDataOffset++;
}
// write "baseline" controller data
Movie.File = fd;
Movie.BytesPerSample = bytes_per_sample();
Movie.InputBufferPtr = Movie.InputBuffer;
write_frame_controller_data();
Movie.CurrentFrame = 0;
Movie.CurrentSample = 0;
Movie.ReadOnly = false;
strncpy(Movie.Filename, filename, PATH_MAX + 1);
Movie.Filename[PATH_MAX] = 0;
change_state(MOVIE_STATE_RECORD);
S9xUpdateFrameCounter(-1);
S9xMessage(S9X_INFO, S9X_MOVIE_INFO, MOVIE_INFO_RECORD);
return (SUCCESS);
}
int S9xMovieGetInfo (const char *filename, struct MovieInfo *info)
{
FILE *fd;
SMovie local_movie;
int metadata_length;
int result, i;
flush_movie();
memset(info, 0, sizeof(*info));
if (!(fd = fopen(filename, "rb")))
return (FILE_NOT_FOUND);
result = read_movie_header(fd, &local_movie);
if (result != SUCCESS)
{
fclose(fd);
return (result);
}
info->TimeCreated = (time_t) local_movie.MovieId;
info->Version = local_movie.Version;
info->Opts = local_movie.Opts;
info->SyncFlags = local_movie.SyncFlags;
info->ControllersMask = local_movie.ControllersMask;
info->RerecordCount = local_movie.RerecordCount;
info->LengthFrames = local_movie.MaxFrame;
info->LengthSamples = local_movie.MaxSample;
info->PortType[0] = local_movie.PortType[0];
info->PortType[1] = local_movie.PortType[1];
if (local_movie.SaveStateOffset > SMV_HEADER_SIZE)
{
uint8 meta_buf[sizeof(uint16) * MOVIE_MAX_METADATA];
int curRomInfoSize = (local_movie.SyncFlags & MOVIE_SYNC_HASROMINFO) ? SMV_EXTRAROMINFO_SIZE : 0;
metadata_length = ((int) local_movie.SaveStateOffset - SMV_HEADER_SIZE - curRomInfoSize) / sizeof(uint16);
metadata_length = (metadata_length >= MOVIE_MAX_METADATA) ? MOVIE_MAX_METADATA - 1 : metadata_length;
metadata_length = (int) fread(meta_buf, sizeof(uint16), metadata_length, fd);
for (i = 0; i < metadata_length; i++)
{
uint16 c = meta_buf[i * 2] | (meta_buf[i * 2 + 1] << 8);
info->Metadata[i] = (wchar_t) c;
}
info->Metadata[i] = '\0';
}
else
info->Metadata[0] = '\0';
read_movie_extrarominfo(fd, &local_movie);
info->ROMCRC32 = local_movie.ROMCRC32;
strncpy(info->ROMName, local_movie.ROMName, 23);
fclose(fd);
if ((fd = fopen(filename, "r+")) == NULL)
info->ReadOnly = true;
else
fclose(fd);
return (SUCCESS);
}
void S9xMovieUpdate (bool addFrame)
{
switch (Movie.State)
{
case MOVIE_STATE_PLAY:
{
if (Movie.CurrentFrame >= Movie.MaxFrame || Movie.CurrentSample >= Movie.MaxSample)
{
change_state(MOVIE_STATE_NONE);
S9xMessage(S9X_INFO, S9X_MOVIE_INFO, MOVIE_INFO_END);
return;
}
else
{
if (addFrame)
S9xUpdateFrameCounter();
else
if (SKIPPED_POLLING_PORT_TYPE(Movie.PortType[0]) && SKIPPED_POLLING_PORT_TYPE(Movie.PortType[1]))
return;
read_frame_controller_data(addFrame);
Movie.CurrentSample++;
if (addFrame)
Movie.CurrentFrame++;
}
break;
}
case MOVIE_STATE_RECORD:
{
if (addFrame)
S9xUpdateFrameCounter();
else
if (SKIPPED_POLLING_PORT_TYPE(Movie.PortType[0]) && SKIPPED_POLLING_PORT_TYPE(Movie.PortType[1]))
return;
write_frame_controller_data();
Movie.MaxSample = ++Movie.CurrentSample;
if (addFrame)
Movie.MaxFrame = ++Movie.CurrentFrame;
size_t ignore;
ignore = fwrite((Movie.InputBufferPtr - Movie.BytesPerSample), 1, Movie.BytesPerSample, Movie.File);
break;
}
default:
{
if (addFrame)
S9xUpdateFrameCounter();
break;
}
}
}
void S9xMovieUpdateOnReset (void)
{
if (Movie.State == MOVIE_STATE_RECORD)
{
reserve_buffer_space((uint32) (Movie.InputBufferPtr + Movie.BytesPerSample - Movie.InputBuffer));
memset(Movie.InputBufferPtr, 0xFF, Movie.BytesPerSample);
Movie.InputBufferPtr += Movie.BytesPerSample;
Movie.MaxSample = ++Movie.CurrentSample;
Movie.MaxFrame = ++Movie.CurrentFrame;
size_t ignore;
ignore = fwrite((Movie.InputBufferPtr - Movie.BytesPerSample), 1, Movie.BytesPerSample, Movie.File);
}
}
void S9xMovieInit (void)
{
memset(&Movie, 0, sizeof(Movie));
Movie.State = MOVIE_STATE_NONE;
}
void S9xMovieStop (bool8 suppress_message)
{
if (Movie.State != MOVIE_STATE_NONE)
{
change_state(MOVIE_STATE_NONE);
if (!suppress_message)
S9xMessage(S9X_INFO, S9X_MOVIE_INFO, MOVIE_INFO_STOP);
}
}
void S9xMovieShutdown (void)
{
if (S9xMovieActive())
S9xMovieStop(TRUE);
}
bool8 S9xMovieActive (void)
{
return (Movie.State != MOVIE_STATE_NONE);
}
bool8 S9xMoviePlaying (void)
{
return (Movie.State == MOVIE_STATE_PLAY);
}
bool8 S9xMovieRecording (void)
{
return (Movie.State == MOVIE_STATE_RECORD);
}
uint8 S9xMovieControllers (void)
{
return (Movie.ControllersMask);
}
bool8 S9xMovieReadOnly (void)
{
if (!S9xMovieActive())
return (FALSE);
return (Movie.ReadOnly);
}
uint32 S9xMovieGetId (void)
{
if (!S9xMovieActive())
return (0);
return (Movie.MovieId);
}
uint32 S9xMovieGetLength (void)
{
if (!S9xMovieActive())
return (0);
return (Movie.MaxFrame);
}
uint32 S9xMovieGetFrameCounter (void)
{
if (!S9xMovieActive())
return (0);
return (Movie.CurrentFrame);
}
void S9xMovieToggleRecState (void)
{
Movie.ReadOnly = !Movie.ReadOnly;
if (Movie.ReadOnly)
S9xMessage(S9X_INFO, S9X_MOVIE_INFO, "Movie is now read-only.");
else
S9xMessage(S9X_INFO, S9X_MOVIE_INFO, "Movie is now read+write.");
}
void S9xMovieToggleFrameDisplay (void)
{
Settings.DisplayMovieFrame = !Settings.DisplayMovieFrame;
S9xReRefresh();
}
void S9xUpdateFrameCounter (int offset)
{
extern bool8 pad_read;
offset++;
if (!Settings.DisplayMovieFrame)
*GFX.FrameDisplayString = 0;
else
if (Movie.State == MOVIE_STATE_RECORD)
sprintf(GFX.FrameDisplayString, "Recording frame: %d%s",
max(0, (int) (Movie.CurrentFrame + offset)), pad_read || !Settings.MovieNotifyIgnored ? "" : " (ignored)");
else
if (Movie.State == MOVIE_STATE_PLAY)
sprintf(GFX.FrameDisplayString, "Playing frame: %d / %d",
max(0, (int) (Movie.CurrentFrame + offset)), Movie.MaxFrame);
#ifdef NETPLAY_SUPPORT
else
if (Settings.NetPlay)
sprintf(GFX.FrameDisplayString, "%s frame: %d", Settings.NetPlayServer ? "Server" : "Client",
max(0, (int) (NetPlay.FrameCount + offset)));
#endif
}