Post subject: Convert .fmv to .vmv files
Joined: 4/11/2004
Posts: 155
Location: Fairfax, VA, USA
Converting an .fmv to a .vmv is slighly more complicated than converting in the other direction. Before you convert, you need to make a movie that was recorded from reset. It's easy though, just go to Options->Movie in VirtuaNES, and check the box "Start a record from reset". Open the ROM, record a movie, stop the movie, then exit. Once you have that, then here's in the C code that will do the conversion:
// convert .fmv to .vmv
// example:
// convert C:\GAMES\fam510\genisto-smb2.fmv "C:\GAMES\VirtuaNes\movie\Super Mario Bros 2 (U) (PRG 0).vmv" smb2.vmv
// convert C:\GAMES\fam510\sleepzteam-zelda.fmv "C:\GAMES\VirtuaNes\movie\Legend of Zelda, The (U) (PRG 0).vmv" zelda.vmv

static unsigned long crc_table[256];

void gen_crc_table(void)
{
	unsigned long crc, poly;
	int i, j;

	poly=0x04c11db7L;
	for(i=0; i<256; i++)
	{
		crc=i<<24;
		for(j=8; j>0; j--)
		{
			if(crc&0x80000000L)
				crc=(crc<<1)^poly;
			else
				crc<<=1;
		}
		crc_table[i]=crc;
	}
}

unsigned long get_crc(unsigned char* buf, int length)
{
	register unsigned long crc;
	int i;

	crc=0xffffffffL;
	for(i=0; i<length; ++i)
	{
		crc=(crc<<8)^crc_table[(crc>>24)^buf[i]];
	}

	return crc^0xffffffffL;
}

static const char* vmv_header_string="VirtuaNES MV\x00\x03\x78\x00";

int main(int argc, char** argv)
{
	if(argc<4)
	{
		printf("usage: convert <input_fmv_file> <input_vmv_from_reset> <output_vmv_file>\n");
		exit(0);
	}

	FILE* f_in=fopen(argv[2], "rb");
	if(!f_in)
	{
		perror("fopen\n");
		exit(0);
	}

	unsigned char vmv_header_buf[0x40];
	fread(vmv_header_buf, 1, 0x40, f_in);
	unsigned long save_state_len=*(unsigned long*)(&vmv_header_buf[0x34])-0x40;
	unsigned long save_state_chksum=*(unsigned long*)(&vmv_header_buf[0x14]);
	unsigned char* save_state_buf=(unsigned char*)malloc(save_state_len);
	fread(save_state_buf, 1, save_state_len, f_in);
	fclose(f_in);

	memset(vmv_header_buf, 0, 0x40);
	memcpy(vmv_header_buf, vmv_header_string, 0x10);

	f_in=fopen(argv[1], "rb");
	if(!f_in)
	{
		perror("fopen\n");
		exit(0);
	}

	unsigned char ctr_flags;
	unsigned long rerecord_count;
	unsigned long num_samples;
	fseek(f_in, 5, SEEK_SET);
	fread(&ctr_flags, 1, 1, f_in);
	fseek(f_in, 10, SEEK_SET);
	fread(&rerecord_count, 1, 4, f_in);
	fseek(f_in, 0, SEEK_END);
	num_samples=ftell(f_in)-0x90;
	unsigned long num_frames=num_samples;
	if(ctr_flags & 0x40)
	{
		num_frames=num_samples>>1;
	}

	vmv_header_buf[0x10]=(ctr_flags & 0x40) ? 0x83 : 0x81;
	*(unsigned long*)(&vmv_header_buf[0x14])=save_state_chksum;
	*(unsigned long*)(&vmv_header_buf[0x1c])=rerecord_count+1;
	memcpy(&vmv_header_buf[0x20], "\x03\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x40\x00\x00\x00", 0x10);
	*(unsigned long*)(&vmv_header_buf[0x30])=0x40+save_state_len+num_samples;
	*(unsigned long*)(&vmv_header_buf[0x34])=0x40+save_state_len;
	*(unsigned long*)(&vmv_header_buf[0x38])=num_frames;
	gen_crc_table();
	*(unsigned long*)(&vmv_header_buf[0x3c])=get_crc(vmv_header_buf, 0x3c);

	FILE* f_out=fopen(argv[3], "wb");
	if(!f_out)
	{
		perror("fopen\n");
		exit(0);
	}

	fwrite(vmv_header_buf, 1, 0x40, f_out);
	fwrite(save_state_buf, 1, save_state_len, f_out);

	unsigned char buf[0x1000];
	fseek(f_in, 0x90, SEEK_SET);
	unsigned long i;
	for(i=0; i<num_samples; )
	{
		long n=(num_samples-i)>0x1000 ? 0x1000 : (num_samples-i);
		fread(buf, 1, n, f_in);
		long x;
		for(x=0; x<n; ++x)
		{
			unsigned char c=0;
			if(buf[x]&0x01) c|=0x80;
			if(buf[x]&0x02) c|=0x40;
			if(buf[x]&0x04) c|=0x10;
			if(buf[x]&0x08) c|=0x20;
			if(buf[x]&0x10) c|=0x02;
			if(buf[x]&0x20) c|=0x01;
			if(buf[x]&0x40) c|=0x04;
			if(buf[x]&0x80) c|=0x08;
			buf[x]=c;
		}
		fwrite(buf, 1, n, f_out);
		i+=n;
	}

	fwrite(save_state_buf, 1, save_state_len, f_out);

	fclose(f_out);
	fclose(f_in);
	free(save_state_buf);

	return 0;
}
Give the .fmv file as the first argument, the short movie you created above as the second argument, and the output .vmv filename as the last argument. I've only tested this method on two movies so far: Genisto's SMB2 run and Sleepz' Zelda run. Genisto's movie plays back fine, but Sleepz' movie desyncs before Link even gets the wooden sword. :( If you manage to make a .vmv that doesn't desync, you should consider yourself lucky. Good luck!
Post subject: Re: Convert .fmv to .vmv files
Editor, Active player (297)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
blip wrote:
Converting an .fmv to a .vmv is slighly more complicated than converting in the other direction. Before you convert, you need to make a movie that was recorded from reset. It's easy though, just go to Options->Movie in VirtuaNES, and check the box "Start a record from reset". Open the ROM, record a movie, stop the movie, then exit.
Why's that? To get a savestate to the beginning? I believe you could make a generic at-reset save state. All games are reseted in the same way, and the RAM/VRAM/register contents don't really matter.
Post subject: Re: Convert .fmv to .vmv files
Joined: 4/11/2004
Posts: 155
Location: Fairfax, VA, USA
Bisqwit wrote:
Why's that? To get a savestate to the beginning?
The goal is to get a savestate at the *absolute* beginning, immediately after the system was reset. It seems easiest to do this using a movie recording that automatically resets and saves the state for you. I don't know of any other way to guarantee that a savestate is made immediately after system reset. Edit: You might be right about only needing a single generic savestate. I haven't tested this, though.