release r2, update credits
[fceu.git] / movie.c
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
30 typedef 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
49 static void FCEUI_LoadMovie_v1(char *fname, int _read_only);
50 //static int FCEUI_MovieGetInfo_v1(const char* fname, MOVIE_INFO* info);
51
52 extern char FileBase[];
53
54 /*
55 struct 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
77 int current = 0;     // > 0 for recording, < 0 for playback
78 static FILE *slots[10]={0};
79 static uint8 joop[4];
80 static uint32 framets = 0;
81 static uint32 frameptr = 0;
82 static uint8* moviedata = NULL;
83 static uint32 moviedatasize = 0;
84 static uint32 firstframeoffset = 0;
85 static uint32 savestate_offset = 0;
86 /*static*/ uint32 framecount = 0;
87 static uint32 rerecord_count = 0;
88 /*static*/ int movie_readonly = 1;
89 int frame_display = 0;
90 //static uint32 last_frame_display = ~0;
91 int input_display = 0;
92 static uint32 cur_input_display = 0;
93 //static uint32 last_input_display = ~0;
94
95 int resetDMCacc=0;
96
97 /* Cache variables used for playback. */
98 static uint32 nextts = 0;
99 static int32 nextd = 0;
100
101 //#define FCEUSTATE_RLSB            0x80000000
102
103 SFORMAT 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
114 static int CurrentMovie = 1;
115 //static int MovieShow = 0;
116
117 static int MovieStatus[10];
118
119 //static void DoEncode(int joy, int button, int);
120
121 int FCEUMOV_IsPlaying(void)
122 {
123  if(current < 0) return(1);
124  else return(0);
125 }
126
127 int FCEUMOV_IsRecording(void)
128 {
129  if(current > 0) return(1);
130  else return(0);
131 }
132
133 int suppressMovieStop=0;
134 int movieConvertOffset1=0, movieConvertOffset2=0,movieConvertOK=0,movieSyncHackOn=0;
135
136 static 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
147 static 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
221 static 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
240 void 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"
250 void 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
265 int justAutoConverted=0;
266 static 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
359 static 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
376 char curMovieFilename[512];
377
378
379 // PlayMovie / MoviePlay function
380 void 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
396 char origname[512];
397 strcpy(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);
434           printf("movie: trying movie v1\n");
435           FCEUI_LoadMovie_v1(fname, _read_only);
436           return;
437   }
438   else if(version == MOVIE_VERSION)
439   {}
440   else
441   {
442           // unsupported version
443           printf("movie: unsupported version\n");
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
489  // Loading new savestates doesn't work and even breaks FDS
490  //if(!FCEUSS_LoadFP(fp,1)) return;
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
525 void 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
667 static 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
679 static int movie_readchar()
680 {
681  if(frameptr >= moviedatasize)
682  {
683   return -1;
684  }
685  return (int)(moviedata[frameptr++]);
686 }
687
688 #if 0
689 static 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?)
719 void FCEUMOV_AddJoy(uint8 *js)
720 {
721 // int x,y;
722
723  if(!current) return;   // Not playback nor recording.
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");
737      FCEU_DoSimpleCommand(nextd&0x1F);
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
811 void 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
820 void 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
841 void 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
851 int 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
872 int movcounter=0;
873
874 void 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
922 int 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
938 static int load_successful;
939
940 int 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
992 void FCEUMOV_PreLoad(void)
993 {
994         load_successful=0;
995 }
996
997 int FCEUMOV_PostLoad(void)
998 {
999         if(!FCEUI_IsMovieActive())
1000                 return 1;
1001         else
1002                 return load_successful;
1003 }
1004
1005 char* FCEUI_MovieGetCurrentName(int addSlotNumber)
1006 {
1007  return FCEU_MakeFName(FCEUMKF_MOVIE,(addSlotNumber ? CurrentMovie : -1),0);
1008 }
1009 #endif
1010
1011 int FCEUI_IsMovieActive(void)
1012 {
1013   return current;
1014 }
1015
1016 #if 0
1017 void 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
1029 void 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
1041 void 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
1057 char lastMovieInfoFilename [512] = {'\0',};
1058
1059 int 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 /*
1198 struct 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
1214 static 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
1332 static 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