1/*****************************************************************************\
2 Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
3 This file is licensed under the Snes9x License.
4 For further information, consult the LICENSE file in the root directory.
5\*****************************************************************************/
6
7// Input recording/playback code
8// (c) Copyright 2004 blip
9
10#ifndef __WIN32__
11#include <unistd.h>
12#endif
13#include "snes9x.h"
14#include "memmap.h"
15#include "controls.h"
16#include "snapshot.h"
17#include "movie.h"
18#include "language.h"
19#ifdef NETPLAY_SUPPORT
20#include "netplay.h"
21#endif
22
23#ifdef __WIN32__
24#include <io.h>
25#ifndef W_OK
26#define W_OK 2
27#endif
28#define ftruncate chsize
29#endif
30
31#define SMV_MAGIC 0x1a564d53 // SMV0x1a
32#define SMV_VERSION 5
33#define SMV_HEADER_SIZE 64
34#define SMV_EXTRAROMINFO_SIZE 30
35#define BUFFER_GROWTH_SIZE 4096
36
37enum MovieState
38{
39 MOVIE_STATE_NONE = 0,
40 MOVIE_STATE_PLAY,
41 MOVIE_STATE_RECORD
42};
43
44struct SMovie
45{
46 enum MovieState State;
47
48 FILE *File;
49 char Filename[PATH_MAX + 1];
50 char ROMName[23];
51 uint32 ROMCRC32;
52 uint32 MovieId;
53 uint32 Version;
54
55 uint32 SaveStateOffset;
56 uint32 ControllerDataOffset;
57
58 uint8 ControllersMask;
59 uint8 Opts;
60 uint8 SyncFlags;
61 uint32 MaxFrame;
62 uint32 MaxSample;
63 uint32 CurrentFrame;
64 uint32 CurrentSample;
65 uint32 BytesPerSample;
66 uint32 RerecordCount;
67 bool8 ReadOnly;
68 uint8 PortType[2];
69 int8 PortIDs[2][4];
70
71 uint8 *InputBuffer;
72 uint8 *InputBufferPtr;
73 uint32 InputBufferSize;
74};
75
76static struct SMovie Movie;
77
78static uint8 prevPortType[2];
79static int8 prevPortIDs[2][4];
80static bool8 prevMouseMaster, prevSuperScopeMaster, prevJustifierMaster, prevMultiPlayer5Master;
81
82static uint8 Read8 (uint8 *&);
83static uint16 Read16 (uint8 *&);
84static uint32 Read32 (uint8 *&);
85static void Write8 (uint8, uint8 *&);
86static void Write16 (uint16, uint8 *&);
87static void Write32 (uint32, uint8 *&);
88static void store_previous_settings (void);
89static void restore_previous_settings (void);
90static void store_movie_settings (void);
91static void restore_movie_settings (void);
92static int bytes_per_sample (void);
93static void reserve_buffer_space (uint32);
94static void reset_controllers (void);
95static void read_frame_controller_data (bool);
96static void write_frame_controller_data (void);
97static void flush_movie (void);
98static void truncate_movie (void);
99static int read_movie_header (FILE *, SMovie *);
100static int read_movie_extrarominfo (FILE *, SMovie *);
101static void write_movie_header (FILE *, SMovie *);
102static void write_movie_extrarominfo (FILE *, SMovie *);
103static void change_state (MovieState);
104
105// HACK: reduce movie size by not storing changes that can only affect polled input in the movie for these types,
106// because currently no port sets these types to polling
107#define SKIPPED_POLLING_PORT_TYPE(x) (((x) == CTL_NONE) || ((x) == CTL_JOYPAD) || ((x) == CTL_MP5))
108
109#ifndef max
110#define max(a, b) (((a) > (b)) ? (a) : (b))
111#endif
112
113
114static uint8 Read8 (uint8 *&ptr)
115{
116 uint8 v = *ptr++;
117 return (v);
118}
119
120static uint16 Read16 (uint8 *&ptr)
121{
122 uint16 v = READ_WORD(ptr);
123 ptr += 2;
124 return (v);
125}
126
127static uint32 Read32 (uint8 *&ptr)
128{
129 uint32 v = READ_DWORD(ptr);
130 ptr += 4;
131 return (v);
132}
133
134static void Write8 (uint8 v, uint8 *&ptr)
135{
136 *ptr++ = v;
137}
138
139static void Write16 (uint16 v, uint8 *&ptr)
140{
141 WRITE_WORD(ptr, v);
142 ptr += 2;
143}
144
145static void Write32 (uint32 v, uint8 *&ptr)
146{
147 WRITE_DWORD(ptr, v);
148 ptr += 4;
149}
150
151static void store_previous_settings (void)
152{
153 for (int i = 0; i < 2; i++)
154 {
155 enum controllers pt;
156 S9xGetController(i, &pt, &prevPortIDs[i][0], &prevPortIDs[i][1], &prevPortIDs[i][2], &prevPortIDs[i][3]);
157 prevPortType[i] = (uint8) pt;
158 }
159
160 prevMouseMaster = Settings.MouseMaster;
161 prevSuperScopeMaster = Settings.SuperScopeMaster;
162 prevJustifierMaster = Settings.JustifierMaster;
163 prevMultiPlayer5Master = Settings.MultiPlayer5Master;
164}
165
166static void restore_previous_settings (void)
167{
168 Settings.MouseMaster = prevMouseMaster;
169 Settings.SuperScopeMaster = prevSuperScopeMaster;
170 Settings.JustifierMaster = prevJustifierMaster;
171 Settings.MultiPlayer5Master = prevMultiPlayer5Master;
172
173 S9xSetController(0, (enum controllers) prevPortType[0], prevPortIDs[0][0], prevPortIDs[0][1], prevPortIDs[0][2], prevPortIDs[0][3]);
174 S9xSetController(1, (enum controllers) prevPortType[1], prevPortIDs[1][0], prevPortIDs[1][1], prevPortIDs[1][2], prevPortIDs[1][3]);
175}
176
177static void store_movie_settings (void)
178{
179 for (int i = 0; i < 2; i++)
180 {
181 enum controllers pt;
182 S9xGetController(i, &pt, &Movie.PortIDs[i][0], &Movie.PortIDs[i][1], &Movie.PortIDs[i][2], &Movie.PortIDs[i][3]);
183 Movie.PortType[i] = (uint8) pt;
184 }
185}
186
187static void restore_movie_settings (void)
188{
189 Settings.MouseMaster = (Movie.PortType[0] == CTL_MOUSE || Movie.PortType[1] == CTL_MOUSE);
190 Settings.SuperScopeMaster = (Movie.PortType[0] == CTL_SUPERSCOPE || Movie.PortType[1] == CTL_SUPERSCOPE);
191 Settings.JustifierMaster = (Movie.PortType[0] == CTL_JUSTIFIER || Movie.PortType[1] == CTL_JUSTIFIER);
192 Settings.MultiPlayer5Master = (Movie.PortType[0] == CTL_MP5 || Movie.PortType[1] == CTL_MP5);
193
194 S9xSetController(0, (enum controllers) Movie.PortType[0], Movie.PortIDs[0][0], Movie.PortIDs[0][1], Movie.PortIDs[0][2], Movie.PortIDs[0][3]);
195 S9xSetController(1, (enum controllers) Movie.PortType[1], Movie.PortIDs[1][0], Movie.PortIDs[1][1], Movie.PortIDs[1][2], Movie.PortIDs[1][3]);
196}
197
198static int bytes_per_sample (void)
199{
200 int num_controllers = 0;
201
202 for (int i = 0; i < 8; i++)
203 {
204 if (Movie.ControllersMask & (1 << i))
205 num_controllers++;
206 }
207
208 int bytes = CONTROLLER_DATA_SIZE * num_controllers;
209
210 for (int p = 0; p < 2; p++)
211 {
212 if (Movie.PortType[p] == CTL_MOUSE)
213 bytes += MOUSE_DATA_SIZE;
214 else
215 if (Movie.PortType[p] == CTL_SUPERSCOPE)
216 bytes += SCOPE_DATA_SIZE;
217 else
218 if (Movie.PortType[p] == CTL_JUSTIFIER)
219 bytes += JUSTIFIER_DATA_SIZE;
220 }
221
222 return (bytes);
223}
224
225static void reserve_buffer_space (uint32 space_needed)
226{
227 if (space_needed > Movie.InputBufferSize)
228 {
229 uint32 ptr_offset = Movie.InputBufferPtr - Movie.InputBuffer;
230 uint32 alloc_chunks = space_needed / BUFFER_GROWTH_SIZE;
231
232 Movie.InputBufferSize = BUFFER_GROWTH_SIZE * (alloc_chunks + 1);
233 Movie.InputBuffer = (uint8 *) realloc(Movie.InputBuffer, Movie.InputBufferSize);
234 Movie.InputBufferPtr = Movie.InputBuffer + ptr_offset;
235 }
236}
237
238static void reset_controllers (void)
239{
240 for (int i = 0; i < 8; i++)
241 MovieSetJoypad(i, 0);
242
243 uint8 clearedMouse[MOUSE_DATA_SIZE];
244 memset(clearedMouse, 0, MOUSE_DATA_SIZE);
245 clearedMouse[4] = 1;
246
247 uint8 clearedScope[SCOPE_DATA_SIZE];
248 memset(clearedScope, 0, SCOPE_DATA_SIZE);
249
250 uint8 clearedJustifier[JUSTIFIER_DATA_SIZE];
251 memset(clearedJustifier, 0, JUSTIFIER_DATA_SIZE);
252
253 for (int p = 0; p < 2; p++)
254 {
255 MovieSetMouse(p, clearedMouse, true);
256 MovieSetScope(p, clearedScope);
257 MovieSetJustifier(p, clearedJustifier);
258 }
259}
260
261static void read_frame_controller_data (bool addFrame)
262{
263 // reset code check
264 if (Movie.InputBufferPtr[0] == 0xff)
265 {
266 bool reset = true;
267 for (int i = 1; i < (int) Movie.BytesPerSample; i++)
268 {
269 if (Movie.InputBufferPtr[i] != 0xff)
270 {
271 reset = false;
272 break;
273 }
274 }
275
276 if (reset)
277 {
278 Movie.InputBufferPtr += Movie.BytesPerSample;
279 S9xSoftReset();
280 return;
281 }
282 }
283
284 for (int i = 0; i < 8; i++)
285 {
286 if (Movie.ControllersMask & (1 << i))
287 MovieSetJoypad(i, Read16(Movie.InputBufferPtr));
288 else
289 MovieSetJoypad(i, 0); // pretend the controller is disconnected
290 }
291
292 for (int p = 0; p < 2; p++)
293 {
294 if (Movie.PortType[p] == CTL_MOUSE)
295 {
296 uint8 buf[MOUSE_DATA_SIZE];
297 memcpy(buf, Movie.InputBufferPtr, MOUSE_DATA_SIZE);
298 Movie.InputBufferPtr += MOUSE_DATA_SIZE;
299 MovieSetMouse(p, buf, !addFrame);
300 }
301 else
302 if (Movie.PortType[p] == CTL_SUPERSCOPE)
303 {
304 uint8 buf[SCOPE_DATA_SIZE];
305 memcpy(buf, Movie.InputBufferPtr, SCOPE_DATA_SIZE);
306 Movie.InputBufferPtr += SCOPE_DATA_SIZE;
307 MovieSetScope(p, buf);
308 }
309 else
310 if (Movie.PortType[p] == CTL_JUSTIFIER)
311 {
312 uint8 buf[JUSTIFIER_DATA_SIZE];
313 memcpy(buf, Movie.InputBufferPtr, JUSTIFIER_DATA_SIZE);
314 Movie.InputBufferPtr += JUSTIFIER_DATA_SIZE;
315 MovieSetJustifier(p, buf);
316 }
317 }
318}
319
320static void write_frame_controller_data (void)
321{
322 reserve_buffer_space((uint32) (Movie.InputBufferPtr + Movie.BytesPerSample - Movie.InputBuffer));
323
324 for (int i = 0; i < 8; i++)
325 {
326 if (Movie.ControllersMask & (1 << i))
327 Write16(MovieGetJoypad(i), Movie.InputBufferPtr);
328 else
329 MovieSetJoypad(i, 0); // pretend the controller is disconnected
330 }
331
332 for (int p = 0; p < 2; p++)
333 {
334 if (Movie.PortType[p] == CTL_MOUSE)
335 {
336 uint8 buf[MOUSE_DATA_SIZE];
337 MovieGetMouse(p, buf);
338 memcpy(Movie.InputBufferPtr, buf, MOUSE_DATA_SIZE);
339 Movie.InputBufferPtr += MOUSE_DATA_SIZE;
340 }
341 else
342 if (Movie.PortType[p] == CTL_SUPERSCOPE)
343 {
344 uint8 buf[SCOPE_DATA_SIZE];
345 MovieGetScope(p, buf);
346 memcpy(Movie.InputBufferPtr, buf, SCOPE_DATA_SIZE);
347 Movie.InputBufferPtr += SCOPE_DATA_SIZE;
348 }
349 else
350 if (Movie.PortType[p] == CTL_JUSTIFIER)
351 {
352 uint8 buf[JUSTIFIER_DATA_SIZE];
353 MovieGetJustifier(p, buf);
354 memcpy(Movie.InputBufferPtr, buf, JUSTIFIER_DATA_SIZE);
355 Movie.InputBufferPtr += JUSTIFIER_DATA_SIZE;
356 }
357 }
358}
359
360static void flush_movie (void)
361{
362 if (!Movie.File)
363 return;
364
365 fseek(Movie.File, 0, SEEK_SET);
366 write_movie_header(Movie.File, &Movie);
367 fseek(Movie.File, Movie.ControllerDataOffset, SEEK_SET);
368
369 if (!fwrite(Movie.InputBuffer, 1, Movie.BytesPerSample * (Movie.MaxSample + 1), Movie.File))
370 printf ("Movie flush failed.\n");
371}
372
373static void truncate_movie (void)
374{
375 if (!Movie.File || !Settings.MovieTruncate)
376 return;
377
378 if (Movie.SaveStateOffset > Movie.ControllerDataOffset)
379 return;
380
381 if (ftruncate(fileno(Movie.File), Movie.ControllerDataOffset + Movie.BytesPerSample * (Movie.MaxSample + 1)))
382 printf ("Couldn't truncate file.\n");
383}
384
385static int read_movie_header (FILE *fd, SMovie *movie)
386{
387 uint32 value;
388 uint8 buf[SMV_HEADER_SIZE], *ptr = buf;
389
390 if (fread(buf, 1, SMV_HEADER_SIZE, fd) != SMV_HEADER_SIZE)
391 return (WRONG_FORMAT);
392
393 value = Read32(ptr);
394 if (value != SMV_MAGIC)
395 return (WRONG_FORMAT);
396
397 value = Read32(ptr);
398 if(value > SMV_VERSION || value < 4)
399 return (WRONG_VERSION);
400
401 movie->Version = value;
402 movie->MovieId = Read32(ptr);
403 movie->RerecordCount = Read32(ptr);
404 movie->MaxFrame = Read32(ptr);
405 movie->ControllersMask = Read8(ptr);
406 movie->Opts = Read8(ptr);
407 ptr++;
408 movie->SyncFlags = Read8(ptr);
409 movie->SaveStateOffset = Read32(ptr);
410 movie->ControllerDataOffset = Read32(ptr);
411 movie->MaxSample = Read32(ptr);
412 movie->PortType[0] = Read8(ptr);
413 movie->PortType[1] = Read8(ptr);
414 for (int p = 0; p < 2; p++)
415 {
416 for (int i = 0; i < 4; i++)
417 movie->PortIDs[p][i] = Read8(ptr);
418 }
419
420 if (movie->MaxSample < movie->MaxFrame)
421 movie->MaxSample = movie->MaxFrame;
422
423 return (SUCCESS);
424}
425
426static int read_movie_extrarominfo (FILE *fd, SMovie *movie)
427{
428 uint8 buf[SMV_EXTRAROMINFO_SIZE], *ptr = buf;
429
430 fseek(fd, movie->SaveStateOffset - SMV_EXTRAROMINFO_SIZE, SEEK_SET);
431
432 if (fread(buf, 1, SMV_EXTRAROMINFO_SIZE, fd) != SMV_EXTRAROMINFO_SIZE)
433 return (WRONG_FORMAT);
434
435 ptr += 3; // zero bytes
436 movie->ROMCRC32 = Read32(ptr);
437 sstrncpy(movie->ROMName, (char *) ptr, 23);
438
439 return (SUCCESS);
440}
441
442static void write_movie_header (FILE *fd, SMovie *movie)
443{
444 uint8 buf[SMV_HEADER_SIZE], *ptr = buf;
445
446 memset(buf, 0, sizeof(buf));
447
448 Write32(SMV_MAGIC, ptr);
449 Write32(SMV_VERSION, ptr);
450 Write32(movie->MovieId, ptr);
451 Write32(movie->RerecordCount, ptr);
452 Write32(movie->MaxFrame, ptr);
453 Write8(movie->ControllersMask, ptr);
454 Write8(movie->Opts, ptr);
455 ptr++;
456 Write8(movie->SyncFlags, ptr);
457 Write32(movie->SaveStateOffset, ptr);
458 Write32(movie->ControllerDataOffset, ptr);
459 Write32(movie->MaxSample, ptr);
460 Write8(movie->PortType[0], ptr);
461 Write8(movie->PortType[1], ptr);
462 for (int p = 0; p < 2; p++)
463 {
464 for (int i = 0; i < 4; i++)
465 Write8(movie->PortIDs[p][i], ptr);
466 }
467
468 if (!fwrite(buf, 1, SMV_HEADER_SIZE, fd))
469 printf ("Couldn't write movie header.\n");
470}
471
472static void write_movie_extrarominfo (FILE *fd, SMovie *movie)
473{
474 uint8 buf[SMV_EXTRAROMINFO_SIZE], *ptr = buf;
475
476 Write8(0, ptr);
477 Write8(0, ptr);
478 Write8(0, ptr);
479 Write32(movie->ROMCRC32, ptr);
480 strncpy((char *) ptr, movie->ROMName, 23);
481
482 fwrite(buf, 1, SMV_EXTRAROMINFO_SIZE, fd);
483}
484
485static void change_state (MovieState new_state)
486{
487 if (new_state == Movie.State)
488 return;
489
490 if (Movie.State == MOVIE_STATE_RECORD)
491 flush_movie();
492
493 if (new_state == MOVIE_STATE_NONE)
494 {
495 truncate_movie();
496 fclose(Movie.File);
497 Movie.File = NULL;
498
499 if (S9xMoviePlaying() || S9xMovieRecording())
500 restore_previous_settings();
501 }
502
503 Movie.State = new_state;
504}
505
506void S9xMovieFreeze (uint8 **buf, uint32 *size)
507{
508 if (!S9xMovieActive())
509 return;
510
511 uint32 size_needed;
512 uint8 *ptr;
513
514 size_needed = sizeof(Movie.MovieId) + sizeof(Movie.CurrentFrame) + sizeof(Movie.MaxFrame) + sizeof(Movie.CurrentSample) + sizeof(Movie.MaxSample);
515 size_needed += (uint32) (Movie.BytesPerSample * (Movie.MaxSample + 1));
516 *size = size_needed;
517
518 *buf = new uint8[size_needed];
519 ptr = *buf;
520 if (!ptr)
521 return;
522
523 Write32(Movie.MovieId, ptr);
524 Write32(Movie.CurrentFrame, ptr);
525 Write32(Movie.MaxFrame, ptr);
526 Write32(Movie.CurrentSample, ptr);
527 Write32(Movie.MaxSample, ptr);
528
529 memcpy(ptr, Movie.InputBuffer, Movie.BytesPerSample * (Movie.MaxSample + 1));
530}
531
532int S9xMovieUnfreeze (uint8 *buf, uint32 size)
533{
534 if (!S9xMovieActive())
535 return (FILE_NOT_FOUND);
536
537 if (size < sizeof(Movie.MovieId) + sizeof(Movie.CurrentFrame) + sizeof(Movie.MaxFrame) + sizeof(Movie.CurrentSample) + sizeof(Movie.MaxSample))
538 return (WRONG_FORMAT);
539
540 uint8 *ptr = buf;
541
542 uint32 movie_id = Read32(ptr);
543 uint32 current_frame = Read32(ptr);
544 uint32 max_frame = Read32(ptr);
545 uint32 current_sample = Read32(ptr);
546 uint32 max_sample = Read32(ptr);
547 uint32 space_needed = (Movie.BytesPerSample * (max_sample + 1));
548
549 if (current_frame > max_frame || current_sample > max_sample || space_needed > size)
550 return (WRONG_MOVIE_SNAPSHOT);
551
552 if (Settings.WrongMovieStateProtection)
553 if (movie_id != Movie.MovieId)
554 if (max_frame < Movie.MaxFrame || max_sample < Movie.MaxSample || memcmp(Movie.InputBuffer, ptr, space_needed))
555 return (WRONG_MOVIE_SNAPSHOT);
556
557 if (!Movie.ReadOnly)
558 {
559 change_state(MOVIE_STATE_RECORD);
560
561 Movie.CurrentFrame = current_frame;
562 Movie.MaxFrame = max_frame;
563 Movie.CurrentSample = current_sample;
564 Movie.MaxSample = max_sample;
565 Movie.RerecordCount++;
566
567 store_movie_settings();
568
569 reserve_buffer_space(space_needed);
570 memcpy(Movie.InputBuffer, ptr, space_needed);
571
572 flush_movie();
573 fseek(Movie.File, Movie.ControllerDataOffset + (Movie.BytesPerSample * (Movie.CurrentSample + 1)), SEEK_SET);
574 }
575 else
576 {
577 uint32 space_processed = (Movie.BytesPerSample * (current_sample + 1));
578 if (current_frame > Movie.MaxFrame || current_sample > Movie.MaxSample || memcmp(Movie.InputBuffer, ptr, space_processed))
579 return (SNAPSHOT_INCONSISTENT);
580
581 change_state(MOVIE_STATE_PLAY);
582
583 Movie.CurrentFrame = current_frame;
584 Movie.CurrentSample = current_sample;
585 }
586
587 Movie.InputBufferPtr = Movie.InputBuffer + (Movie.BytesPerSample * Movie.CurrentSample);
588 read_frame_controller_data(true);
589
590 return (SUCCESS);
591}
592
593int S9xMovieOpen (const char *filename, bool8 read_only)
594{
595 FILE *fd;
596 STREAM stream;
597 int result;
598 int fn;
599
600 if (!(fd = fopen(filename, "rb+")))
601 {
602 if (!(fd = fopen(filename, "rb")))
603 return (FILE_NOT_FOUND);
604 else
605 read_only = TRUE;
606 }
607
608 change_state(MOVIE_STATE_NONE);
609
610 result = read_movie_header(fd, &Movie);
611 if (result != SUCCESS)
612 {
613 fclose(fd);
614 return (result);
615 }
616
617 read_movie_extrarominfo(fd, &Movie);
618
619 fflush(fd);
620 fn = fileno(fd);
621
622 store_previous_settings();
623 restore_movie_settings();
624
625 lseek(fn, Movie.SaveStateOffset, SEEK_SET);
626
627 // reopen stream to access as gzipped data
628 stream = REOPEN_STREAM(fn, "rb");
629 if (!stream)
630 return (FILE_NOT_FOUND);
631
632 if (Movie.Opts & MOVIE_OPT_FROM_RESET)
633 {
634 S9xReset();
635 reset_controllers();
636 result = (READ_STREAM(Memory.SRAM, 0x20000, stream) == 0x20000) ? SUCCESS : WRONG_FORMAT;
637 }
638 else
639 result = S9xUnfreezeFromStream(stream);
640
641 // do not close stream but close FILE *
642 // (msvcrt will try to close all open FILE *handles on exit - if we do CLOSE_STREAM here
643 // the underlying file will be closed by zlib, causing problems when msvcrt tries to do it)
644 delete stream;
645 fclose(fd);
646
647 if (result != SUCCESS)
648 return (result);
649
650 if (!(fd = fopen(filename, "rb+")))
651 {
652 if (!(fd = fopen(filename, "rb")))
653 return (FILE_NOT_FOUND);
654 else
655 read_only = TRUE;
656 }
657
658 if (fseek(fd, Movie.ControllerDataOffset, SEEK_SET))
659 {
660 fclose(fd);
661 return (WRONG_FORMAT);
662 }
663
664 Movie.File = fd;
665 Movie.BytesPerSample = bytes_per_sample();
666 Movie.InputBufferPtr = Movie.InputBuffer;
667 reserve_buffer_space(Movie.BytesPerSample * (Movie.MaxSample + 1));
668
669 if (!fread(Movie.InputBufferPtr, 1, Movie.BytesPerSample * (Movie.MaxSample + 1), fd))
670 {
671 printf ("Failed to read from movie file.\n");
672 fclose(fd);
673 return (WRONG_FORMAT);
674 }
675
676 // read "baseline" controller data
677 if (Movie.MaxSample && Movie.MaxFrame)
678 read_frame_controller_data(true);
679
680 Movie.CurrentFrame = 0;
681 Movie.CurrentSample = 0;
682 Movie.ReadOnly = read_only;
683 strncpy(Movie.Filename, filename, PATH_MAX + 1);
684 Movie.Filename[PATH_MAX] = 0;
685
686 change_state(MOVIE_STATE_PLAY);
687
688 S9xUpdateFrameCounter(-1);
689
690 S9xMessage(S9X_INFO, S9X_MOVIE_INFO, MOVIE_INFO_REPLAY);
691
692 return (SUCCESS);
693}
694
695int S9xMovieCreate (const char *filename, uint8 controllers_mask, uint8 opts, const wchar_t *metadata, int metadata_length)
696{
697 FILE *fd;
698 STREAM stream;
699
700 if (controllers_mask == 0)
701 return (WRONG_FORMAT);
702
703 if (!(fd = fopen(filename, "wb")))
704 return (FILE_NOT_FOUND);
705
706 if (metadata_length > MOVIE_MAX_METADATA)
707 metadata_length = MOVIE_MAX_METADATA;
708
709 change_state(MOVIE_STATE_NONE);
710
711 store_previous_settings();
712 store_movie_settings();
713
714 Movie.MovieId = (uint32) time(NULL);
715 Movie.RerecordCount = 0;
716 Movie.MaxFrame = 0;
717 Movie.MaxSample = 0;
718 Movie.SaveStateOffset = SMV_HEADER_SIZE + (sizeof(uint16) * metadata_length) + SMV_EXTRAROMINFO_SIZE;
719 Movie.ControllerDataOffset = 0;
720 Movie.ControllersMask = controllers_mask;
721 Movie.Opts = opts;
722 Movie.SyncFlags = MOVIE_SYNC_DATA_EXISTS | MOVIE_SYNC_HASROMINFO;
723
724 write_movie_header(fd, &Movie);
725
726 // convert wchar_t metadata string/array to a uint16 array
727 // XXX: UTF-8 is much better...
728 if (metadata_length > 0)
729 {
730 uint8 meta_buf[sizeof(uint16) * MOVIE_MAX_METADATA];
731 for (int i = 0; i < metadata_length; i++)
732 {
733 uint16 c = (uint16) metadata[i];
734 meta_buf[i * 2] = (uint8) (c & 0xff);
735 meta_buf[i * 2 + 1] = (uint8) ((c >> 8) & 0xff);
736 }
737
738 if (!fwrite(meta_buf, sizeof(uint16), metadata_length, fd))
739 printf ("Failed writing movie metadata.\n");
740 }
741
742 Movie.ROMCRC32 = Memory.ROMCRC32;
743 strncpy(Movie.ROMName, Memory.RawROMName, 23);
744
745 write_movie_extrarominfo(fd, &Movie);
746
747 fclose(fd);
748
749 stream = OPEN_STREAM(filename, "ab");
750 if (!stream)
751 return (FILE_NOT_FOUND);
752
753 if (opts & MOVIE_OPT_FROM_RESET)
754 {
755 S9xReset();
756 reset_controllers();
757 WRITE_STREAM(Memory.SRAM, 0x20000, stream);
758 }
759 else
760 S9xFreezeToStream(stream);
761
762 CLOSE_STREAM(stream);
763
764 if (!(fd = fopen(filename, "rb+")))
765 return (FILE_NOT_FOUND);
766
767 fseek(fd, 0, SEEK_END);
768 Movie.ControllerDataOffset = (uint32) ftell(fd);
769
770 // 16-byte align the controller input, for hex-editing friendliness if nothing else
771 while (Movie.ControllerDataOffset % 16)
772 {
773 fputc(0xcc, fd); // arbitrary
774 Movie.ControllerDataOffset++;
775 }
776
777 // write "baseline" controller data
778 Movie.File = fd;
779 Movie.BytesPerSample = bytes_per_sample();
780 Movie.InputBufferPtr = Movie.InputBuffer;
781 write_frame_controller_data();
782
783 Movie.CurrentFrame = 0;
784 Movie.CurrentSample = 0;
785 Movie.ReadOnly = false;
786 strncpy(Movie.Filename, filename, PATH_MAX + 1);
787 Movie.Filename[PATH_MAX] = 0;
788
789 change_state(MOVIE_STATE_RECORD);
790
791 S9xUpdateFrameCounter(-1);
792
793 S9xMessage(S9X_INFO, S9X_MOVIE_INFO, MOVIE_INFO_RECORD);
794
795 return (SUCCESS);
796}
797
798int S9xMovieGetInfo (const char *filename, struct MovieInfo *info)
799{
800 FILE *fd;
801 SMovie local_movie;
802 int metadata_length;
803 int result, i;
804
805 flush_movie();
806
807 memset(info, 0, sizeof(*info));
808
809 if (!(fd = fopen(filename, "rb")))
810 return (FILE_NOT_FOUND);
811
812 result = read_movie_header(fd, &local_movie);
813 if (result != SUCCESS)
814 {
815 fclose(fd);
816 return (result);
817 }
818
819 info->TimeCreated = (time_t) local_movie.MovieId;
820 info->Version = local_movie.Version;
821 info->Opts = local_movie.Opts;
822 info->SyncFlags = local_movie.SyncFlags;
823 info->ControllersMask = local_movie.ControllersMask;
824 info->RerecordCount = local_movie.RerecordCount;
825 info->LengthFrames = local_movie.MaxFrame;
826 info->LengthSamples = local_movie.MaxSample;
827 info->PortType[0] = local_movie.PortType[0];
828 info->PortType[1] = local_movie.PortType[1];
829
830 if (local_movie.SaveStateOffset > SMV_HEADER_SIZE)
831 {
832 uint8 meta_buf[sizeof(uint16) * MOVIE_MAX_METADATA];
833 int curRomInfoSize = (local_movie.SyncFlags & MOVIE_SYNC_HASROMINFO) ? SMV_EXTRAROMINFO_SIZE : 0;
834
835 metadata_length = ((int) local_movie.SaveStateOffset - SMV_HEADER_SIZE - curRomInfoSize) / sizeof(uint16);
836 metadata_length = (metadata_length >= MOVIE_MAX_METADATA) ? MOVIE_MAX_METADATA - 1 : metadata_length;
837 metadata_length = (int) fread(meta_buf, sizeof(uint16), metadata_length, fd);
838
839 for (i = 0; i < metadata_length; i++)
840 {
841 uint16 c = meta_buf[i * 2] | (meta_buf[i * 2 + 1] << 8);
842 info->Metadata[i] = (wchar_t) c;
843 }
844
845 info->Metadata[i] = '\0';
846 }
847 else
848 info->Metadata[0] = '\0';
849
850 read_movie_extrarominfo(fd, &local_movie);
851
852 info->ROMCRC32 = local_movie.ROMCRC32;
853 strncpy(info->ROMName, local_movie.ROMName, 23);
854
855 fclose(fd);
856 if ((fd = fopen(filename, "r+")) == NULL)
857 info->ReadOnly = true;
858 else
859 fclose(fd);
860
861 return (SUCCESS);
862}
863
864void S9xMovieUpdate (bool addFrame)
865{
866 switch (Movie.State)
867 {
868 case MOVIE_STATE_PLAY:
869 {
870 if (Movie.CurrentFrame >= Movie.MaxFrame || Movie.CurrentSample >= Movie.MaxSample)
871 {
872 change_state(MOVIE_STATE_NONE);
873 S9xMessage(S9X_INFO, S9X_MOVIE_INFO, MOVIE_INFO_END);
874 return;
875 }
876 else
877 {
878 if (addFrame)
879 S9xUpdateFrameCounter();
880 else
881 if (SKIPPED_POLLING_PORT_TYPE(Movie.PortType[0]) && SKIPPED_POLLING_PORT_TYPE(Movie.PortType[1]))
882 return;
883
884 read_frame_controller_data(addFrame);
885 Movie.CurrentSample++;
886 if (addFrame)
887 Movie.CurrentFrame++;
888 }
889
890 break;
891 }
892
893 case MOVIE_STATE_RECORD:
894 {
895 if (addFrame)
896 S9xUpdateFrameCounter();
897 else
898 if (SKIPPED_POLLING_PORT_TYPE(Movie.PortType[0]) && SKIPPED_POLLING_PORT_TYPE(Movie.PortType[1]))
899 return;
900
901 write_frame_controller_data();
902 Movie.MaxSample = ++Movie.CurrentSample;
903 if (addFrame)
904 Movie.MaxFrame = ++Movie.CurrentFrame;
905
906 if (!fwrite((Movie.InputBufferPtr - Movie.BytesPerSample), 1, Movie.BytesPerSample, Movie.File))
907 printf ("Error writing control data.\n");
908
909 break;
910 }
911
912 default:
913 {
914 if (addFrame)
915 S9xUpdateFrameCounter();
916
917 break;
918 }
919 }
920}
921
922void S9xMovieUpdateOnReset (void)
923{
924 if (Movie.State == MOVIE_STATE_RECORD)
925 {
926 reserve_buffer_space((uint32) (Movie.InputBufferPtr + Movie.BytesPerSample - Movie.InputBuffer));
927 memset(Movie.InputBufferPtr, 0xFF, Movie.BytesPerSample);
928 Movie.InputBufferPtr += Movie.BytesPerSample;
929 Movie.MaxSample = ++Movie.CurrentSample;
930 Movie.MaxFrame = ++Movie.CurrentFrame;
931
932 if (!fwrite((Movie.InputBufferPtr - Movie.BytesPerSample), 1, Movie.BytesPerSample, Movie.File))
933 printf ("Failed writing reset data.\n");
934 }
935}
936
937void S9xMovieInit (void)
938{
939 memset(&Movie, 0, sizeof(Movie));
940 Movie.State = MOVIE_STATE_NONE;
941}
942
943void S9xMovieStop (bool8 suppress_message)
944{
945 if (Movie.State != MOVIE_STATE_NONE)
946 {
947 change_state(MOVIE_STATE_NONE);
948
949 if (!suppress_message)
950 S9xMessage(S9X_INFO, S9X_MOVIE_INFO, MOVIE_INFO_STOP);
951 }
952}
953
954void S9xMovieShutdown (void)
955{
956 if (S9xMovieActive())
957 S9xMovieStop(TRUE);
958}
959
960bool8 S9xMovieActive (void)
961{
962 return (Movie.State != MOVIE_STATE_NONE);
963}
964
965bool8 S9xMoviePlaying (void)
966{
967 return (Movie.State == MOVIE_STATE_PLAY);
968}
969
970bool8 S9xMovieRecording (void)
971{
972 return (Movie.State == MOVIE_STATE_RECORD);
973}
974
975uint8 S9xMovieControllers (void)
976{
977 return (Movie.ControllersMask);
978}
979
980bool8 S9xMovieReadOnly (void)
981{
982 if (!S9xMovieActive())
983 return (FALSE);
984 return (Movie.ReadOnly);
985}
986
987uint32 S9xMovieGetId (void)
988{
989 if (!S9xMovieActive())
990 return (0);
991 return (Movie.MovieId);
992}
993
994uint32 S9xMovieGetLength (void)
995{
996 if (!S9xMovieActive())
997 return (0);
998 return (Movie.MaxFrame);
999}
1000
1001uint32 S9xMovieGetFrameCounter (void)
1002{
1003 if (!S9xMovieActive())
1004 return (0);
1005 return (Movie.CurrentFrame);
1006}
1007
1008void S9xMovieToggleRecState (void)
1009{
1010 Movie.ReadOnly = !Movie.ReadOnly;
1011
1012 if (Movie.ReadOnly)
1013 S9xMessage(S9X_INFO, S9X_MOVIE_INFO, "Movie is now read-only.");
1014 else
1015 S9xMessage(S9X_INFO, S9X_MOVIE_INFO, "Movie is now read+write.");
1016}
1017
1018void S9xMovieToggleFrameDisplay (void)
1019{
1020 Settings.DisplayMovieFrame = !Settings.DisplayMovieFrame;
1021 S9xReRefresh();
1022}
1023
1024void S9xUpdateFrameCounter (int offset)
1025{
1026 extern bool8 pad_read;
1027
1028 offset++;
1029
1030 if (!Settings.DisplayMovieFrame)
1031 *GFX.FrameDisplayString = 0;
1032 else
1033 if (Movie.State == MOVIE_STATE_RECORD)
1034 sprintf(GFX.FrameDisplayString, "Recording frame: %d%s",
1035 max(0, (int) (Movie.CurrentFrame + offset)), pad_read || !Settings.MovieNotifyIgnored ? "" : " (ignored)");
1036 else
1037 if (Movie.State == MOVIE_STATE_PLAY)
1038 sprintf(GFX.FrameDisplayString, "Playing frame: %d / %d",
1039 max(0, (int) (Movie.CurrentFrame + offset)), Movie.MaxFrame);
1040#ifdef NETPLAY_SUPPORT
1041 else
1042 if (Settings.NetPlay)
1043 sprintf(GFX.FrameDisplayString, "%s frame: %d", Settings.NetPlayServer ? "Server" : "Client",
1044 max(0, (int) (NetPlay.FrameCount + offset)));
1045#endif
1046}
1047