Files
ZSNES/zsnes/src/zmovie.c
2005-03-28 14:23:06 +00:00

1807 lines
45 KiB
C

/*
Copyright (C) 1997-2005 ZSNES Team ( zsKnight, _Demo_, pagefault, Nach )
http://www.zsnes.com
http://sourceforge.net/projects/zsnes
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifdef __LINUX__
#include "gblhdr.h"
#define DIR_SLASH "/"
#else
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
#include <zlib.h>
#ifdef __WIN32__
#include <io.h>
#define ftruncate chsize
#else
#include <unistd.h>
#endif
#define DIR_SLASH "\\"
#endif
#include "gblvars.h"
#include "asm_call.h"
#include "numconv.h"
#ifndef __WIN32__
#define mkdir(path) mkdir(path, (S_IRWXU|S_IRWXG|S_IRWXO)) //0777
#endif
extern unsigned int versionNumber, CRC32, cur_zst_size;
extern unsigned int JoyAOrig, JoyBOrig, JoyCOrig, JoyDOrig, JoyEOrig;
extern unsigned int MsgCount, MessageOn;
extern unsigned char MovieStartMethod, GUIReset, ReturnFromSPCStall, GUIQuit;
extern unsigned char MovieProcessing, *Msgptr, fnamest[512];
extern unsigned char CMovieExt;
extern bool romispal;
void GUIDoReset();
void powercycle(bool);
void zst_sram_load(FILE *);
void zst_save(FILE *, bool);
bool zst_load(FILE *);
/////////////////////////////////////////////////////////
/*
ZMV Format
-----------------------------------------------------------------
Header
-----------------------------------------------------------------
3 bytes - "ZMV"
2 bytes - ZMV Version # (version of ZSNES)
4 bytes - CRC32 of ROM
4 bytes - Number of frames in this movie
4 bytes - Number of rerecords
4 bytes - Number of frames removed by rerecord
4 bytes - Number of frames with slow down
4 bytes - Number of key combos
2 bytes - Number of internal chapters
2 bytes - Length of author name
3 bytes - ZST size
1 byte - Flag Byte
2 bits - Start from ZST/Power On/Reset
1 bit - NTSC or PAL
5 bits - Reserved
ZST size - ZST (no thumbnail)
-----------------------------------------------------------------
Key input - Repeated for all input / internal chapters
-----------------------------------------------------------------
1 byte - Flag Byte
1 bit - Controller 1 changed
1 bit - Controller 2 changed
1 bit - Controller 3 changed
1 bit - Controller 4 changed
1 bit - Controller 5 changed
1 bit - Chapter instead of input here
2 bits - Reserved
-If Chapter-
ZST size - ZST
4 bytes - Frame #
8 bytes - Previous input (60 controller bits [12*5] + 4 padded bits)
-Else-
variable - Input
12 bits per controller where input changed padded to next full byte size
Minimum 2 bytes (12 controller bits + 4 padded bits)
Maximum 8 bytes (60 controller bits [12*5] + 4 padded bits)
-----------------------------------------------------------------
Internal chapter offsets - Repeated for all internal chapters
-----------------------------------------------------------------
4 bytes - Offset to chapter from beginning of file (after input flag byte for ZST)
-----------------------------------------------------------------
External chapters - Repeated for all external chapters
-----------------------------------------------------------------
ZST Size - ZST
4 bytes - Frame #
8 bytes - Previous input (60 controller bits [12*5] + 4 padded bits)
4 bytes - Offset to input for current chapter from beginning of file
-----------------------------------------------------------------
External chapter count
-----------------------------------------------------------------
2 bytes - Number of external chapters
-----------------------------------------------------------------
Author name
-----------------------------------------------------------------
Name Len - Author's name
*/
/*
ZMV header types, vars, and functions
*/
enum zmv_start_methods { zmv_sm_zst, zmv_sm_power, zmv_sm_reset };
enum zmv_video_modes { zmv_vm_ntsc, zmv_vm_pal };
#define INT_CHAP_SIZE (cur_zst_size+4+8)
#define EXT_CHAP_SIZE (cur_zst_size+4+8+4)
#define INT_CHAP_INDEX_SIZE (zmv_vars.header.internal_chapters*4)
#define EXT_CHAP_BLOCK_SIZE (zmv_open_vars.external_chapter_count*EXT_CHAP_SIZE + 2)
#define EXT_CHAP_END_DIST (EXT_CHAP_BLOCK_SIZE + (size_t)zmv_vars.header.author_len)
#define INT_CHAP_END_DIST (INT_CHAP_INDEX_SIZE + EXT_CHAP_END_DIST)
#define EXT_CHAP_COUNT_END_DIST ((size_t)zmv_vars.header.author_len + 2)
struct zmv_header
{
char magic[3];
unsigned short zsnes_version;
unsigned int rom_crc32;
unsigned int frames;
unsigned int rerecords;
unsigned int removed_frames;
unsigned int slow_frames;
unsigned int key_combos;
unsigned short internal_chapters;
unsigned short author_len;
unsigned int zst_size; //We only read/write 3 bytes for this
struct
{
enum zmv_start_methods start_method;
enum zmv_video_modes video_mode;
} zmv_flag;
};
static void zmv_header_write(struct zmv_header *zmv_head, FILE *fp)
{
unsigned char flag = 0;
fwrite(zmv_head->magic, 3, 1, fp);
fwrite2(zmv_head->zsnes_version, fp);
fwrite4(zmv_head->rom_crc32, fp);
fwrite4(zmv_head->frames, fp);
fwrite4(zmv_head->rerecords, fp);
fwrite4(zmv_head->removed_frames, fp);
fwrite4(zmv_head->slow_frames, fp);
fwrite4(zmv_head->key_combos, fp);
fwrite2(zmv_head->internal_chapters, fp);
fwrite2(zmv_head->author_len, fp);
fwrite3(zmv_head->zst_size, fp);
switch (zmv_head->zmv_flag.start_method)
{
case zmv_sm_zst:
flag &= ~BIT(7);
flag &= ~BIT(6);
break;
case zmv_sm_power:
flag |= BIT(7);
flag &= ~BIT(6);
break;
case zmv_sm_reset:
flag &= ~BIT(7);
flag |= BIT(6);
break;
}
switch (zmv_head->zmv_flag.video_mode)
{
case zmv_vm_ntsc:
flag &= ~BIT(5);
break;
case zmv_vm_pal:
flag |= BIT(5);
break;
}
//Not needed, but oh well, it makes it easier to read for some.
//Reserved bits:
flag &= ~BIT(4);
flag &= ~BIT(3);
flag &= ~BIT(2);
flag &= ~BIT(1);
flag &= ~BIT(0);
fwrite(&flag, 1, 1, fp);
}
static bool zmv_header_read(struct zmv_header *zmv_head, FILE *fp)
{
unsigned char flag;
fread(zmv_head->magic, 3, 1, fp);
zmv_head->zsnes_version = fread2(fp);
zmv_head->rom_crc32 = fread4(fp);
zmv_head->frames = fread4(fp);
zmv_head->rerecords = fread4(fp);
zmv_head->removed_frames = fread4(fp);
zmv_head->slow_frames = fread4(fp);
zmv_head->key_combos = fread4(fp);
zmv_head->internal_chapters = fread2(fp);
zmv_head->author_len = fread2(fp);
zmv_head->zst_size = fread3(fp);
fread(&flag, 1, 1, fp);
if (feof(fp))
{
return(false);
}
switch (flag & (BIT(7)|BIT(6)))
{
case 0:
zmv_head->zmv_flag.start_method = zmv_sm_zst;
break;
case BIT(7):
zmv_head->zmv_flag.start_method = zmv_sm_power;
break;
case BIT(6):
zmv_head->zmv_flag.start_method = zmv_sm_reset;
break;
case BIT(7)|BIT(6):
return(false);
break;
}
switch (flag & BIT(5))
{
case 0:
zmv_head->zmv_flag.video_mode = zmv_vm_ntsc;
break;
case BIT(5):
zmv_head->zmv_flag.video_mode = zmv_vm_pal;
break;
}
if (flag & (BIT(4)|BIT(3)|BIT(2)|BIT(1)|BIT(0)))
{
return(false);
}
return(true);
}
/*
Internal chapter types, vars, and functions
*/
#define INTERNAL_CHAPTER_BUF_LIM 10
struct internal_chapter_buf
{
size_t offsets[INTERNAL_CHAPTER_BUF_LIM];
unsigned char used;
struct internal_chapter_buf *next;
};
static void internal_chapter_add_offset(struct internal_chapter_buf *icb, size_t offset)
{
while (icb->next)
{
icb = icb->next;
}
if (icb->used == INTERNAL_CHAPTER_BUF_LIM)
{
icb->next = (struct internal_chapter_buf *)malloc(sizeof(struct internal_chapter_buf));
icb = icb->next;
memset(icb, 0, sizeof(struct internal_chapter_buf));
}
icb->offsets[icb->used] = offset;
icb->used++;
}
static void internal_chapter_free_chain(struct internal_chapter_buf *icb)
{
if (icb)
{
if (icb->next)
{
internal_chapter_free_chain(icb->next);
}
free(icb);
}
}
static void internal_chapter_write(struct internal_chapter_buf *icb, FILE *fp)
{
unsigned char i;
for (i = 0; i < icb->used; i++)
{
fwrite4(icb->offsets[i], fp);
}
if (icb->next)
{
internal_chapter_write(icb->next, fp);
}
}
static void internal_chapter_read(struct internal_chapter_buf *icb, FILE *fp, size_t count)
{
while (count--)
{
internal_chapter_add_offset(icb, fread4(fp));
}
}
size_t internal_chapter_pos(struct internal_chapter_buf *icb, size_t offset)
{
size_t pos = 0;
do
{
unsigned char i;
for (i = 0; i < icb->used; i++, pos++)
{
if (icb->offsets[i] == offset)
{
return(pos);
}
}
} while ((icb = icb->next));
return(~0);
}
static size_t internal_chapter_greater(struct internal_chapter_buf *icb, size_t offset)
{
size_t greater = ~0;
do
{
unsigned char i;
for (i = 0; i < icb->used; i++)
{
if ((icb->offsets[i] > offset) && (icb->offsets[i] < greater))
{
greater = icb->offsets[i];
}
}
} while ((icb = icb->next));
return((greater == ~0) ? offset : greater);
}
static size_t internal_chapter_lesser(struct internal_chapter_buf *icb, size_t offset)
{
size_t lesser = 0;
do
{
unsigned char i;
for (i = 0; i < icb->used; i++)
{
if ((icb->offsets[i] < offset) && (icb->offsets[i] > lesser))
{
lesser = icb->offsets[i];
}
}
} while ((icb = icb->next));
return((lesser == 0) ? offset : lesser);
}
static size_t internal_chapter_delete_after(struct internal_chapter_buf *icb, size_t offset)
{
if (icb->used)
{
size_t last_offset = 0;
if (icb->offsets[0] == offset)
{
last_offset = offset;
}
else
{
last_offset = internal_chapter_lesser(icb, offset);
if (last_offset == offset)
{
internal_chapter_free_chain(icb->next);
icb->next = 0;
icb->used = 0;
return(0);
}
if (internal_chapter_greater(icb, last_offset) == offset)
{
last_offset = offset;
}
}
if (last_offset)
{
size_t buffer_pos = internal_chapter_pos(icb, last_offset);
size_t link = buffer_pos/INTERNAL_CHAPTER_BUF_LIM;
while (link--)
{
icb = icb->next;
}
internal_chapter_free_chain(icb->next);
icb->next = 0;
icb->used = (buffer_pos%INTERNAL_CHAPTER_BUF_LIM)+1;
return(buffer_pos+1);
}
}
return(0);
}
/*
Shared var between record/replay functions
*/
#define WRITE_BUFFER_SIZE 512
static struct
{
struct zmv_header header;
FILE *fp;
struct
{
unsigned short A;
unsigned short B;
unsigned short C;
unsigned short D;
unsigned short E;
} last_joy_state;
unsigned char write_buffer[WRITE_BUFFER_SIZE];
size_t write_buffer_loc;
struct internal_chapter_buf internal_chapters;
size_t last_internal_chapter_offset;
char *filename;
} zmv_vars;
static void save_last_joy_state(unsigned char *buffer)
{
buffer[0] = (zmv_vars.last_joy_state.A >> 4) & 0xFF;
buffer[1] = ((zmv_vars.last_joy_state.A << 4) & 0xF0) | ((zmv_vars.last_joy_state.B >> 8) & 0x0F);
buffer[2] = zmv_vars.last_joy_state.B & 0xFF;
buffer[3] = (zmv_vars.last_joy_state.C >> 4) & 0xFF;
buffer[4] = ((zmv_vars.last_joy_state.C << 4) & 0xF0) | ((zmv_vars.last_joy_state.D >> 8) & 0x0F);
buffer[5] = zmv_vars.last_joy_state.D & 0xFF;
buffer[6] = (zmv_vars.last_joy_state.E >> 4) & 0xFF;
buffer[7] = (zmv_vars.last_joy_state.E << 4) & 0xF0;
}
static void load_last_joy_state(unsigned char *buffer)
{
zmv_vars.last_joy_state.A = (((unsigned short)(buffer[0])) << 4) | ((buffer[1] & 0xF0) >> 4);
zmv_vars.last_joy_state.B = (((unsigned short)(buffer[1] & 0x0F)) << 8) | buffer[2];
zmv_vars.last_joy_state.C = (((unsigned short)(buffer[3])) << 4) | ((buffer[4] & 0xF0) >> 4);
zmv_vars.last_joy_state.D = (((unsigned short)(buffer[4] & 0x0F)) << 8) | buffer[5];
zmv_vars.last_joy_state.E = (((unsigned short)(buffer[6])) << 4) | ((buffer[7] & 0xF0) >> 4);
}
static void write_last_joy_state(FILE *fp)
{
save_last_joy_state(zmv_vars.write_buffer);
fwrite(zmv_vars.write_buffer, 8, 1, fp);
}
static void read_last_joy_state(FILE *fp)
{
fread(zmv_vars.write_buffer, 8, 1, fp);
load_last_joy_state(zmv_vars.write_buffer);
}
static void flush_input_buffer()
{
if (zmv_vars.write_buffer_loc)
{
fwrite(zmv_vars.write_buffer, zmv_vars.write_buffer_loc, 1, zmv_vars.fp);
zmv_vars.write_buffer_loc = 0;
}
zmv_vars.header.author_len = 0; //If we're writing, then author is erased if there
}
/*
Create and record ZMV
*/
static void zmv_create(char *filename)
{
memset(&zmv_vars, 0, sizeof(zmv_vars));
if ((zmv_vars.fp = fopen(filename,"w+b")))
{
size_t filename_len = strlen(filename);
strncpy(zmv_vars.header.magic, "ZMV", 3);
zmv_vars.header.zsnes_version = versionNumber & 0xFFFF;
zmv_vars.header.rom_crc32 = CRC32;
zmv_vars.header.zst_size = cur_zst_size;
zmv_vars.header.zmv_flag.start_method = (enum zmv_start_methods)MovieStartMethod;
zmv_vars.header.zmv_flag.video_mode = romispal ? zmv_vm_pal : zmv_vm_ntsc;
zmv_header_write(&zmv_vars.header, zmv_vars.fp);
switch (zmv_vars.header.zmv_flag.start_method)
{
case zmv_sm_zst:
break;
case zmv_sm_power:
powercycle(true);
break;
case zmv_sm_reset:
GUIReset = 1;
asm_call(GUIDoReset);
ReturnFromSPCStall = 0;
break;
}
zst_save(zmv_vars.fp, false);
zmv_vars.filename = (char *)malloc(filename_len+1); //+1 for null
strcpy(zmv_vars.filename, filename);
}
else
{
}
}
#define RECORD_PAD(prev, cur, bit) \
if ((unsigned short)(cur >> 20) != prev) \
{ \
prev = (unsigned short)(cur >> 20); \
flag |= BIT(bit); \
\
if (nibble & 1) \
{ \
press_buf[nibble/2] |= ((unsigned char)(prev & 0x0F)) << 4; \
nibble++; \
press_buf[nibble/2] = (unsigned char)(prev >> 4); \
nibble += 2; \
} \
else \
{ \
press_buf[nibble/2] = (unsigned char)(prev & 0xFF); \
nibble += 2; \
press_buf[nibble/2] = (unsigned char)(prev >> 8); \
nibble++; \
} \
}
static void zmv_record(bool slow, unsigned char combos_used)
{
unsigned char flag = 0;
unsigned char press_buf[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
unsigned char nibble = 0;
zmv_vars.header.frames++;
if (slow) { zmv_vars.header.slow_frames++; }
zmv_vars.header.key_combos += combos_used;
RECORD_PAD(zmv_vars.last_joy_state.A, JoyAOrig, 7);
RECORD_PAD(zmv_vars.last_joy_state.B, JoyBOrig, 6);
RECORD_PAD(zmv_vars.last_joy_state.C, JoyCOrig, 5);
RECORD_PAD(zmv_vars.last_joy_state.D, JoyDOrig, 4);
RECORD_PAD(zmv_vars.last_joy_state.E, JoyEOrig, 3);
zmv_vars.write_buffer[zmv_vars.write_buffer_loc] = flag;
zmv_vars.write_buffer_loc++;
if (flag)
{
unsigned char buffer_used = (nibble/2) + (nibble&1);
memcpy(zmv_vars.write_buffer+zmv_vars.write_buffer_loc, press_buf, buffer_used);
zmv_vars.write_buffer_loc += buffer_used;
}
if (zmv_vars.write_buffer_loc > WRITE_BUFFER_SIZE - (1+sizeof(press_buf)))
{
flush_input_buffer();
}
}
static bool zmv_insert_chapter()
{
if ((zmv_vars.header.internal_chapters < 65535) && zmv_vars.header.frames &&
(zmv_vars.last_internal_chapter_offset != (ftell(zmv_vars.fp) + zmv_vars.write_buffer_loc - INT_CHAP_SIZE)))
{
unsigned char flag = BIT(2);
flush_input_buffer();
fwrite(&flag, 1, 1, zmv_vars.fp);
internal_chapter_add_offset(&zmv_vars.internal_chapters, ftell(zmv_vars.fp));
zmv_vars.header.internal_chapters++;
zmv_vars.last_internal_chapter_offset = ftell(zmv_vars.fp);
zst_save(zmv_vars.fp, false);
fwrite4(zmv_vars.header.frames, zmv_vars.fp);
write_last_joy_state(zmv_vars.fp);
return(true);
}
return(false);
}
static void zmv_record_finish()
{
flush_input_buffer();
internal_chapter_write(&zmv_vars.internal_chapters, zmv_vars.fp);
internal_chapter_free_chain(zmv_vars.internal_chapters.next);
free(zmv_vars.filename);
fwrite2(0, zmv_vars.fp); //External chapter count
rewind(zmv_vars.fp);
zmv_header_write(&zmv_vars.header, zmv_vars.fp);
fclose(zmv_vars.fp);
}
static size_t zmv_frames_recorded()
{
return(zmv_vars.header.frames);
}
/*
Open and replay ZMV
*/
typedef struct internal_chapter_buf external_chapter_buf;
static struct
{
external_chapter_buf external_chapters;
unsigned short external_chapter_count;
unsigned int frames_replayed;
size_t last_chapter_frame;
size_t input_start_pos;
} zmv_open_vars; //Additional vars for open/replay of a ZMV
static bool zmv_open(char *filename)
{
memset(&zmv_vars, 0, sizeof(zmv_vars));
memset(&zmv_open_vars, 0, sizeof(zmv_open_vars));
zmv_vars.fp = fopen(filename,"r+b");
if (zmv_vars.fp && zmv_header_read(&zmv_vars.header, zmv_vars.fp) &&
!strncmp(zmv_vars.header.magic, "ZMV", 3))
{
unsigned short i;
size_t filename_len = strlen(filename);
if (zmv_vars.header.rom_crc32 != CRC32)
{
}
MovieStartMethod = (unsigned char)zmv_vars.header.zmv_flag.start_method;
if (zmv_vars.header.zsnes_version != (versionNumber & 0xFFFF))
{
zst_load(zmv_vars.fp);
Msgptr = "MOVIE VERSION MISMATCH - MAY DESYNC.";
}
else
{
switch (zmv_vars.header.zmv_flag.start_method)
{
case zmv_sm_zst:
zst_load(zmv_vars.fp);
break;
case zmv_sm_power:
powercycle(false);
zst_sram_load(zmv_vars.fp);
break;
case zmv_sm_reset:
GUIReset = 1;
asm_call(GUIDoReset);
ReturnFromSPCStall = 0;
zst_sram_load(zmv_vars.fp);
break;
}
zmv_open_vars.input_start_pos = ftell(zmv_vars.fp);
Msgptr = "MOVIE STARTED.";
}
fseek(zmv_vars.fp, -(EXT_CHAP_COUNT_END_DIST), SEEK_END);
zmv_open_vars.external_chapter_count = fread2(zmv_vars.fp);
fseek(zmv_vars.fp, -(INT_CHAP_END_DIST), SEEK_END);
internal_chapter_read(&zmv_vars.internal_chapters, zmv_vars.fp, zmv_vars.header.internal_chapters);
for (i = 0; i < zmv_open_vars.external_chapter_count; i++)
{
//Seek to 4 bytes before end of chapter, since last 4 bytes is where it contains offset value
fseek(zmv_vars.fp, EXT_CHAP_SIZE-4, SEEK_CUR);
internal_chapter_add_offset(&zmv_open_vars.external_chapters, fread4(zmv_vars.fp));
}
fseek(zmv_vars.fp, zmv_open_vars.input_start_pos, SEEK_SET);
zmv_vars.filename = (char *)malloc(filename_len+1); //+1 for null
strcpy(zmv_vars.filename, filename);
return(true);
}
return(false);
}
#define REPLAY_PAD(prev, cur, bit) \
if (flag & BIT(bit)) \
{ \
if (mid_byte) \
{ \
prev = (byte & 0xF0) >> 4; \
fread(&byte, 1, 1, zmv_vars.fp); \
prev |= ((unsigned long)byte) << 4; \
mid_byte = false; \
} \
else \
{ \
fread(&byte, 1, 1, zmv_vars.fp); \
prev = byte; \
fread(&byte, 1, 1, zmv_vars.fp); \
prev |= ((unsigned long)(byte & 0xF)) << 8; \
mid_byte = true; \
} \
} \
cur = (((unsigned int)prev) << 20) | 0x8000;
static bool zmv_replay()
{
if (zmv_open_vars.frames_replayed < zmv_vars.header.frames)
{
unsigned char flag = 0;
unsigned char byte;
bool mid_byte = false;
fread(&flag, 1, 1, zmv_vars.fp);
if (flag & BIT(2))
{
fseek(zmv_vars.fp, INT_CHAP_SIZE, SEEK_CUR);
fread(&flag, 1, 1, zmv_vars.fp);
}
REPLAY_PAD(zmv_vars.last_joy_state.A, JoyAOrig, 7);
REPLAY_PAD(zmv_vars.last_joy_state.B, JoyBOrig, 6);
REPLAY_PAD(zmv_vars.last_joy_state.C, JoyCOrig, 5);
REPLAY_PAD(zmv_vars.last_joy_state.D, JoyDOrig, 4);
REPLAY_PAD(zmv_vars.last_joy_state.E, JoyEOrig, 3);
zmv_open_vars.frames_replayed++;
return(true);
}
return(false);
}
static bool zmv_next_chapter()
{
size_t current_loc = ftell(zmv_vars.fp);
size_t next_internal = internal_chapter_greater(&zmv_vars.internal_chapters, current_loc);
size_t next_external = internal_chapter_greater(&zmv_open_vars.external_chapters, current_loc);
size_t next = 0;
if (next_internal != current_loc)
{
next = next_internal;
}
else
{
next_internal = ~0;
}
if ((next_external != current_loc) && next_external < next_internal)
{
next = next_external;
}
if (next)
{
if (next == next_internal)
{
fseek(zmv_vars.fp, next_internal, SEEK_SET);
zst_load(zmv_vars.fp);
zmv_open_vars.frames_replayed = fread4(zmv_vars.fp);
read_last_joy_state(zmv_vars.fp);
}
else
{
size_t ext_chapter_loc = EXT_CHAP_END_DIST - internal_chapter_pos(&zmv_open_vars.external_chapters, next)*EXT_CHAP_SIZE;
fseek(zmv_vars.fp, -(ext_chapter_loc), SEEK_END);
zst_load(zmv_vars.fp);
zmv_open_vars.frames_replayed = fread4(zmv_vars.fp);
read_last_joy_state(zmv_vars.fp);
fseek(zmv_vars.fp, next_external, SEEK_SET);
}
return(true);
}
return(false);
}
static void zmv_rewind_playback()
{
fseek(zmv_vars.fp, zmv_open_vars.input_start_pos - cur_zst_size, SEEK_SET);
zst_load(zmv_vars.fp);
zmv_open_vars.frames_replayed = 0;
zmv_open_vars.last_chapter_frame = 0;
}
static void zmv_prev_chapter()
{
size_t current_loc = ftell(zmv_vars.fp);
size_t prev_internal = internal_chapter_lesser(&zmv_vars.internal_chapters, current_loc);
size_t prev_external = internal_chapter_lesser(&zmv_open_vars.external_chapters, current_loc);
size_t prev = 0;
if (prev_internal != current_loc)
{
prev = prev_internal;
}
else
{
prev_internal = 0;
}
if ((prev_external != current_loc) && prev_external > prev_internal)
{
prev = prev_external;
}
if (!prev)
{
zmv_rewind_playback();
return;
}
//Code to go back before the previous chapter if the previous chapter was loaded recently
if (zmv_open_vars.frames_replayed - zmv_open_vars.last_chapter_frame < 150) //2.5 seconds NTSC
{
size_t pprev = prev-1;
size_t pprev_internal = internal_chapter_lesser(&zmv_vars.internal_chapters, pprev);
size_t pprev_external = internal_chapter_lesser(&zmv_open_vars.external_chapters, pprev);
if ((pprev_internal == pprev) && (pprev_external == pprev))
{
zmv_rewind_playback();
return;
}
if (pprev_internal != pprev)
{
prev = prev_internal = pprev_internal;
}
else
{
pprev_internal = 0;
}
if ((pprev_external != pprev) && pprev_external > pprev_internal)
{
prev = prev_external = pprev_external;
}
}
if (prev == prev_internal)
{
fseek(zmv_vars.fp, prev_internal, SEEK_SET);
zst_load(zmv_vars.fp);
zmv_open_vars.frames_replayed = fread4(zmv_vars.fp);
read_last_joy_state(zmv_vars.fp);
}
else
{
size_t ext_chapter_loc = EXT_CHAP_END_DIST - internal_chapter_pos(&zmv_open_vars.external_chapters, prev)*EXT_CHAP_SIZE;
fseek(zmv_vars.fp, -(ext_chapter_loc), SEEK_END);
zst_load(zmv_vars.fp);
zmv_open_vars.frames_replayed = fread4(zmv_vars.fp);
read_last_joy_state(zmv_vars.fp);
fseek(zmv_vars.fp, prev_external, SEEK_SET);
}
zmv_open_vars.last_chapter_frame = zmv_open_vars.frames_replayed;
}
static void zmv_add_chapter()
{
if ((zmv_open_vars.external_chapter_count < 65535) && zmv_open_vars.frames_replayed)
{
size_t current_loc = ftell(zmv_vars.fp);
//Check if previous input contained internal chapter to here, or if there is external here already
if ((internal_chapter_pos(&zmv_vars.internal_chapters, current_loc-(INT_CHAP_SIZE)) == ~0) &&
(internal_chapter_pos(&zmv_open_vars.external_chapters, current_loc)) == ~0)
{
//Check if we have internal right here
unsigned char flag;
fread(&flag, 1, 1, zmv_vars.fp);
if (!(flag & BIT(2)))
{
char *author = 0;
internal_chapter_add_offset(&zmv_open_vars.external_chapters, current_loc);
zmv_open_vars.external_chapter_count++;
if (zmv_vars.header.author_len)
{
if ((author = (char *)malloc(zmv_vars.header.author_len)))
{
fseek(zmv_vars.fp, -(zmv_vars.header.author_len), SEEK_END);
fread(author, zmv_vars.header.author_len, 1, zmv_vars.fp);
}
}
fseek(zmv_vars.fp, -(EXT_CHAP_COUNT_END_DIST), SEEK_END);
zst_save(zmv_vars.fp, false);
fwrite4(zmv_open_vars.frames_replayed, zmv_vars.fp);
write_last_joy_state(zmv_vars.fp);
fwrite4(current_loc, zmv_vars.fp);
fwrite2(zmv_open_vars.external_chapter_count, zmv_vars.fp);
if (author)
{
fwrite(author, zmv_vars.header.author_len, 1, zmv_vars.fp);
free(author);
}
fseek(zmv_vars.fp, current_loc, SEEK_SET);
}
else //Just skip the internal
{
fseek(zmv_vars.fp, INT_CHAP_SIZE, SEEK_CUR);
}
}
}
}
static void zmv_replay_finished()
{
internal_chapter_free_chain(zmv_vars.internal_chapters.next);
internal_chapter_free_chain(zmv_open_vars.external_chapters.next);
free(zmv_vars.filename);
fclose(zmv_vars.fp);
}
static void zmv_replay_to_record()
{
internal_chapter_free_chain(zmv_open_vars.external_chapters.next);
zmv_vars.header.rerecords++;
zmv_vars.header.removed_frames += zmv_vars.header.frames - zmv_open_vars.frames_replayed;
zmv_vars.header.frames = zmv_open_vars.frames_replayed;
zmv_vars.header.internal_chapters = internal_chapter_delete_after(&zmv_vars.internal_chapters, ftell(zmv_vars.fp));
zmv_vars.last_internal_chapter_offset = internal_chapter_lesser(&zmv_vars.internal_chapters, ~0);
ftruncate(fileno(zmv_vars.fp), ftell(zmv_vars.fp));
}
static size_t zmv_frames_replayed()
{
return(zmv_open_vars.frames_replayed);
}
/*
Rewind related functions and vars
*/
unsigned char *zmv_rewind_buffer = 0;
static void zmv_alloc_rewind_buffer(unsigned char rewind_states)
{
zmv_rewind_buffer = (unsigned char *)malloc(16*rewind_states);
}
static void zmv_dealloc_rewind_buffer()
{
if (zmv_rewind_buffer)
{
free(zmv_rewind_buffer);
zmv_rewind_buffer = 0;
}
}
void zmv_rewind_save(size_t state, bool playback)
{
unsigned char *state_start_pos = zmv_rewind_buffer + 16*state;
size_t file_pos = ftell(zmv_vars.fp) + zmv_vars.write_buffer_loc;
save_last_joy_state(state_start_pos);
memcpy(state_start_pos+8, &file_pos, 4);
memcpy(state_start_pos+12, playback ? &zmv_open_vars.frames_replayed : &zmv_vars.header.frames, 4);
}
void zmv_rewind_load(size_t state, bool playback)
{
unsigned char *state_start_pos = zmv_rewind_buffer + 16*state;
size_t file_pos = 0;
load_last_joy_state(state_start_pos);
memcpy(&file_pos, state_start_pos+8, 4);
if (playback)
{
memcpy(&zmv_open_vars.frames_replayed, state_start_pos+12, 4);
fseek(zmv_vars.fp, file_pos, SEEK_SET);
}
else
{
size_t frame = 0;
memcpy(&frame, state_start_pos+12, 4);
zmv_vars.header.rerecords++;
zmv_vars.header.removed_frames += zmv_vars.header.frames - frame;
zmv_vars.header.frames = frame;
flush_input_buffer();
fseek(zmv_vars.fp, file_pos, SEEK_SET);
ftruncate(fileno(zmv_vars.fp), file_pos);
zmv_vars.header.internal_chapters = internal_chapter_delete_after(&zmv_vars.internal_chapters, file_pos);
zmv_vars.last_internal_chapter_offset = internal_chapter_lesser(&zmv_vars.internal_chapters, ~0);
}
}
/*
Save and load MZT
*/
void mzt_chdir()
{
size_t filename_len = strlen(zmv_vars.filename);
memcpy(zmv_vars.filename+filename_len-3, "mz", 2);
if (!isdigit(zmv_vars.filename[filename_len-1]))
{
zmv_vars.filename[filename_len-1] = 't';
}
chdir(zmv_vars.filename);
}
//Currently this doesn't work right in playback
bool mzt_save(char *statename, bool thumb, bool playback)
{
size_t filename_len = strlen(zmv_vars.filename);
struct stat stat_buffer;
bool mzt_saved = false;
memcpy(zmv_vars.filename+filename_len-3, "mz", 2);
if (!isdigit(zmv_vars.filename[filename_len-1]))
{
zmv_vars.filename[filename_len-1] = 't';
}
if (stat(zmv_vars.filename, &stat_buffer))
{
mkdir(zmv_vars.filename);
}
if (!chdir(zmv_vars.filename))
{
FILE *fp = 0;
if ((fp = fopen(statename,"wb")))
{
char FileExt[3];
gzFile gzp = 0;
size_t rewind_point;
zst_save(fp, thumb);
fclose(fp);
flush_input_buffer();
rewind_point = ftell(zmv_vars.fp);
internal_chapter_write(&zmv_vars.internal_chapters, zmv_vars.fp);
memcpy(FileExt, statename+filename_len-3, 3);
memcpy(statename+filename_len-3, "zm", 2);
if (!isdigit(statename[filename_len-1]))
{
statename[filename_len-1] = 'v';
}
if ((gzp = gzopen(statename, "wb9")))
{
rewind(zmv_vars.fp);
zmv_header_write(&zmv_vars.header, zmv_vars.fp);
rewind(zmv_vars.fp);
while (!feof(zmv_vars.fp))
{
size_t amount_read = fread(zmv_vars.write_buffer, 1, WRITE_BUFFER_SIZE, zmv_vars.fp);
gzwrite(gzp, zmv_vars.write_buffer, amount_read);
}
gzclose(gzp);
memcpy(statename+filename_len-3, "mz", 2);
if (!isdigit(statename[filename_len-1]))
{
statename[filename_len-1] = 'i';
}
if ((fp = fopen(statename,"wb")))
{
fwrite4((playback) ? zmv_open_vars.frames_replayed : zmv_vars.header.frames, fp);
write_last_joy_state(fp);
fwrite4(rewind_point, fp);
fclose(fp);
mzt_saved = true;
}
fseek(zmv_vars.fp, rewind_point, SEEK_SET);
}
memcpy(statename+filename_len-3, FileExt, 3);
}
chdir("..");
}
return(mzt_saved);
}
bool mzt_load(char *statename, bool playback)
{
size_t filename_len = strlen(zmv_vars.filename);
bool mzt_saved = false;
memcpy(zmv_vars.filename+filename_len-3, "mz", 2);
if (!isdigit(zmv_vars.filename[filename_len-1]))
{
zmv_vars.filename[filename_len-1] = 't';
}
if (!chdir(zmv_vars.filename))
{
FILE *fp = 0;
if ((fp = fopen(statename,"rb")))
{
char FileExt[3];
zst_load(fp);
fclose(fp);
memcpy(FileExt, statename+filename_len-3, 3);
memcpy(statename+filename_len-3, "mz", 2);
if (!isdigit(statename[filename_len-1]))
{
statename[filename_len-1] = 'i';
}
if ((fp = fopen(statename,"rb")))
{
size_t rewind_point;
size_t current_frame = fread4(fp);
read_last_joy_state(fp);
rewind_point = fread4(fp);
fclose(fp);
if (!playback)
{
gzFile gzp = 0;
memcpy(statename+filename_len-3, "zm", 2);
if (!isdigit(statename[filename_len-1]))
{
statename[filename_len-1] = 'v';
}
if ((gzp = gzopen(statename, "rb")))
{
size_t rerecords = zmv_vars.header.rerecords+1;
size_t removed_frames = zmv_vars.header.removed_frames + (zmv_vars.header.frames - current_frame);
internal_chapter_free_chain(zmv_vars.internal_chapters.next);
memset(&zmv_vars.internal_chapters, 0, sizeof(struct internal_chapter_buf));
rewind(zmv_vars.fp);
while (!gzeof(gzp))
{
size_t amount_read = gzread(gzp, zmv_vars.write_buffer, WRITE_BUFFER_SIZE);
fwrite(zmv_vars.write_buffer, 1, amount_read, zmv_vars.fp);
}
gzclose(gzp);
rewind(zmv_vars.fp);
zmv_header_read(&zmv_vars.header, zmv_vars.fp);
zmv_vars.header.removed_frames = removed_frames;
zmv_vars.header.rerecords = rerecords;
zmv_vars.write_buffer_loc = 0;
fseek(zmv_vars.fp, rewind_point, SEEK_SET);
internal_chapter_read(&zmv_vars.internal_chapters, zmv_vars.fp, zmv_vars.header.internal_chapters);
fseek(zmv_vars.fp, rewind_point, SEEK_SET);
zmv_vars.last_internal_chapter_offset = internal_chapter_lesser(&zmv_vars.internal_chapters, ~0);
ftruncate(fileno(zmv_vars.fp), ftell(zmv_vars.fp));
}
}
else
{
zmv_open_vars.frames_replayed = current_frame;
fseek(zmv_vars.fp, rewind_point, SEEK_SET);
}
mzt_saved = true;
}
memcpy(statename+filename_len-3, FileExt, 3);
}
chdir("..");
}
return(mzt_saved);
}
/////////////////////////////////////////////////////////
/*
Nach's insane subtitle library for movies files :)
The filename would be gamename.sub in the same directory the ZMV would be in.
If you're playing gamename.zm1, then the sub file will be gamename.su1 etc...
Format of the sub file:
Start Frame:Frame Duration:Message
Example:
1:180:Hi how are you?
300:180:Isn't this cool?
700:180:This is great :)
2500:375:Kill 'em!
3500:20:Did you see this? Of course not
*/
static struct
{
FILE *fp;
char linebuf[256];
size_t message_start;
size_t message_duration;
} MovieSub;
static void MovieSub_Open(const char *filename)
{
memset(&MovieSub, 0, sizeof(MovieSub));
MovieSub.fp = fopen(filename, "r");
}
static void MovieSub_Close()
{
if (MovieSub.fp)
{
fclose(MovieSub.fp);
MovieSub.fp = 0;
}
}
static char *MovieSub_GetData(size_t frame_count)
{
if (MovieSub.fp)
{
char *i, *num;
if (frame_count > MovieSub.message_start + MovieSub.message_duration)
{
MovieSub.message_duration = 0;
do
{
if (!fgets(MovieSub.linebuf, 256, MovieSub.fp))
{
return(0);
}
if (!(num = strtok(MovieSub.linebuf, ":"))) { return(0); }
for (i = num; *i; i++)
{
if (!isdigit(*i)) { return(0); }
}
MovieSub.message_start = atoi(num);
} while(MovieSub.message_start < zmv_frames_replayed());
if (!(num = strtok(0, ":"))) { return(0); }
for (i = num; *i; i++)
{
if (!isdigit(*i))
{
MovieSub.message_start = 0;
return(0);
}
}
MovieSub.message_duration = atoi(num);
}
if (frame_count == MovieSub.message_start)
{
return(strtok(0, ":"));
}
}
return(0);
}
static void MovieSub_ResetStream()
{
if (MovieSub.fp)
{
rewind(MovieSub.fp);
MovieSub.message_start = 0;
MovieSub.message_duration = 0;
}
}
static size_t MovieSub_GetDuration()
{
return(MovieSub.message_duration);
}
/////////////////////////////////////////////////////////
enum MovieStatus { MOVIE_OFF = 0, MOVIE_PLAYBACK, MOVIE_RECORD, MOVIE_OLD_PLAY };
#define SetMovieMode(mode) (MovieProcessing = (unsigned char)mode)
extern bool SRAMState, SloMo50;
bool PrevSRAMState;
extern unsigned int statefileloc;
extern unsigned char ComboCounter, MovieRecordWinVal, RewindStates;
char MovieFrameStr[10];
void SRAMChdir();
void ChangetoLOADdir();
/*
Code to playback old ZMVs
*/
struct
{
FILE *fp;
size_t frames_replayed;
struct
{
unsigned int A;
unsigned int B;
unsigned int C;
unsigned int D;
unsigned int E;
} last_joy_state;
} old_movie;
static void OldMovieReplay()
{
unsigned char byte;
if (fread(&byte, 1, 1, old_movie.fp))
{
if (byte < 2) // 1 or 0 are correct values
{
char *sub;
if (byte == 0) // 0 means the input has changed
{
fread(&old_movie.last_joy_state.A, 1, 4, old_movie.fp);
fread(&old_movie.last_joy_state.B, 1, 4, old_movie.fp);
fread(&old_movie.last_joy_state.C, 1, 4, old_movie.fp);
fread(&old_movie.last_joy_state.D, 1, 4, old_movie.fp);
fread(&old_movie.last_joy_state.E, 1, 4, old_movie.fp);
}
JoyAOrig = old_movie.last_joy_state.A;
JoyBOrig = old_movie.last_joy_state.B;
JoyCOrig = old_movie.last_joy_state.C;
JoyDOrig = old_movie.last_joy_state.D;
JoyEOrig = old_movie.last_joy_state.E;
if ((sub = MovieSub_GetData(old_movie.frames_replayed)))
{
Msgptr = sub;
MessageOn = MovieSub_GetDuration();
}
old_movie.frames_replayed++;
}
else // anything else is bad - the file isn't a movie.
{
SetMovieMode(MOVIE_OFF);
MessageOn = 0;
fclose(old_movie.fp);
MovieSub_Close();
}
}
else
{
if (old_movie.frames_replayed)
{
Msgptr = "MOVIE FINISHED.";
}
else
{
Msgptr = "STATE LOADED.";
}
MessageOn = MsgCount;
SetMovieMode(MOVIE_OFF);
fclose(old_movie.fp);
MovieSub_Close();
}
}
static void OldMoviePlay(FILE *fp)
{
unsigned char RecData[16];
extern unsigned char NextLineCache, soundon, sramsavedis;
extern size_t Totalbyteloaded;
extern unsigned int curexecstate;
extern unsigned int nmiprevaddrl, nmiprevaddrh, nmirept, nmiprevline, nmistatus;
void loadstate2();
memset(&old_movie, 0, sizeof(old_movie));
old_movie.fp = fp;
loadstate2();
fseek(fp, Totalbyteloaded, SEEK_SET);
fread(RecData, 1, 16, fp);
printf("Movie made with version: %d\n", RecData[1]);
if (RecData[2] == 1)
{
timer2upd = bytes_to_uint32(RecData+3);
curexecstate = bytes_to_uint32(RecData+7);
nmiprevaddrl = 0;
nmiprevaddrh = 0;
nmirept = 0;
nmiprevline = 224;
nmistatus = 0;
spcnumread = 0;
spchalted = 0xFFFFFFFF;
NextLineCache = 0;
}
if (soundon == RecData[0])
{
if (ramsize) { fread(sram, 1, ramsize, fp); }
SetMovieMode(MOVIE_OLD_PLAY);
sramsavedis = 1;
DSPMem[0x08] = 0;
DSPMem[0x18] = 0;
DSPMem[0x28] = 0;
DSPMem[0x38] = 0;
DSPMem[0x48] = 0;
DSPMem[0x58] = 0;
DSPMem[0x68] = 0;
DSPMem[0x78] = 0;
Msgptr = "OLD MOVIE REPLAYING.";
}
else
{
Msgptr = (!soundon) ? "MUST PLAY WITH SOUND ON." : "MUST PLAY WITH SOUND OFF.";
fclose(fp);
}
MessageOn = MsgCount;
}
void MovieInsertChapter()
{
switch (MovieProcessing)
{
case MOVIE_PLAYBACK: // replaying - external
zmv_add_chapter();
Msgptr = "EXTERNAL CHAPTER ADDED.";
break;
case MOVIE_RECORD: // recording - internal
if (zmv_insert_chapter())
{
Msgptr = "INTERNAL CHAPTER ADDED.";
}
else
{
Msgptr = "";
}
break;
case MOVIE_OLD_PLAY:
Msgptr = "OLD MOVIES DO NOT SUPPORT CHAPTERS.";
break;
default: // no movie processing
Msgptr = "NO MOVIE PROCESSING.";
}
MessageOn = MsgCount;
}
void MovieSeekAhead()
{
switch (MovieProcessing)
{
case MOVIE_PLAYBACK: // replay seeking ok
if (zmv_next_chapter()) { Msgptr = "NEXT CHAPTER LOADED."; }
else { Msgptr = "NO CHAPTERS AHEAD."; }
break;
case MOVIE_RECORD: // record will use MZTs
Msgptr = "NO SEEKING DURING RECORD.";
break;
case MOVIE_OLD_PLAY:
Msgptr = "OLD MOVIES DO NOT SUPPORT CHAPTERS.";
break;
default:
Msgptr = "NO MOVIE PROCESSING.";
}
MessageOn = MsgCount;
}
void MovieSeekBehind()
{
switch (MovieProcessing)
{
case MOVIE_PLAYBACK: // replay seeking ok
zmv_prev_chapter();
MovieSub_ResetStream();
Msgptr = "PREVIOUS CHAPTER LOADED.";
break;
case MOVIE_RECORD: // record will use MZTs
Msgptr = "NO SEEKING DURING RECORD.";
break;
case MOVIE_OLD_PLAY:
Msgptr = "OLD MOVIES DO NOT SUPPORT CHAPTERS.";
break;
default:
Msgptr = "NO MOVIE PROCESSING.";
}
MessageOn = MsgCount;
}
void Replay()
{
if (zmv_replay())
{
char *sub;
if ((sub = MovieSub_GetData(zmv_frames_replayed())))
{
Msgptr = sub;
MessageOn = MovieSub_GetDuration();
}
}
else
{
if (zmv_frames_replayed())
{
Msgptr = "MOVIE FINISHED.";
}
else
{
Msgptr = "STATE LOADED.";
}
MessageOn = MsgCount;
SetMovieMode(MOVIE_OFF);
zmv_replay_finished();
zmv_dealloc_rewind_buffer();
MovieSub_Close();
SRAMState = PrevSRAMState;
}
}
void ProcessMovies()
{
switch (MovieProcessing)
{
case MOVIE_PLAYBACK:
Replay();
break;
case MOVIE_RECORD:
zmv_record(SloMo50 ? true : false, ComboCounter);
break;
case MOVIE_OLD_PLAY:
OldMovieReplay();
break;
}
}
void SkipMovie()
{
MovieRecordWinVal = 0;
}
void MovieStop()
{
if (MovieProcessing)
{
switch (MovieProcessing)
{
case MOVIE_PLAYBACK:
zmv_replay_finished();
MovieSub_Close();
MessageOn = 0;
break;
case MOVIE_RECORD:
zmv_record_finish();
if (!zmv_frames_recorded())
{
Msgptr = "STATE SAVED.";
MessageOn = MsgCount;
}
break;
case MOVIE_OLD_PLAY:
fclose(old_movie.fp);
MovieSub_Close();
MessageOn = 0;
break;
}
zmv_dealloc_rewind_buffer();
SetMovieMode(MOVIE_OFF);
SRAMState = PrevSRAMState;
}
}
void MoviePlay()
{
if (!MovieProcessing)
{
unsigned char FileExt[4];
FILE *fp;
PrevSRAMState = SRAMState;
SRAMState = true;
GUIQuit = 2;
memcpy(FileExt, &fnamest[statefileloc-3], 4);
memcpy(&fnamest[statefileloc-3], ".zmv", 4);
fnamest[statefileloc] = CMovieExt;
SRAMChdir();
if ((fp = fopen(fnamest+1, "rb")))
{
char header_buf[3];
fread(header_buf, 3, 1, fp);
if (!strncmp("ZMV", header_buf, 3)) //New Enhanced Format
{
fclose(fp);
if (zmv_open(fnamest+1))
{
zmv_alloc_rewind_buffer(RewindStates);
SetMovieMode(MOVIE_PLAYBACK);
memcpy(&fnamest[statefileloc-3], ".sub", 4);
if (isdigit(CMovieExt)) { fnamest[statefileloc] = CMovieExt; }
MovieSub_Open(fnamest+1);
MessageOn = MsgCount;
}
else
{
Msgptr = "MOVIE COULD NOT BE STARTED.";
MessageOn = MsgCount;
}
}
else //Old Pathetic Format
{
OldMoviePlay(fp);
}
}
else
{
Msgptr = "MOVIE COULD NOT BE OPENED.";
MessageOn = MsgCount;
}
memcpy(&fnamest[statefileloc-3], FileExt, 4);
asm_call(ChangetoLOADdir);
}
}
void MovieRecord()
{
if (MovieProcessing == MOVIE_PLAYBACK)
{
zmv_replay_to_record();
MovieProcessing = 2;
}
if (!MovieProcessing)
{
unsigned char FileExt[4];
FILE *tempfhandle;
memcpy(FileExt, &fnamest[statefileloc-3], 4);
memcpy(&fnamest[statefileloc-3], ".zmv", 4);
fnamest[statefileloc] = CMovieExt;
SRAMChdir();
if (MovieRecordWinVal == 1)
{
remove(fnamest+1);
MovieRecordWinVal = 0;
}
if (!(tempfhandle = fopen(fnamest+1,"rb")))
{
PrevSRAMState = SRAMState;
SRAMState = true;
zmv_create(fnamest+1);
zmv_alloc_rewind_buffer(RewindStates);
SetMovieMode(MOVIE_RECORD);
Msgptr = "MOVIE RECORDING.";
MessageOn = MsgCount;
}
else
{
fclose(tempfhandle);
MovieRecordWinVal = 1;
}
asm_call(ChangetoLOADdir);
memcpy (&fnamest[statefileloc-3], FileExt, 4);
}
}
void GetMovieFrameStr()
{
*MovieFrameStr = 0;
switch (MovieProcessing)
{
case MOVIE_PLAYBACK:
sprintf(MovieFrameStr, "%u",zmv_frames_replayed());
break;
case MOVIE_RECORD:
sprintf(MovieFrameStr, "%u",zmv_frames_recorded());
break;
case MOVIE_OLD_PLAY:
sprintf(MovieFrameStr, "%u",old_movie.frames_replayed);
break;
}
}