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 64 |
34 | #define 30 |
35 | #define BUFFER_GROWTH_SIZE 4096 |
36 | |
37 | enum MovieState |
38 | { |
39 | MOVIE_STATE_NONE = 0, |
40 | MOVIE_STATE_PLAY, |
41 | MOVIE_STATE_RECORD |
42 | }; |
43 | |
44 | struct 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 | |
76 | static struct SMovie Movie; |
77 | |
78 | static uint8 prevPortType[2]; |
79 | static int8 prevPortIDs[2][4]; |
80 | static bool8 prevMouseMaster, prevSuperScopeMaster, prevJustifierMaster, prevMultiPlayer5Master; |
81 | |
82 | static uint8 Read8 (uint8 *&); |
83 | static uint16 Read16 (uint8 *&); |
84 | static uint32 Read32 (uint8 *&); |
85 | static void Write8 (uint8, uint8 *&); |
86 | static void Write16 (uint16, uint8 *&); |
87 | static void Write32 (uint32, uint8 *&); |
88 | static void store_previous_settings (void); |
89 | static void restore_previous_settings (void); |
90 | static void store_movie_settings (void); |
91 | static void restore_movie_settings (void); |
92 | static int bytes_per_sample (void); |
93 | static void reserve_buffer_space (uint32); |
94 | static void reset_controllers (void); |
95 | static void read_frame_controller_data (bool); |
96 | static void write_frame_controller_data (void); |
97 | static void flush_movie (void); |
98 | static void truncate_movie (void); |
99 | static int read_movie_header (FILE *, SMovie *); |
100 | static int read_movie_extrarominfo (FILE *, SMovie *); |
101 | static void write_movie_header (FILE *, SMovie *); |
102 | static void write_movie_extrarominfo (FILE *, SMovie *); |
103 | static 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 | |
114 | static uint8 Read8 (uint8 *&ptr) |
115 | { |
116 | uint8 v = *ptr++; |
117 | return (v); |
118 | } |
119 | |
120 | static uint16 Read16 (uint8 *&ptr) |
121 | { |
122 | uint16 v = READ_WORD(ptr); |
123 | ptr += 2; |
124 | return (v); |
125 | } |
126 | |
127 | static uint32 Read32 (uint8 *&ptr) |
128 | { |
129 | uint32 v = READ_DWORD(ptr); |
130 | ptr += 4; |
131 | return (v); |
132 | } |
133 | |
134 | static void Write8 (uint8 v, uint8 *&ptr) |
135 | { |
136 | *ptr++ = v; |
137 | } |
138 | |
139 | static void Write16 (uint16 v, uint8 *&ptr) |
140 | { |
141 | WRITE_WORD(ptr, v); |
142 | ptr += 2; |
143 | } |
144 | |
145 | static void Write32 (uint32 v, uint8 *&ptr) |
146 | { |
147 | WRITE_DWORD(ptr, v); |
148 | ptr += 4; |
149 | } |
150 | |
151 | static 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 | |
166 | static 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 | |
177 | static 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 | |
187 | static 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 | |
198 | static 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 | |
225 | static 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 | |
238 | static 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 | |
261 | static 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 | |
320 | static 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 | |
360 | static 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 | |
373 | static 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 | |
385 | static int (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 | |
426 | static int (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 | |
442 | static void (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 | |
472 | static void (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 | |
485 | static 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 | |
506 | void 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 | |
532 | int 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 | |
593 | int 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 | |
695 | int 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 | |
798 | int 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 | |
864 | void 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 | |
922 | void 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 | |
937 | void S9xMovieInit (void) |
938 | { |
939 | memset(&Movie, 0, sizeof(Movie)); |
940 | Movie.State = MOVIE_STATE_NONE; |
941 | } |
942 | |
943 | void 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 | |
954 | void S9xMovieShutdown (void) |
955 | { |
956 | if (S9xMovieActive()) |
957 | S9xMovieStop(TRUE); |
958 | } |
959 | |
960 | bool8 S9xMovieActive (void) |
961 | { |
962 | return (Movie.State != MOVIE_STATE_NONE); |
963 | } |
964 | |
965 | bool8 S9xMoviePlaying (void) |
966 | { |
967 | return (Movie.State == MOVIE_STATE_PLAY); |
968 | } |
969 | |
970 | bool8 S9xMovieRecording (void) |
971 | { |
972 | return (Movie.State == MOVIE_STATE_RECORD); |
973 | } |
974 | |
975 | uint8 S9xMovieControllers (void) |
976 | { |
977 | return (Movie.ControllersMask); |
978 | } |
979 | |
980 | bool8 S9xMovieReadOnly (void) |
981 | { |
982 | if (!S9xMovieActive()) |
983 | return (FALSE); |
984 | return (Movie.ReadOnly); |
985 | } |
986 | |
987 | uint32 S9xMovieGetId (void) |
988 | { |
989 | if (!S9xMovieActive()) |
990 | return (0); |
991 | return (Movie.MovieId); |
992 | } |
993 | |
994 | uint32 S9xMovieGetLength (void) |
995 | { |
996 | if (!S9xMovieActive()) |
997 | return (0); |
998 | return (Movie.MaxFrame); |
999 | } |
1000 | |
1001 | uint32 S9xMovieGetFrameCounter (void) |
1002 | { |
1003 | if (!S9xMovieActive()) |
1004 | return (0); |
1005 | return (Movie.CurrentFrame); |
1006 | } |
1007 | |
1008 | void 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 | |
1018 | void S9xMovieToggleFrameDisplay (void) |
1019 | { |
1020 | Settings.DisplayMovieFrame = !Settings.DisplayMovieFrame; |
1021 | S9xReRefresh(); |
1022 | } |
1023 | |
1024 | void 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 | |