continue with pandora port
[fceu.git] / movie.c
CommitLineData
5a2aa426 1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4#include <unistd.h>
5#ifdef WIN32
6#include <windows.h>
7#endif
8
9#include "types.h"
10#include "endian.h"
11#include "input.h"
12#include "fce.h"
13#include "svga.h"
14//#include "palette.h"
15#include "driver.h"
16#include "state.h"
17#include "general.h"
18#include "video.h"
19#include "file.h"
20#include "movie.h"
21
22#define MOVIE_MAGIC 0x1a4d4346 // FCM\x1a
23#define MOVIE_VERSION 2 // still at 2 since the format itself is still compatible - to detect which version the movie was made with, check the fceu version stored in the movie header (e.g against FCEU_VERSION_NUMERIC)
24
25#define MOVIE_FLAG_NOSYNCHACK (1<<4) // set in newer version, used for old movie compatibility
26
27
28#define read32le read32
29
30typedef struct
31{
32 int movie_version; // version of the movie format in the file
33 uint32 num_frames;
34 uint32 rerecord_count;
35 uint8 flags;
36 int read_only;
37 uint32 emu_version_used; // 9813 = 0.98.13
38 char* metadata; // caller-supplied buffer to store metadata. can be NULL.
39 int metadata_size; // size of the buffer pointed to by metadata
40 uint8 md5_of_rom_used[16];
41 int md5_of_rom_used_present; // v1 movies don't have md5 info available
42 char* name_of_rom_used; // caller-supplied buffer to store metadata. can be NULL.
43 int name_of_rom_used_size; // size of the buffer pointer to by name_of_rom_used
44} MOVIE_INFO;
45
46
47
48// backwards compat
49static void FCEUI_LoadMovie_v1(char *fname, int _read_only);
890e37ba 50//static int FCEUI_MovieGetInfo_v1(const char* fname, MOVIE_INFO* info);
5a2aa426 51
52extern char FileBase[];
53
54/*
55struct MovieHeader
56{
57 uint32 magic; // +0
58 uint32 version=2; // +4
59 uint8 flags[4]; // +8
60 uint32 length_frames; // +12
61 uint32 rerecord_count; // +16
62 uint32 movie_data_size; // +20
63 uint32 offset_to_savestate; // +24, should be 4-byte-aligned
64 uint32 offset_to_movie_data; // +28, should be 4-byte-aligned
65 uint8 md5_of_rom_used[16]; // +32
66 uint32 version_of_emu_used // +48
67 char name_of_rom_used[] // +52, utf-8, null-terminated
68 char metadata[]; // utf-8, null-terminated
69 uint8 padding[];
70 uint8 savestate[]; // always present, even in a "from reset" recording
71 uint8 padding[]; // used for byte-alignment
72 uint8 movie_data[];
73}
74*/
75
76//static
77int current = 0; // > 0 for recording, < 0 for playback
78static FILE *slots[10]={0};
79static uint8 joop[4];
80static uint32 framets = 0;
81static uint32 frameptr = 0;
82static uint8* moviedata = NULL;
83static uint32 moviedatasize = 0;
84static uint32 firstframeoffset = 0;
85static uint32 savestate_offset = 0;
4fdfab07 86/*static*/ uint32 framecount = 0;
5a2aa426 87static uint32 rerecord_count = 0;
88/*static*/ int movie_readonly = 1;
89int frame_display = 0;
890e37ba 90//static uint32 last_frame_display = ~0;
5a2aa426 91int input_display = 0;
92static uint32 cur_input_display = 0;
890e37ba 93//static uint32 last_input_display = ~0;
5a2aa426 94
95int resetDMCacc=0;
96
97/* Cache variables used for playback. */
98static uint32 nextts = 0;
99static int32 nextd = 0;
100
92764e62 101//#define FCEUSTATE_RLSB 0x80000000
5a2aa426 102
103SFORMAT FCEUMOV_STATEINFO[]={
104 { joop, 4,"JOOP"},
105 { &framets, 4|FCEUSTATE_RLSB, "FTS "},
106 { &nextts, 4|FCEUSTATE_RLSB, "NXTS"},
107 { &nextd, 4|FCEUSTATE_RLSB, "NXTD"},
108 { &frameptr, 4|FCEUSTATE_RLSB, "FPTR"},
109 { &framecount, 4|FCEUSTATE_RLSB, "FCNT"},
110
111 { 0 }
112};
113
114static int CurrentMovie = 1;
890e37ba 115//static int MovieShow = 0;
5a2aa426 116
117static int MovieStatus[10];
118
890e37ba 119//static void DoEncode(int joy, int button, int);
5a2aa426 120
121int FCEUMOV_IsPlaying(void)
122{
123 if(current < 0) return(1);
124 else return(0);
125}
126
127int FCEUMOV_IsRecording(void)
128{
129 if(current > 0) return(1);
130 else return(0);
131}
132
133int suppressMovieStop=0;
134int movieConvertOffset1=0, movieConvertOffset2=0,movieConvertOK=0,movieSyncHackOn=0;
135
136static void StopPlayback(void)
137{
138 if(suppressMovieStop)
139 return;
140 resetDMCacc=movieSyncHackOn=0;
141 fclose(slots[-1 - current]);
142 current=0;
143 FCEU_DispMessage("Movie playback stopped.");
144}
145
146#if 0
147static void FlushHeader(void)
148{
149 if(current <= 0)
150 return;// only write header data if recording
151
152 FILE* fp = slots[current - 1];
153 if(fp == 0)
154 return;
155
156 unsigned long loc = ftell(fp);
157 fseek(fp, 4, SEEK_SET);
158 write32le(MOVIE_VERSION, fp);
159 fseek(fp, 12, SEEK_SET);
160 write32le(framecount, fp);
161 write32le(rerecord_count, fp);
162 write32le(frameptr, fp);
163 fseek(fp, 32, SEEK_SET);
164 fwrite(FCEUGameInfo->MD5, 1, 16, fp); // write ROM checksum
165 write32le(FCEU_VERSION_NUMERIC, fp); // write emu version used
166
167 // write ROM name used
168 fseek(fp, 52, SEEK_SET);
169 char str[512];
170 fgets(str,512,fp);
171 str[511]='\0';
172 int strdiff=strlen(FileBase)-strlen(str);
173 if(strdiff)
174 {
175 // resize the whole damn movie because the ROM name in the header is of variable length
176 int off=52;
177 fseek(fp, 52, SEEK_SET);
178 do { off++;
179 } while(fgetc(fp) && !feof(fp) && !ferror(fp));
180
181 if(feof(fp) || ferror(fp))
182 {
183 fseek(fp, loc, SEEK_SET);
184 return;
185 }
186
187 fseek(fp, 0, SEEK_END);
188 uint32 fsize=ftell(fp)-off;
189 char* ctemp=(char*)FCEU_malloc(fsize*sizeof(char)+4);
190 if(!ctemp)
191 {
192 fseek(fp, loc, SEEK_SET);
193 return;
194 }
195 fseek(fp, off, SEEK_SET);
196 fread(ctemp, 1,fsize, fp);
197 fseek(fp, 52+strlen(FileBase)+1, SEEK_SET);
198 int wrote = fwrite(ctemp, fsize,1, fp);
199 FCEU_free(ctemp);
200 if(!wrote)
201 {
202 fseek(fp, loc, SEEK_SET);
203 return;
204 }
205
206 if(loc >= firstframeoffset)
207 loc += strdiff;
208 savestate_offset += strdiff;
209 firstframeoffset += strdiff;
210 fseek(fp, 24, SEEK_SET);
211 write32le(savestate_offset, fp);
212 write32le(firstframeoffset, fp);
213 }
214 fseek(fp, 52, SEEK_SET);
215 fputs(FileBase, fp);
216 fputc('\0', fp);
217
218 fseek(fp, loc, SEEK_SET);
219}
220
221static void StopRecording(void)
222{
223 if(suppressMovieStop)
224 return;
225 resetDMCacc=movieSyncHackOn=0;
226 DoEncode(0,0,1); /* Write a dummy timestamp value so that the movie will keep
227 "playing" after user input has stopped. */
228 // finish header
229 FlushHeader();
230
231 // FIXME: truncate movie to length
232 // ftruncate();
233 fclose(slots[current - 1]);
234 MovieStatus[current - 1] = 1;
235 current=0;
236 FCEU_DispMessage("Movie recording stopped.");
237}
238#endif
239
240void FCEUI_StopMovie(void)
241{
242 if(current < 0) StopPlayback();
243#if 0
244 if(current > 0) StopRecording();
245#endif
246}
247
248#ifdef WIN32
249#include "process.h"
250void executeCommand(const char* cmd)
251{
252 if(!cmd || !*cmd)
253 return;
254
255 const char *argv[4];
256 argv[0] = getenv("COMSPEC");
257 argv[1] = "/c";
258 argv[2] = cmd;
259 argv[3] = NULL;
260 if(*argv && *(*argv))
261 _spawnve(_P_WAIT, argv[0], argv, NULL);
262}
263#endif
264
265int justAutoConverted=0;
266static const char* convertToFCM(const char *fname, char *buffer)
267{
268#ifdef WIN32
269 justAutoConverted=0;
270
271 // convert to fcm if not already
272 const char* dot = strrchr(fname, '.');
273 if(dot)
274 {
275 int fmv = !stricmp(dot, ".fmv");
276 int nmv = !stricmp(dot, ".nmv");
277 int vmv = !stricmp(dot, ".vmv");
278 if(fmv || nmv || vmv)
279 {
280 strcpy(buffer, fname);
281 buffer[dot-fname]='\0';
282 strcat(buffer,"-autoconverted.fcm");
283
284 int fceuver=0;
285 if(fmv)
286 fceuver=1;
287 else if(nmv)
288 fceuver=2;
289 else if(vmv)
290 fceuver=3;
291
292 extern char lastLoadedGameName [2048];
293 char cmd [1024], offset[64], romInfo[1024];
294 if(movieConvertOK)
295 sprintf(romInfo, "-smd5=\"%s\" -sromname=\"%s (MAYBE)\" -s", lastLoadedGameName, FileBase);
296 else
297 sprintf(romInfo, "-sromname=\"(unknown)\" -s");
298 if(movieConvertOffset2) sprintf(offset, "-o %d:%d", movieConvertOffset2,movieConvertOffset1);
299 else sprintf(offset, "-o %d", movieConvertOffset1);
300 sprintf(cmd, ".\\util\\nesmock\\nesmock.exe %s %s -spal=%c -sfceuver=%d \"%s\" \"%s\" ", offset, romInfo, FCEUI_GetCurrentVidSystem(0,0)?'1':'0', fceuver, fname, buffer);
301// FCEU_PrintError(cmd);
302 executeCommand(cmd);
303
304 FILE* file = FCEUD_UTF8fopen(buffer,"rb");
305 if(file)
306 {
307 fseek(file, 12, SEEK_SET);
308 int frames=0;
309 read32le(&frames, file);
310 if(frames)
311 {
312 fname = buffer;
313 justAutoConverted=1;
314 }
315 else
316 {
317 static int errAlready=0;
318 if(!errAlready)
319 {
320 errAlready=1;
321 FCEU_PrintError("For some reason, nesmock was unable to create a valid FCM from the given file.\nThe command given was:\n%s\nPerhaps the file specified is not a movie file or contains no input data,\nor perhaps it is a movie file of a version unsupported by nesmock.\n\n(This error message will self-destruct until you restart FCEU.)", cmd);
322 }
323 }
324 fclose(file);
325 }
326 else
327 {
328 char str [512];
329 str[0] = '\0';
330 GetCurrentDirectory(512,str);
331 strcat(str, "\\util\\nesmock\\nesmock.exe");
332 file = FCEUD_UTF8fopen(str, "rb");
333 if(file)
334 {
335 static int errAlready=0;
336 if(!errAlready)
337 {
338 errAlready=1;
339 FCEU_PrintError("For some reason, nesmock was unable to convert the movie to FCM format.\nThe command given was:\n%s\n\n(This error message will self-destruct until you restart FCEU.)", cmd);
340 fclose(file);
341 }
342 }
343 else
344 {
345 static int errAlready=0;
346 if(!errAlready)
347 {
348 errAlready=1;
349 FCEU_PrintError("Nesmock not found, so the movie could not be converted to FCM format.\nYou must place nesmock.exe at this location so FCEU can find it:\n%s\n\n(This error message will self-destruct until you restart FCEU.)", str);
350 }
351 }
352 }
353 }
354 }
355#endif
356 return fname;
357}
358
359static void ResetInputTypes()
360{
361#ifdef WIN32
362 extern int UsrInputType[3];
363 UsrInputType[0] = SI_GAMEPAD;
364 UsrInputType[1] = SI_GAMEPAD;
365 UsrInputType[2] = SIFC_NONE;
366
367 ParseGIInput(NULL/*FCEUGameInfo*/);
368 extern int cspec, gametype;
369 cspec=FCEUGameInfo->cspecial;
370 gametype=FCEUGameInfo->type;
371
372 InitOtherInput();
373#endif
374}
375
376char curMovieFilename[512];
377
378
379// PlayMovie / MoviePlay function
380void FCEUI_LoadMovie(char *fname, int _read_only)
381{
382 char buffer [512];
383 fname = (char*)convertToFCM(fname,buffer);
384
385 FILE *fp;
386 char *fn = NULL;
387
388 FCEUI_StopMovie();
389
390#if 0
391 if(!fname)
392 fname = fn = FCEU_MakeFName(FCEUMKF_MOVIE,CurrentMovie,0);
393#endif
394
395#if 0
396char origname[512];
397strcpy(origname,fname);
398#endif
399
400 // check movie_readonly
401 movie_readonly = _read_only;
402 if(access(fname, W_OK))
403 movie_readonly = 2;
404
405 fp = fopen(fname, (movie_readonly>=2) ? "rb" : "r+b");
406
407 if(fn)
408 {
409 free(fn);
410 fname = NULL;
411 }
412
413 if(!fp) return;
414
415 // read header
416 {
417 uint32 magic;
418 uint32 version;
419 uint8 flags[4];
420
421 read32le(&magic, fp);
422 if(magic != MOVIE_MAGIC)
423 {
424 fclose(fp);
425 return;
426 }
427//DEBUG_COMPARE_RAM(__LINE__);
428
429 read32le(&version, fp);
430 if(version == 1)
431 {
432 // attempt to load previous version's format
433 fclose(fp);
989672f4 434 printf("movie: trying movie v1\n");
5a2aa426 435 FCEUI_LoadMovie_v1(fname, _read_only);
436 return;
437 }
438 else if(version == MOVIE_VERSION)
439 {}
440 else
441 {
442 // unsupported version
c4980f9e 443 printf("movie: unsupported version\n");
5a2aa426 444 fclose(fp);
445 return;
446 }
447
448 fread(flags, 1, 4, fp);
449 read32le(&framecount, fp);
450 read32le(&rerecord_count, fp);
451 read32le(&moviedatasize, fp);
452 read32le(&savestate_offset, fp);
453 read32le(&firstframeoffset, fp);
454 if(fseek(fp, savestate_offset, SEEK_SET))
455 {
456 fclose(fp);
457 return;
458 }
459
460// FCEU_PrintError("flags[0] & MOVIE_FLAG_NOSYNCHACK=%d",flags[0] & MOVIE_FLAG_NOSYNCHACK);
461 if(flags[0] & MOVIE_FLAG_NOSYNCHACK)
462 movieSyncHackOn=0;
463 else
464 movieSyncHackOn=1;
465 }
466
467 // fully reload the game to reinitialize everything before playing any movie
468 // to try fixing nondeterministic playback of some games
469#if 0 // do we need this?
470 {
471 extern char lastLoadedGameName [2048];
472#if 0 // TODO?
473 extern int disableBatteryLoading, suppressAddPowerCommand;
474 suppressAddPowerCommand=1;
475 suppressMovieStop=1;
476#endif
477 {
478 FCEUGI * gi = FCEUI_LoadGame(lastLoadedGameName);
479 if(!gi)
480 PowerNES();
481 }
482#if 0 // TODO?
483 suppressMovieStop=0;
484 suppressAddPowerCommand=0;
485#endif
486 }
487#endif
488
c4980f9e 489 // Loading new savestates doesn't work and even breaks FDS
490 //if(!FCEUSS_LoadFP(fp,1)) return;
5a2aa426 491
492 ResetInputTypes();
493
494 fseek(fp, firstframeoffset, SEEK_SET);
495 moviedata = (uint8*)realloc(moviedata, moviedatasize);
496 fread(moviedata, 1, moviedatasize, fp);
497
498 framecount = 0; // movies start at frame 0!
499 frameptr = 0;
500 current = CurrentMovie;
501 slots[current] = fp;
502
503 memset(joop,0,sizeof(joop));
504 current = -1 - current;
505 framets=0;
506 nextts=0;
507 nextd = -1;
508
509 MovieStatus[CurrentMovie] = 1;
510#if 0
511 if(!fname)
512 FCEUI_SelectMovie(CurrentMovie,1); /* Quick hack to display status. */
513 else
514#endif
515 FCEU_DispMessage("Movie playback started.");
516
517#if 0
518 strcpy(curMovieFilename, origname);
519#else
520 strcpy(curMovieFilename, fname);
521#endif
522}
523
524#if 0
525void FCEUI_SaveMovie(char *fname, uint8 flags, const char* metadata)
526{
527 FILE *fp;
528 char *fn;
529 int poweron=0;
530 uint8 padding[4] = {0,0,0,0};
531 int n_padding;
532
533 FCEUI_StopMovie();
534
535 char origname[512];
536 if(fname)
537 {
538 fp = FCEUD_UTF8fopen(fname, "wb");
539 strcpy(origname,fname);
540 }
541 else
542 {
543 fp=FCEUD_UTF8fopen(fn=FCEU_MakeFName(FCEUMKF_MOVIE,CurrentMovie,0),"wb");
544 strcpy(origname,fn);
545 free(fn);
546 }
547
548 if(!fp) return;
549
550 // don't need the movieSyncHackOn sync hack for newly recorded movies
551 flags |= MOVIE_FLAG_NOSYNCHACK;
552 resetDMCacc=movieSyncHackOn=0;
553
554 // add PAL flag
555 if(FCEUI_GetCurrentVidSystem(0,0))
556 flags |= MOVIE_FLAG_PAL;
557
558 if(flags & MOVIE_FLAG_FROM_POWERON)
559 {
560 poweron=1;
561 flags &= ~MOVIE_FLAG_FROM_POWERON;
562 flags |= MOVIE_FLAG_FROM_RESET;
563 }
564
565 // write header
566 write32le(MOVIE_MAGIC, fp);
567 write32le(MOVIE_VERSION, fp);
568 fputc(flags, fp);
569 fputc(0, fp); // reserved
570 fputc(0, fp); // reserved
571 fputc(0, fp); // reserved
572 write32le(0, fp); // leave room for length frames
573 write32le(0, fp); // leave room for rerecord count
574 write32le(0, fp); // leave room for movie data size
575 write32le(0, fp); // leave room for savestate_offset
576 write32le(0, fp); // leave room for offset_to_controller_data
577 fwrite(FCEUGameInfo->MD5, 1, 16, fp); // write ROM checksum
578 write32le(FCEU_VERSION_NUMERIC, fp); // write emu version used
579 fputs(FileBase, fp); // write ROM name used
580 fputc(0, fp);
581 if(metadata)
582 {
583 if(strlen(metadata) < MOVIE_MAX_METADATA)
584 fputs(metadata, fp);
585 else
586 fwrite(metadata, 1, MOVIE_MAX_METADATA-1, fp);
587 }
588 fputc(0, fp);
589
590 // add padding
591 n_padding = (4 - (ftell(fp) & 0x3)) & 0x3;
592 fwrite(padding, 1, n_padding, fp);
593
594 if(flags & MOVIE_FLAG_FROM_RESET)
595 {
596 if(poweron)
597 {
598 // make a for-movie-recording power-on clear the game's save data, too
599 // (note: FCEU makes a save state immediately after this and that loads that on movie playback)
600 extern char lastLoadedGameName [2048];
601 extern int disableBatteryLoading, suppressAddPowerCommand;
602 suppressAddPowerCommand=1;
603 disableBatteryLoading=1;
604 suppressMovieStop=1;
605 {
606 // NOTE: this will NOT write an FCEUNPCMD_POWER into the movie file
607 FCEUGI * gi = FCEUI_LoadGame(lastLoadedGameName);
608 if(!gi)
609 PowerNES(); // and neither will this, if it can even happen
610 }
611 suppressMovieStop=0;
612 disableBatteryLoading=0;
613 suppressAddPowerCommand=0;
614 }
615 }
616
617 savestate_offset = ftell(fp);
618 FCEUSS_SaveFP(fp);
619 fseek(fp, 0, SEEK_END);
620
621 ResetInputTypes();
622
623 // add padding
624 n_padding = (4 - (ftell(fp) & 0x3)) & 0x3;
625 fwrite(padding, 1, n_padding, fp);
626
627 firstframeoffset = ftell(fp);
628
629 // finish header
630 fseek(fp, 24, SEEK_SET); // offset_to_savestate offset
631 write32le(savestate_offset, fp);
632 write32le(firstframeoffset, fp);
633
634 fseek(fp, firstframeoffset, SEEK_SET);
635
636 // set recording flag
637 current=CurrentMovie;
638
639 movie_readonly = 0;
640 frameptr = 0;
641 framecount = 0;
642 rerecord_count = 0;
643 slots[current] = fp;
644 memset(joop,0,sizeof(joop));
645 current++;
646 framets=0;
647 nextd = -1;
648
649 // trigger a reset
650 if(flags & MOVIE_FLAG_FROM_RESET)
651 {
652 if(poweron)
653 {
654 PowerNES(); // NOTE: this will write an FCEUNPCMD_POWER into the movie file
655 }
656 else
657 ResetNES(); // NOTE: this will write an FCEUNPCMD_RESET into the movie file
658 }
659 if(!fname)
660 FCEUI_SelectMovie(CurrentMovie,1); /* Quick hack to display status. */
661 else
662 FCEU_DispMessage("Movie recording started.");
663
664 strcpy(curMovieFilename, origname);
665}
666
667static void movie_writechar(int c)
668{
669 if(frameptr == moviedatasize)
670 {
671 moviedatasize += 4096;
672 moviedata = (uint8*)realloc(moviedata, moviedatasize);
673 }
674 moviedata[frameptr++] = (uint8)(c & 0xff);
675 fputc(c, slots[current - 1]);
676}
677#endif
678
679static int movie_readchar()
680{
681 if(frameptr >= moviedatasize)
682 {
683 return -1;
684 }
685 return (int)(moviedata[frameptr++]);
686}
687
688#if 0
689static void DoEncode(int joy, int button, int dummy)
690{
691 uint8 d;
692
693 d = 0;
694
695 if(framets >= 65536)
696 d = 3 << 5;
697 else if(framets >= 256)
698 d = 2 << 5;
699 else if(framets > 0)
700 d = 1 << 5;
701
702 if(dummy) d|=0x80;
703
704 d |= joy << 3;
705 d |= button;
706
707 movie_writechar(d);
708 //printf("Wr: %02x, %d\n",d,slots[current-1]);
709 while(framets)
710 {
711 movie_writechar(framets & 0xff);
712 //printf("Wrts: %02x\n",framets & 0xff);
713 framets >>= 8;
714 }
715}
716#endif
717
718// TODO: make this function legible! (what are all these magic numbers and weirdly named variables and crazy unexplained loops?)
719void FCEUMOV_AddJoy(uint8 *js)
720{
890e37ba 721// int x,y;
5a2aa426 722
890e37ba 723 if(!current) return; // Not playback nor recording.
5a2aa426 724
725 if(current < 0) // Playback
726 {
727 while(nextts == framets || nextd == -1)
728 {
729 int tmp,ti;
730 uint8 d;
731
732 if(nextd != -1)
733 {
734 if(nextd&0x80)
735 {
736 //puts("Egads");
5a2aa426 737 FCEU_DoSimpleCommand(nextd&0x1F);
5a2aa426 738 }
739 else
740 joop[(nextd >> 3)&0x3] ^= 1 << (nextd&0x7);
741 }
742
743
744 tmp = movie_readchar();
745 d = tmp;
746
747 if(tmp < 0)
748 {
749 StopPlayback();
750 memcpy(&cur_input_display,js,4);
751 return;
752 }
753
754 nextts = 0;
755 tmp >>= 5;
756 tmp &= 0x3;
757 ti=0;
758
759 int tmpfix = tmp;
760 while(tmp--) { nextts |= movie_readchar() << (ti * 8); ti++; }
761
762 // This fixes a bug in movies recorded before version 0.98.11
763 // It's probably not necessary, but it may keep away CRAZY PEOPLE who recorded
764 // movies on <= 0.98.10 and don't work on playback.
765 if(tmpfix == 1 && !nextts)
766 {nextts |= movie_readchar()<<8; }
767 else if(tmpfix == 2 && !nextts) {nextts |= movie_readchar()<<16; }
768
769 if(nextd != -1)
770 framets = 0;
771 nextd = d;
772 }
773
774 memcpy(js,joop,4);
775 }
776#if 0
777 else if(current > 0) // Recording
778 {
779 // flush header info every 300 frames in case of crash
780 {
781 static int fcounter=0;
782 fcounter++;
783 if(!(fcounter%300))
784 FlushHeader();
785 }
786
787 for(x=0;x<4;x++)
788 {
789 if(js[x] != joop[x])
790 {
791 for(y=0;y<8;y++)
792 if((js[x] ^ joop[x]) & (1 << y))
793 DoEncode(x, y, 0);
794 joop[x] = js[x];
795 }
796 else if(framets == ((1<<24)-1)) DoEncode(0,0,1); // Overflow will happen, so do dummy update.
797 }
798 }
799#endif
800
801 if(current)
802 {
803 framets++;
804 framecount++;
805 }
806
807 memcpy(&cur_input_display,js,4);
808}
809
810#if 0
811void FCEUMOV_AddCommand(int cmd)
812{
813 if(current <= 0) return; // Return if not recording a movie
814 //printf("%d\n",cmd);
815 DoEncode((cmd>>3)&0x3,cmd&0x7,1);
816}
817#endif
818
819#if 0
820void FCEUMOV_CheckMovies(void)
821{
822 FILE *st=NULL;
823 char *fn;
824 int ssel;
825
826 for(ssel=0;ssel<10;ssel++)
827 {
828 st=FCEUD_UTF8fopen(fn=FCEU_MakeFName(FCEUMKF_MOVIE,ssel,0),"rb");
829 free(fn);
830 if(st)
831 {
832 MovieStatus[ssel]=1;
833 fclose(st);
834 }
835 else
836 MovieStatus[ssel]=0;
837 }
838
839}
840
841void FCEUI_SelectMovieNext(int n)
842{
843 if(n>0)
844 CurrentMovie=(CurrentMovie+1)%10;
845 else
846 CurrentMovie=(CurrentMovie+9)%10;
847 FCEUI_SelectMovie(CurrentMovie, 1);
848}
849
850
851int FCEUI_SelectMovie(int w, int show)
852{
853 int oldslot=CurrentMovie;
854 if(w == -1) { MovieShow = 0; return; }
855 FCEUI_SelectState(-1,0);
856
857 CurrentMovie=w;
858 MovieShow=180;
859
860 if(show)
861 {
862 MovieShow=180;
863 if(current > 0)
864 FCEU_DispMessage("-recording movie %d-",current-1);
865 else if (current < 0)
866 FCEU_DispMessage("-playing movie %d-",-1 - current);
867 else
868 FCEU_DispMessage("-select movie-");
869 }
870}
871
872int movcounter=0;
873
874void FCEU_DrawMovies(uint8 *XBuf)
875{
876 int frameDisplayOn = current != 0 && frame_display;
877 extern int howlong;
878#if WIN32
879 extern int32 fps_scale;
880#else
881 int32 fps_scale=256;
882#endif
883 int howl=(180-(FCEUI_EmulationPaused()?(60):(20*fps_scale/256)));
884 if(howl>176) howl=180;
885 if(howl<1) howl=1;
886 if((howlong<howl || movcounter)
887 && (frameDisplayOn && (!movcounter || last_frame_display!=framecount) || input_display && (!movcounter || last_input_display!=cur_input_display)))
888 {
889 char inputstr [32];
890 if(input_display)
891 {
892 uint32 c = cur_input_display;
893 sprintf(inputstr, "%c%c%c%c%c%c%c%c %c%c%c%c%c%c%c%c",
894 (c&0x40)?'<':' ', (c&0x10)?'^':' ', (c&0x80)?'>':' ', (c&0x20)?'v':' ',
895 (c&0x01)?'A':' ', (c&0x02)?'B':' ', (c&0x08)?'S':' ', (c&0x04)?'s':' ',
896 (c&0x4000)?'<':' ', (c&0x1000)?'^':' ', (c&0x8000)?'>':' ', (c&0x2000)?'v':' ',
897 (c&0x0100)?'A':' ', (c&0x0200)?'B':' ', (c&0x0800)?'S':' ', (c&0x0400)?'s':' ');
898 if(!(c&0xff00))
899 inputstr[8] = '\0';
900 }
901 if(frameDisplayOn && !input_display)
902 FCEU_DispMessage("%s frame %u",current >= 0?"Recording":"Playing",framecount);
903 else if(input_display && !frameDisplayOn)
904 FCEU_DispMessage("Input: %s",inputstr);
905 else //if(input_display && frame_display)
906 FCEU_DispMessage("%s %u %s",current >= 0?"Recording":"Playing",framecount,inputstr);
907
908 last_frame_display = framecount;
909 last_input_display = cur_input_display;
910 movcounter=180-1;
911 return;
912 }
913
914 if(movcounter) movcounter--;
915
916 if(!MovieShow) return;
917
918 FCEU_DrawNumberRow(XBuf,MovieStatus, CurrentMovie);
919 MovieShow--;
920}
921
922int FCEUMOV_WriteState(FILE* st)
923{
924 uint32 to_write = 0;
925 if(current < 0)
926 to_write = moviedatasize;
927 else if(current > 0)
928 to_write = frameptr;
929
930 if(!st)
931 return to_write;
932
933 if(to_write)
934 fwrite(moviedata, 1, to_write, st);
935 return to_write;
936}
937
938static int load_successful;
939
940int FCEUMOV_ReadState(FILE* st, uint32 size)
941{
942 // if this savestate was made while replaying,
943 // we need to "undo" nextd and nextts
944 if(nextd != -1)
945 {
946 int d = 1;
947 if(nextts > 65536)
948 d = 4;
949 else if(nextts > 256)
950 d = 3;
951 else if(nextts > 0)
952 d = 2;
953 frameptr -= d;
954 nextd = -1;
955 }
956
957// if(current > 0 || (!movie_readonly && current < 0)) /* Recording or Playback (!read-only) */
958 if(current!=0 && !movie_readonly)
959 {
960 // copy movie data from savestate
961 moviedata = (uint8*)realloc(moviedata, size);
962 moviedatasize = size;
963 if(size && fread(moviedata, 1, size, st)<size)
964 return 0;
965 if(current < 0) // switch to recording
966 current = -current;
967 fseek(slots[current - 1], firstframeoffset, SEEK_SET);
968 fwrite(moviedata, 1, frameptr, slots[current - 1]);
969 rerecord_count++;
970 }
971// else if(current < 0) /* Playback (read-only) */
972 else if(current!=0 && movie_readonly)
973 {
974 if(current > 0) // switch to playback
975 current = -current;
976 // allow frameptr to be updated but keep movie data
977 fseek(st, size, SEEK_CUR);
978 // prevent seeking beyond the end of the movie
979 if(frameptr > moviedatasize)
980 frameptr = moviedatasize;
981 }
982 else /* Neither recording or replaying */
983 {
984 // skip movie data
985 fseek(st, size, SEEK_CUR);
986 }
987
988 load_successful=1;
989 return 1;
990}
991
992void FCEUMOV_PreLoad(void)
993{
994 load_successful=0;
995}
996
997int FCEUMOV_PostLoad(void)
998{
999 if(!FCEUI_IsMovieActive())
1000 return 1;
1001 else
1002 return load_successful;
1003}
1004
1005char* FCEUI_MovieGetCurrentName(int addSlotNumber)
1006{
1007 return FCEU_MakeFName(FCEUMKF_MOVIE,(addSlotNumber ? CurrentMovie : -1),0);
1008}
1009#endif
1010
1011int FCEUI_IsMovieActive(void)
1012{
1013 return current;
1014}
1015
1016#if 0
1017void FCEUI_MovieToggleFrameDisplay(void)
1018{
1019 frame_display=!frame_display;
1020 if(!(current != 0 && frame_display) && !input_display)
1021 FCEU_ResetMessages();
1022 else
1023 {
1024 last_frame_display = ~framecount;
1025 last_input_display = ~cur_input_display;
1026 }
1027}
1028
1029void FCEUI_ToggleInputDisplay(void)
1030{
1031 input_display=!input_display;
1032 if(!input_display && !(current != 0 && frame_display))
1033 FCEU_ResetMessages();
1034 else
1035 {
1036 last_frame_display = ~framecount;
1037 last_input_display = ~cur_input_display;
1038 }
1039}
1040
1041void FCEUI_MovieToggleReadOnly(void)
1042{
1043 if(movie_readonly < 2)
1044 {
1045 movie_readonly = !movie_readonly;
1046 if(movie_readonly)
1047 FCEU_DispMessage("Movie is now Read-Only.");
1048 else
1049 FCEU_DispMessage("Movie is now Read+Write.");
1050 }
1051 else
1052 {
1053 FCEU_DispMessage("Movie file is Read-Only.");
1054 }
1055}
1056
1057char lastMovieInfoFilename [512] = {'\0',};
1058
1059int FCEUI_MovieGetInfo(const char* fname, MOVIE_INFO* info)
1060{
1061 FlushHeader();
1062
1063 char buffer [512];
1064 fname = (const char*)convertToFCM(fname,buffer);
1065 strncpy(lastMovieInfoFilename, fname, 512);
1066
1067// main get info part of function
1068{
1069 uint32 magic;
1070 uint32 version;
1071 uint8 _flags[4];
1072
1073 FILE* fp = FCEUD_UTF8fopen(fname, "rb");
1074 if(!fp)
1075 return 0;
1076
1077 read32le(&magic, fp);
1078 if(magic != MOVIE_MAGIC)
1079 {
1080 fclose(fp);
1081 return 0;
1082 }
1083
1084 read32le(&version, fp);
1085 if(version != MOVIE_VERSION)
1086 {
1087 fclose(fp);
1088 if(version == 1)
1089 return FCEUI_MovieGetInfo_v1(fname, info);
1090 else
1091 return 0;
1092 }
1093
1094 info->movie_version = MOVIE_VERSION;
1095
1096 fread(_flags, 1, 4, fp);
1097
1098 info->flags = _flags[0];
1099 read32le(&info->num_frames, fp);
1100 read32le(&info->rerecord_count, fp);
1101
1102 if(access(fname, W_OK))
1103 info->read_only = 1;
1104 else
1105 info->read_only = 0;
1106
1107 fseek(fp, 12, SEEK_CUR); // skip movie_data_size, offset_to_savestate, and offset_to_movie_data
1108
1109 fread(info->md5_of_rom_used, 1, 16, fp);
1110 info->md5_of_rom_used_present = 1;
1111
1112 read32le(&info->emu_version_used, fp);
1113
1114 // I probably could have planned this better...
1115 {
1116 char str[256];
1117 size_t r;
1118 int p;
1119 int p2=0;
1120 char last_c=32;
1121
1122 if(info->name_of_rom_used && info->name_of_rom_used_size)
1123 info->name_of_rom_used[0]='\0';
1124
1125 r=fread(str, 1, 256, fp);
1126 while(r > 0)
1127 {
1128 for(p=0; p<r && last_c != '\0'; ++p)
1129 {
1130 if(info->name_of_rom_used && info->name_of_rom_used_size && (p2 < info->name_of_rom_used_size-1))
1131 {
1132 info->name_of_rom_used[p2]=str[p];
1133 p2++;
1134 last_c=str[p];
1135 }
1136 }
1137 if(p<r)
1138 {
1139 memmove(str, str+p, r-p);
1140 r -= p;
1141 break;
1142 }
1143 r=fread(str, 1, 256, fp);
1144 }
1145
1146 p2=0;
1147 last_c=32;
1148
1149 if(info->metadata && info->metadata_size)
1150 info->metadata[0]='\0';
1151
1152 while(r > 0)
1153 {
1154 for(p=0; p<r && last_c != '\0'; ++p)
1155 {
1156 if(info->metadata && info->metadata_size && (p2 < info->metadata_size-1))
1157 {
1158 info->metadata[p2]=str[p];
1159 p2++;
1160 last_c=str[p];
1161 }
1162 }
1163 if(p != r)
1164 break;
1165 r=fread(str, 1, 256, fp);
1166 }
1167
1168 if(r<=0)
1169 {
1170 // somehow failed to read romname and metadata
1171 fclose(fp);
1172 return 0;
1173 }
1174 }
1175
1176 // check what hacks are necessary
1177 fseek(fp, 24, SEEK_SET); // offset_to_savestate offset
1178 uint32 temp_savestate_offset;
1179 read32le(&temp_savestate_offset, fp);
1180 if(fseek(fp, temp_savestate_offset, SEEK_SET))
1181 {
1182 fclose(fp);
1183 return 0;
1184 }
1185 if(!FCEUSS_LoadFP(fp,2)) return 0; // 2 -> don't really load, just load to find what's there then load backup
1186
1187 fclose(fp);
1188 return 1;
1189}
1190}
1191#endif
1192/*
1193 Backwards compat
1194*/
1195
1196
1197/*
1198struct MovieHeader_v1
1199{
1200 uint32 magic;
1201 uint32 version=1;
1202 uint8 flags[4];
1203 uint32 length_frames;
1204 uint32 rerecord_count;
1205 uint32 movie_data_size;
1206 uint32 offset_to_savestate;
1207 uint32 offset_to_movie_data;
1208 uint16 metadata_ucs2[]; // ucs-2, ick! sizeof(metadata) = offset_to_savestate - MOVIE_HEADER_SIZE
1209}
1210*/
1211
1212#define MOVIE_V1_HEADER_SIZE 32
1213
1214static void FCEUI_LoadMovie_v1(char *fname, int _read_only)
1215{
1216 FILE *fp;
1217 char *fn = NULL;
1218
1219 FCEUI_StopMovie();
1220
1221#if 0
1222 if(!fname)
1223 fname = fn = FCEU_MakeFName(FCEUMKF_MOVIE,CurrentMovie,0);
1224#endif
1225
1226 // check movie_readonly
1227 movie_readonly = _read_only;
1228 if(access(fname, W_OK))
1229 movie_readonly = 2;
1230
1231#if 0
1232 fp = FCEUD_UTF8fopen(fname, (movie_readonly>=2) ? "rb" : "r+b");
1233#else
1234 fp = fopen(fname, (movie_readonly>=2) ? "rb" : "r+b");
1235#endif
1236
1237 if(fn)
1238 {
1239 free(fn);
1240 fname = NULL;
1241 }
1242
1243 if(!fp) return;
1244
1245 // read header
1246 {
1247 uint32 magic;
1248 uint32 version;
1249 uint8 flags[4];
1250 uint32 fc;
1251
1252 read32le(&magic, fp);
1253 if(magic != MOVIE_MAGIC)
1254 {
1255 fclose(fp);
1256 return;
1257 }
1258
1259 read32le(&version, fp);
1260 if(version != 1)
1261 {
1262 fclose(fp);
1263 return;
1264 }
1265
1266 fread(flags, 1, 4, fp);
1267 read32le(&fc, fp);
1268 read32le(&rerecord_count, fp);
1269 read32le(&moviedatasize, fp);
1270 read32le(&savestate_offset, fp);
1271 read32le(&firstframeoffset, fp);
1272 if(fseek(fp, savestate_offset, SEEK_SET))
1273 {
1274 fclose(fp);
1275 return;
1276 }
1277
1278 if(flags[0] & MOVIE_FLAG_NOSYNCHACK)
1279 movieSyncHackOn=0;
1280 else
1281 movieSyncHackOn=1;
1282 }
1283
1284 // fully reload the game to reinitialize everything before playing any movie
1285 // to try fixing nondeterministic playback of some games
1286 {
1287 extern char lastLoadedGameName [2048];
1288#if 0 // TODO?
1289 extern int disableBatteryLoading, suppressAddPowerCommand;
1290 suppressAddPowerCommand=1;
1291 suppressMovieStop=1;
1292#endif
1293 {
1294 FCEUGI * gi = FCEUI_LoadGame(lastLoadedGameName);
1295 if(!gi)
1296 PowerNES();
1297 }
1298#if 0 // TODO?
1299 suppressMovieStop=0;
1300 suppressAddPowerCommand=0;
1301#endif
1302 }
1303
1304 if(!FCEUSS_LoadFP(fp,1)) return;
1305
1306 ResetInputTypes();
1307
1308 fseek(fp, firstframeoffset, SEEK_SET);
1309 moviedata = (uint8*)realloc(moviedata, moviedatasize);
1310 fread(moviedata, 1, moviedatasize, fp);
1311
1312 framecount = 0; // movies start at frame 0!
1313 frameptr = 0;
1314 current = CurrentMovie;
1315 slots[current] = fp;
1316
1317 memset(joop,0,sizeof(joop));
1318 current = -1 - current;
1319 framets=0;
1320 nextts=0;
1321 nextd = -1;
1322 MovieStatus[CurrentMovie] = 1;
1323#if 0
1324 if(!fname)
1325 FCEUI_SelectMovie(CurrentMovie,1); /* Quick hack to display status. */
1326 else
1327#endif
1328 FCEU_DispMessage("Movie playback started.");
1329}
1330
1331#if 0
1332static int FCEUI_MovieGetInfo_v1(const char* fname, MOVIE_INFO* info)
1333{
1334 uint32 magic;
1335 uint32 version;
1336 uint8 _flags[4];
1337 uint32 savestateoffset;
1338 uint8 tmp[MOVIE_MAX_METADATA<<1];
1339 int metadata_length;
1340
1341 FILE* fp = FCEUD_UTF8fopen(fname, "rb");
1342 if(!fp)
1343 return 0;
1344
1345 read32le(&magic, fp);
1346 if(magic != MOVIE_MAGIC)
1347 {
1348 fclose(fp);
1349 return 0;
1350 }
1351
1352 read32le(&version, fp);
1353 if(version != 1)
1354 {
1355 fclose(fp);
1356 return 0;
1357 }
1358
1359 info->movie_version = 1;
1360 info->emu_version_used = 0; // unknown
1361
1362 fread(_flags, 1, 4, fp);
1363
1364 info->flags = _flags[0];
1365 read32le(&info->num_frames, fp);
1366 read32le(&info->rerecord_count, fp);
1367
1368 if(access(fname, W_OK))
1369 info->read_only = 1;
1370 else
1371 info->read_only = 0;
1372
1373 fseek(fp, 4, SEEK_CUR);
1374 read32le(&savestateoffset, fp);
1375
1376 metadata_length = (int)savestateoffset - MOVIE_V1_HEADER_SIZE;
1377 if(metadata_length > 0)
1378 {
1379 int i;
1380
1381 metadata_length >>= 1;
1382 if(metadata_length >= MOVIE_MAX_METADATA)
1383 metadata_length = MOVIE_MAX_METADATA-1;
1384
1385 fseek(fp, MOVIE_V1_HEADER_SIZE, SEEK_SET);
1386 fread(tmp, 1, metadata_length<<1, fp);
1387 }
1388
1389 // turn old ucs2 metadata into utf8
1390 if(info->metadata && info->metadata_size)
1391 {
1392 char* ptr=info->metadata;
1393 char* ptr_end=info->metadata+info->metadata_size-1;
1394 int c_ptr=0;
1395 while(ptr<ptr_end && c_ptr<metadata_length)
1396 {
1397 uint16 c=(tmp[c_ptr<<1] | (tmp[(c_ptr<<1)+1] << 8));
1398 switch(c)
1399 {
1400 case 0 ... 0x7f:
1401 *ptr++ = (char)(c&0x7f);
1402 break;
1403 case 0x80 ... 0x7ff:
1404 if(ptr+1>=ptr_end)
1405 ptr_end=ptr;
1406 else
1407 {
1408 *ptr++=(0xc0 | (c>>6));
1409 *ptr++=(0x80 | (c & 0x3f));
1410 }
1411 break;
1412 case 0x800 ... 0xffff:
1413 if(ptr+2>=ptr_end)
1414 ptr_end=ptr;
1415 else
1416 {
1417 *ptr++=(0xe0 | (c>>12));
1418 *ptr++=(0x80 | ((c>>6) & 0x3f));
1419 *ptr++=(0x80 | (c & 0x3f));
1420 }
1421 break;
1422 }
1423 c_ptr++;
1424 }
1425 *ptr='\0';
1426 }
1427
1428 // md5 info not available from v1
1429 info->md5_of_rom_used_present = 0;
1430 // rom name used for the movie not available from v1
1431 if(info->name_of_rom_used && info->name_of_rom_used_size)
1432 info->name_of_rom_used[0] = '\0';
1433
1434 // check what hacks are necessary
1435 fseek(fp, 24, SEEK_SET); // offset_to_savestate offset
1436 uint32 temp_savestate_offset;
1437 read32le(&temp_savestate_offset, fp);
1438 if(fseek(fp, temp_savestate_offset, SEEK_SET))
1439 {
1440 fclose(fp);
1441 return 0;
1442 }
1443 if(!FCEUSS_LoadFP(fp,2)) return 0; // 2 -> don't really load, just load to find what's there then load backup
1444
1445
1446 fclose(fp);
1447 return 1;
1448}
1449#endif
1450