1 /*******************************************************************************
2 Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
4 (c) Copyright 1996 - 2002 Gary Henderson (gary.henderson@ntlworld.com) and
5 Jerremy Koot (jkoot@snes9x.com)
7 (c) Copyright 2001 - 2004 John Weidman (jweidman@slip.net)
9 (c) Copyright 2002 - 2004 Brad Jorsch (anomie@users.sourceforge.net),
10 funkyass (funkyass@spam.shaw.ca),
11 Joel Yliluoma (http://iki.fi/bisqwit/)
12 Kris Bleakley (codeviolation@hotmail.com),
14 Nach (n-a-c-h@users.sourceforge.net),
15 Peter Bortas (peter@bortas.org) and
16 zones (kasumitokoduck@yahoo.com)
18 C4 x86 assembler and some C emulation code
19 (c) Copyright 2000 - 2003 zsKnight (zsknight@zsnes.com),
20 _Demo_ (_demo_@zsnes.com), and Nach
23 (c) Copyright 2003 Brad Jorsch
26 (c) Copyright 1998 - 2004 Ivar (ivar@snes9x.com), _Demo_, Gary Henderson,
27 John Weidman, neviksti (neviksti@hotmail.com),
28 Kris Bleakley, Andreas Naive
31 (c) Copyright 2003 Kris Bleakley, John Weidman, neviksti, Matthew Kendora, and
32 Lord Nightmare (lord_nightmare@users.sourceforge.net
35 (c) Copyright 2001 - 2004 zsKnight, pagefault (pagefault@zsnes.com) and
37 Ported from x86 assembler to C by sanmaiwashi
39 SPC7110 and RTC C++ emulator code
40 (c) Copyright 2002 Matthew Kendora with research by
41 zsKnight, John Weidman, and Dark Force
44 (c) Copyright 2003 Brad Jorsch with research by
45 Andreas Naive and John Weidman
48 (c) Copyright 2001 John Weidman
50 ST010 C++ emulator code
51 (c) Copyright 2003 Feather, Kris Bleakley, John Weidman and Matthew Kendora
53 Super FX x86 assembler emulator code
54 (c) Copyright 1998 - 2003 zsKnight, _Demo_, and pagefault
56 Super FX C emulator code
57 (c) Copyright 1997 - 1999 Ivar, Gary Henderson and John Weidman
60 SH assembler code partly based on x86 assembler code
61 (c) Copyright 2002 - 2004 Marcus Comstedt (marcus@mc.pp.se)
63 Input recording/playback code
64 (c) Copyright 2004 blip
66 Specific ports contains the works of other authors. See headers in
69 Snes9x homepage: http://www.snes9x.com
71 Permission to use, copy, modify and distribute Snes9x in both binary and
72 source form, for non-commercial purposes, is hereby granted without fee,
73 providing that this license information and copyright notice appear with
74 all copies and any derived work.
76 This software is provided 'as-is', without any express or implied
77 warranty. In no event shall the authors be held liable for any damages
78 arising from the use of this software.
80 Snes9x is freeware for PERSONAL USE only. Commercial users should
81 seek permission of the copyright holders first. Commercial use includes
82 charging money for Snes9x or software derived from Snes9x.
84 The copyright holders request that bug fixes and improvements to the code
85 should be forwarded to them so everyone can benefit from the modifications
88 Super NES and Super Nintendo Entertainment System are trademarks of
89 Nintendo Co., Limited and its subsidiary companies.
90 *******************************************************************************/
99 #if defined(__unix) || defined(__linux) || defined(__sun) || defined(__DJGPP)
101 #include <sys/types.h>
102 #include <sys/stat.h>
116 #include "snapshot.h"
118 #define SMV_MAGIC 0x1a564d53 // SMV0x1a
119 #define SMV_VERSION 1
120 #define SMV_HEADER_SIZE 32
121 #define CONTROLLER_DATA_SIZE 2
122 #define BUFFER_GROWTH_SIZE 4096
133 enum MovieState State;
134 char Filename [_MAX_PATH];
136 uint32 SaveStateOffset;
137 uint32 ControllerDataOffset;
141 uint32 RerecordCount;
142 uint8 ControllersMask;
145 uint32 BytesPerFrame;
147 uint32 InputBufferSize;
148 uint8* InputBufferPtr;
150 char FrameDisplayString[256];
155 struct MovieFileHeader
157 uint32 magic; // SMV0x1a
159 uint32 uid; // used to match savestates to a particular movie
160 uint32 rerecord_count;
161 uint32 length_frames;
163 uint32 offset_to_savestate; // smvs have an embedded savestate
164 uint32 offset_to_controller_data;
165 // after the header comes extra metadata
166 // sizeof(metadata) = offset_to_savestate - sizeof(MovieFileHeader)
170 static int bytes_per_frame()
178 if(Movie.ControllersMask & (1<<i))
184 return CONTROLLER_DATA_SIZE*num_controllers;
187 static inline uint32 Read32(const uint8*& ptr)
189 uint32 v=(ptr[0] | (ptr[1]<<8) | (ptr[2]<<16) | (ptr[3]<<24));
194 static inline uint16 Read16(const uint8*& ptr) /* const version */
196 uint16 v=(ptr[0] | (ptr[1]<<8));
201 static inline uint16 Read16(uint8*& ptr) /* non-const version */
203 uint16 v=(ptr[0] | (ptr[1]<<8));
208 static void Write32(uint32 v, uint8*& ptr)
210 ptr[0]=(uint8)(v&0xff);
211 ptr[1]=(uint8)((v>>8)&0xff);
212 ptr[2]=(uint8)((v>>16)&0xff);
213 ptr[3]=(uint8)((v>>24)&0xff);
217 static void Write16(uint16 v, uint8*& ptr)
219 ptr[0]=(uint8)(v&0xff);
220 ptr[1]=(uint8)((v>>8)&0xff);
224 static int read_movie_header(FILE* fd, SMovie* movie)
226 uint8 header[SMV_HEADER_SIZE];
227 if(fread(header, 1, SMV_HEADER_SIZE, fd) != SMV_HEADER_SIZE)
230 const uint8* ptr=header;
231 uint32 magic=Read32(ptr);
235 uint32 version=Read32(ptr);
236 if(version!=SMV_VERSION)
237 return WRONG_VERSION;
239 movie->MovieId=Read32(ptr);
240 movie->RerecordCount=Read32(ptr);
241 movie->MaxFrame=Read32(ptr);
243 movie->ControllersMask=*ptr++;
247 movie->SaveStateOffset=Read32(ptr);
248 movie->ControllerDataOffset=Read32(ptr);
253 static void write_movie_header(FILE* fd, const SMovie* movie)
255 uint8 header[SMV_HEADER_SIZE];
258 Write32(SMV_MAGIC, ptr);
259 Write32(SMV_VERSION, ptr);
260 Write32(movie->MovieId, ptr);
261 Write32(movie->RerecordCount, ptr);
262 Write32(movie->MaxFrame, ptr);
264 *ptr++=movie->ControllersMask;
269 Write32(movie->SaveStateOffset, ptr);
270 Write32(movie->ControllerDataOffset, ptr);
272 fwrite(header, 1, SMV_HEADER_SIZE, fd);
275 static void flush_movie()
277 fseek(Movie.File, 0, SEEK_SET);
278 write_movie_header(Movie.File, &Movie);
279 fseek(Movie.File, Movie.ControllerDataOffset, SEEK_SET);
280 fwrite(Movie.InputBuffer, 1, Movie.BytesPerFrame*(Movie.MaxFrame+1), Movie.File);
283 static void change_state(MovieState new_state)
285 if(new_state==Movie.State)
288 if(Movie.State==MOVIE_STATE_RECORD)
293 Movie.State=new_state;
295 if(new_state==MOVIE_STATE_NONE)
299 // FIXME: truncate movie to MaxFrame length
300 /* truncate() could be used, if it's certain
301 * that the savestate block is never after
302 * the controller data block. It is not guaranteed
308 static void reserve_buffer_space(uint32 space_needed)
310 if(space_needed > Movie.InputBufferSize)
312 uint32 ptr_offset = Movie.InputBufferPtr - Movie.InputBuffer;
313 uint32 alloc_chunks = space_needed / BUFFER_GROWTH_SIZE;
314 Movie.InputBufferSize = BUFFER_GROWTH_SIZE * (alloc_chunks+1);
315 Movie.InputBuffer = (uint8*)realloc(Movie.InputBuffer, Movie.InputBufferSize);
316 Movie.InputBufferPtr = Movie.InputBuffer + ptr_offset;
320 static void read_frame_controller_data()
325 if(Movie.ControllersMask & (1<<i))
327 IPPU.Joypads[i]=(uint32)(Read16(Movie.InputBufferPtr)) | 0x80000000L;
331 IPPU.Joypads[i]=0; // pretend the controller is disconnected
336 static void write_frame_controller_data()
338 reserve_buffer_space((uint32)((Movie.InputBufferPtr+Movie.BytesPerFrame)-Movie.InputBuffer));
343 if(Movie.ControllersMask & (1<<i))
345 Write16((uint16)(IPPU.Joypads[i] & 0xffff), Movie.InputBufferPtr);
349 IPPU.Joypads[i]=0; // pretend the controller is disconnected
356 memset(&Movie, 0, sizeof(Movie));
357 Movie.State = MOVIE_STATE_NONE;
360 int S9xMovieOpen (const char* filename, bool8 read_only)
367 return FILE_NOT_FOUND;
370 if(!(fd=fopen(filename, read_only ? "rb" : "rb+")))
371 return FILE_NOT_FOUND;
373 // stop current movie before opening
374 change_state(MOVIE_STATE_NONE);
377 if((result=read_movie_header(fd, &Movie))!=SUCCESS)
386 // apparently this lseek is necessary
387 lseek(fn, Movie.SaveStateOffset, SEEK_SET);
388 if(!(stream=REOPEN_STREAM(fn, "rb")))
389 return FILE_NOT_FOUND;
391 if(Movie.Opts & MOVIE_OPT_FROM_RESET)
394 // save only SRAM for a from-reset snapshot
395 result=(READ_STREAM(SRAM, 0x20000, stream) == 0x20000) ? SUCCESS : WRONG_FORMAT;
399 result=S9xUnfreezeFromStream(stream);
401 CLOSE_STREAM(stream);
408 if(!(fd=fopen(filename, read_only ? "rb" : "rb+")))
409 return FILE_NOT_FOUND;
411 if(fseek(fd, Movie.ControllerDataOffset, SEEK_SET))
414 // read controller data
416 Movie.BytesPerFrame=bytes_per_frame();
417 Movie.InputBufferPtr=Movie.InputBuffer;
418 uint32 to_read=Movie.BytesPerFrame * (Movie.MaxFrame+1);
419 reserve_buffer_space(to_read);
420 fread(Movie.InputBufferPtr, 1, to_read, fd);
422 // read "baseline" controller data
423 read_frame_controller_data();
425 strncpy(Movie.Filename, filename, _MAX_PATH);
426 Movie.Filename[_MAX_PATH-1]='\0';
427 Movie.CurrentFrame=0;
428 Movie.ReadOnly=read_only;
429 change_state(MOVIE_STATE_PLAY);
431 S9xMessage(S9X_INFO, S9X_MOVIE_INFO, MOVIE_INFO_REPLAY);
436 int S9xMovieCreate (const char* filename, uint8 controllers_mask, uint8 opts, const wchar_t* metadata, int metadata_length)
442 return FILE_NOT_FOUND;
445 if(controllers_mask==0)
448 if(!(fd=fopen(filename, "wb")))
449 return FILE_NOT_FOUND;
451 // stop current movie before opening
452 change_state(MOVIE_STATE_NONE);
454 if(metadata_length>MOVIE_MAX_METADATA)
456 metadata_length=MOVIE_MAX_METADATA;
459 Movie.MovieId=(uint32)time(NULL);
460 Movie.RerecordCount=0;
462 Movie.SaveStateOffset=SMV_HEADER_SIZE+(sizeof(uint16)*metadata_length);
463 Movie.ControllerDataOffset=0;
464 Movie.ControllersMask=controllers_mask;
468 Movie.Opts |= MOVIE_OPT_PAL;
472 Movie.Opts &= ~MOVIE_OPT_PAL;
475 write_movie_header(fd, &Movie);
477 // convert wchar_t metadata string/array to a uint16 array
478 if(metadata_length>0)
480 uint8 meta_buf[MOVIE_MAX_METADATA * sizeof(uint16)];
483 for(i=0; i<metadata_length; ++i)
485 uint16 c=(uint16)metadata[i];
486 meta_buf[i+i] =(uint8)(c&0xff);
487 meta_buf[i+i+1]=(uint8)((c>>8)&0xff);
490 fwrite(meta_buf, sizeof(uint16), metadata_length, fd);
497 // lseek(fn, Movie.SaveStateOffset, SEEK_SET);
498 if(!(stream=REOPEN_STREAM(fn, "ab")))
499 return FILE_NOT_FOUND;
501 if(opts & MOVIE_OPT_FROM_RESET)
504 // save only SRAM for a from-reset snapshot
505 WRITE_STREAM(SRAM, 0x20000, stream);
509 S9xFreezeToStream(stream);
511 CLOSE_STREAM(stream);
513 if(!(fd=fopen(filename, "rb+")))
514 return FILE_NOT_FOUND;
516 fseek(fd, 0, SEEK_END);
517 Movie.ControllerDataOffset=(uint32)ftell(fd);
519 // write "baseline" controller data
521 Movie.BytesPerFrame=bytes_per_frame();
522 Movie.InputBufferPtr=Movie.InputBuffer;
523 write_frame_controller_data();
525 strncpy(Movie.Filename, filename, _MAX_PATH);
526 Movie.Filename[_MAX_PATH-1]='\0';
527 Movie.CurrentFrame=0;
528 Movie.ReadOnly=false;
529 change_state(MOVIE_STATE_RECORD);
531 S9xMessage(S9X_INFO, S9X_MOVIE_INFO, MOVIE_INFO_RECORD);
536 void S9xMovieUpdate ()
540 case MOVIE_STATE_PLAY:
541 if(Movie.CurrentFrame>=Movie.MaxFrame)
543 change_state(MOVIE_STATE_NONE);
544 S9xMessage(S9X_INFO, S9X_MOVIE_INFO, MOVIE_INFO_END);
549 if(Movie.FrameDisplay)
551 sprintf(Movie.FrameDisplayString, "Playing frame: %d", Movie.CurrentFrame);
552 S9xMessage (S9X_INFO, S9X_MOVIE_INFO, Movie.FrameDisplayString);
554 read_frame_controller_data();
555 ++Movie.CurrentFrame;
559 case MOVIE_STATE_RECORD:
561 if(Movie.FrameDisplay)
563 sprintf(Movie.FrameDisplayString, "Recording frame: %d", Movie.CurrentFrame);
564 S9xMessage (S9X_INFO, S9X_MOVIE_INFO, Movie.FrameDisplayString);
566 write_frame_controller_data();
567 ++Movie.CurrentFrame;
568 Movie.MaxFrame=Movie.CurrentFrame;
569 fwrite((Movie.InputBufferPtr - Movie.BytesPerFrame), 1, Movie.BytesPerFrame, Movie.File);
578 void S9xMovieStop (bool8 suppress_message)
580 if(Movie.State!=MOVIE_STATE_NONE)
582 change_state(MOVIE_STATE_NONE);
584 if(!suppress_message)
585 S9xMessage(S9X_INFO, S9X_MOVIE_INFO, MOVIE_INFO_STOP);
589 int S9xMovieGetInfo (const char* filename, struct MovieInfo* info)
596 return FILE_NOT_FOUND;
599 memset(info, 0, sizeof(*info));
600 if(!(fd=fopen(filename, "rb")))
601 return FILE_NOT_FOUND;
603 if((result=(read_movie_header(fd, &local_movie)))!=SUCCESS)
606 info->TimeCreated=(time_t)local_movie.MovieId;
607 info->LengthFrames=local_movie.MaxFrame;
608 info->RerecordCount=local_movie.RerecordCount;
609 info->Opts=local_movie.Opts;
610 info->ControllersMask=local_movie.ControllersMask;
612 if(local_movie.SaveStateOffset > SMV_HEADER_SIZE)
614 uint8 meta_buf[MOVIE_MAX_METADATA * sizeof(uint16)];
617 metadata_length=((int)local_movie.SaveStateOffset-SMV_HEADER_SIZE)/sizeof(uint16);
618 metadata_length=(metadata_length>=MOVIE_MAX_METADATA) ? MOVIE_MAX_METADATA-1 : metadata_length;
619 metadata_length=(int)fread(meta_buf, sizeof(uint16), metadata_length, fd);
621 for(i=0; i<metadata_length; ++i)
623 uint16 c=meta_buf[i+i] | (meta_buf[i+i+1] << 8);
624 info->Metadata[i]=(wchar_t)c;
626 info->Metadata[i]='\0';
630 info->Metadata[0]='\0';
635 if(access(filename, W_OK))
642 bool8 S9xMovieActive ()
644 return (Movie.State!=MOVIE_STATE_NONE);
647 bool8 S9xMovieReadOnly ()
649 if(!S9xMovieActive())
652 return Movie.ReadOnly;
655 uint32 S9xMovieGetId ()
657 if(!S9xMovieActive())
660 return Movie.MovieId;
663 uint32 S9xMovieGetLength ()
665 if(!S9xMovieActive())
668 return Movie.MaxFrame;
671 uint32 S9xMovieGetFrameCounter ()
673 if(!S9xMovieActive())
676 return Movie.CurrentFrame;
679 void S9xMovieToggleFrameDisplay ()
681 Movie.FrameDisplay = !Movie.FrameDisplay;
682 if(!Movie.FrameDisplay)
684 GFX.InfoStringTimeout = 1;
688 void S9xMovieFreeze (uint8** buf, uint32* size)
691 if(!S9xMovieActive())
699 // compute size needed for the buffer
700 uint32 size_needed = 4*3; // room for MovieId, CurrentFrame, and MaxFrame
701 size_needed += (uint32)(Movie.BytesPerFrame * (Movie.MaxFrame+1));
702 *buf=new uint8[size_needed];
711 Write32(Movie.MovieId, ptr);
712 Write32(Movie.CurrentFrame, ptr);
713 Write32(Movie.MaxFrame, ptr);
715 memcpy(ptr, Movie.InputBuffer, Movie.BytesPerFrame * (Movie.MaxFrame+1));
718 bool8 S9xMovieUnfreeze (const uint8* buf, uint32 size)
721 if(!S9xMovieActive())
726 const uint8* ptr = buf;
732 uint32 movie_id = Read32(ptr);
733 uint32 current_frame = Read32(ptr);
734 uint32 max_frame = Read32(ptr);
735 uint32 space_needed = (Movie.BytesPerFrame * (max_frame+1));
737 if(movie_id != Movie.MovieId ||
738 current_frame > max_frame ||
746 // here, we are going to take the input data from the savestate
747 // and make it the input data for the current movie, then continue
748 // writing new input data at the currentframe pointer
749 change_state(MOVIE_STATE_RECORD);
750 S9xMessage(S9X_INFO, S9X_MOVIE_INFO, MOVIE_INFO_RERECORD);
752 Movie.CurrentFrame = current_frame;
753 Movie.MaxFrame = max_frame;
754 ++Movie.RerecordCount;
756 reserve_buffer_space(space_needed);
757 memcpy(Movie.InputBuffer, ptr, space_needed);
759 fseek(Movie.File, Movie.ControllerDataOffset+(Movie.BytesPerFrame * (Movie.CurrentFrame+1)), SEEK_SET);
763 // here, we are going to keep the input data from the movie file
764 // and simply rewind to the currentframe pointer
765 // this will cause a desync if the savestate is not in sync
766 // with the on-disk recording data, but it's easily solved
767 // by loading another savestate or playing the movie from the beginning
769 // and older savestate might have a currentframe pointer past
770 // the end of the input data, so check for that here
771 if(current_frame > Movie.MaxFrame)
776 change_state(MOVIE_STATE_PLAY);
777 S9xMessage(S9X_INFO, S9X_MOVIE_INFO, MOVIE_INFO_REWIND);
779 Movie.CurrentFrame = current_frame;
782 Movie.InputBufferPtr = Movie.InputBuffer + (Movie.BytesPerFrame * Movie.CurrentFrame);
783 read_frame_controller_data();